autosway 0.5.0

Automation program
Documentation
use crate::common::*;
use clap::{self, arg, ArgAction, Command};
use std::error::Error;
use std::{fs, io::prelude::*};

impl State {
    pub fn run_cli(&mut self) -> Result<(), Box<dyn Error>> {
        let matches = Command::new("autosway")
            .about("System automation program")
            .arg(arg!(-v --verbose "Set verbose mode").action(ArgAction::SetTrue))
            .subcommand_required(true)
            .subcommand(Command::new("list").about("List knifes"))
            .subcommand(Command::new("api").about("Display API documentation as HTML in browser"))
            .subcommand(Command::new("doc").about("Display documentation as HTML in browser"))
            .subcommand(Command::new("repl").about("Run a Lua REPL"))
            .subcommand(
                Command::new("run")
                    .about("Run a knife")
                    .arg(arg!(<KNIFE> "The knife to run"))
                    .arg_required_else_help(true),
            )
            .subcommand(
                Command::new("script")
                    .about("Run a Lua source file")
                    .arg(arg!(<FILE> "The script to run"))
                    .arg_required_else_help(true),
            )
            .get_matches();

        self.options.verbose = matches.get_flag("verbose");

        let cfg_dir = if let Some(p) = get_config_dir(ConfigDir::RootDir) {
            p
        } else {
            log::log!([fatal] "Failed to get config directory");
        };

        fs::create_dir_all(&cfg_dir)?;

        match matches.subcommand() {
            Some(("list", _)) => {
                self.knifes.par_iter().for_each(|knife| {
                    if !self.options.verbose {
                        println!("{}: {}", knife.name, knife.path.display());
                    } else {
                        println!("{}: {} - {}", knife.name, knife.path.display(), knife.init);
                    }
                });
            }
            Some(("api", _)) => {
                let html = markdown::to_html(include_str!("../API.md"));
                if self.options.verbose {
                    println!("{}", &html);
                }

                let path = std::env::temp_dir().join("autosway_api.html");
                let mut file = fs::File::create(&path)?;

                file.write_all(html.as_bytes())?;
                open::that(&path)?;
            }
            Some(("doc", _)) => {
                let html = markdown::to_html(include_str!("../DOC.md"));
                if self.options.verbose {
                    println!("{}", &html);
                }

                let path = std::env::temp_dir().join("autosway_doc.html");
                let mut file = fs::File::create(&path)?;

                file.write_all(html.as_bytes())?;
                open::that(&path)?;
            }
            Some(("run", sm)) => {
                let s = match sm.get_one::<String>("KNIFE") {
                    Some(s) => s,
                    None => {
                        log::log!([fatal] "No argument supplied");
                    }
                };

                if let Err(e) = self.run_knife(s) {
                    log::log!([error] "Failed to run knife: {e}");
                }
            }
            Some(("script", sm)) => {
                let s = match sm.get_one::<String>("FILE") {
                    Some(s) => s,
                    None => {
                        log::log!([fatal] "No argument supplied");
                    }
                };

                let mut file = fs::File::open(s)?;
                let mut buf = String::new();
                file.read_to_string(&mut buf)?;

                if let Err(e) = self.lua.load(buf).exec() {
                    log::log!([error] "Failed to run script: {e}");
                }
            }
            Some(("repl", _)) => {
                use rustyline::{error::ReadlineError, DefaultEditor};
                let history_path = &cfg_dir.join("repl_history");
                let mut rl = DefaultEditor::new()?;

                let _ = rl.load_history(&history_path);

                'repl: loop {
                    match rl.readline("lua> ") {
                        Ok(line) => {
                            rl.add_history_entry(line.as_str())?;

                            if let Err(e) = self.lua.load(line).exec() {
                                log::log!([error] "Failed to run code: {e}");
                            }
                        }
                        Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break 'repl,
                        Err(e) => {
                            log::log!([error] "Readline error: {e}");
                        }
                    }
                }

                rl.save_history(&history_path)?;
            }
            _ => {}
        }

        Ok(())
    }
}