wachit 0.2.2

Instant rebuilds for your dev workflow
Documentation
pub mod constants {
    use std::collections::HashMap;

    pub const CONFIG_FILE: &str = "wachit.json";
    pub const ERR_MESSAGE: &str = "wacht [wachit options] [target file]";

    pub const PY_EXTS: &str = ".py";
    pub const NODE_EXTS: &str = ".js .jsx .ts .tsx";
    pub const GOLANG_EXTS: &str = ".go";
    pub const CARGO_EXTS: &str = ".rs";
}

pub mod types {
    use std::{
        fmt,
        fs::{self, File},
        io::{ErrorKind, Read},
        ops::Index,
    };

    use serde::{Deserialize, Serialize};
    use serde_json::{Map, Value};

    use crate::constants::{CARGO_EXTS, ERR_MESSAGE, GOLANG_EXTS, NODE_EXTS, PY_EXTS};
    use crate::processor;

    #[derive(Serialize, Deserialize, Debug)]
    pub enum ConfigValues {
        String,
    }
    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
    pub enum Executable {
        NODE,
        GOLANG,
        CARGO,
        PYTHON,
    }

    impl fmt::Display for Executable {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            match self {
                Executable::NODE => write!(f, "node"),
                Executable::GOLANG => write!(f, "go run"),
                Executable::PYTHON => write!(f, "python3"),
                Executable::CARGO => write!(f, "cargo run"),
            }
        }
    }

    #[derive(Serialize, Deserialize, Debug, Clone)]
    pub struct Config {
        #[serde(default = "set_default_executable")]
        pub executable: Executable,

        #[serde(default = "set_default_target")]
        pub target: String,

        #[serde(default = "set_default_inspect")]
        pub inspect: bool,

        #[serde(default = "set_default_ignore_list")]
        pub ignore_list: Vec<String>,

        #[serde(default = "set_default_watch_list")]
        pub watch_list: Vec<String>,

        #[serde(default = "set_default_delay")]
        pub delay: u64,
    }
    impl Config {
        pub fn create_default_config() -> Config {
            Config {
                inspect: set_default_inspect(),
                executable: set_default_executable(),
                target: set_default_target(),
                watch_list: set_default_watch_list(),
                ignore_list: set_default_ignore_list(),
                delay: set_default_delay(),
            }
        }

        pub fn should_restart(&self, path: &str) -> bool {
            if self.ignore_list.len() > 0 {
                for wf in &self.ignore_list {
                    let mut fs = wf.clone();

                    if wf.starts_with(".") {
                        fs = wf[1..].to_string();
                    }

                    if path.ends_with(&fs) {
                        return false;
                    }
                }
            }

            if self.watch_list.len() > 0 {
                for wf in &self.watch_list {
                    let mut fs = wf.clone();

                    if wf.starts_with(".") {
                        fs = wf[1..].to_string();
                    }

                    if path.ends_with(&fs) {
                        return true;
                    }
                }
            }

            let extension_options: &str = match self.executable {
                Executable::NODE => NODE_EXTS,
                Executable::CARGO => CARGO_EXTS,
                Executable::GOLANG => GOLANG_EXTS,
                Executable::PYTHON => PY_EXTS,
            };

            for n_ext in extension_options.split(" ") {
                if path.ends_with(n_ext) {
                    return true;
                }
            }

            return false;
        }

        pub fn new(args: Vec<String>) -> Config {
            let file_conf_result = processor::load_file_config();
            let mut is_config_file: bool = false;
            let mut new_config: Config = match file_conf_result {
                Some(_conf) => {
                    is_config_file = true;
                    _conf
                }
                _ => Config::create_default_config(),
            };

            if is_config_file {
                return new_config;
            }

            if args.len() < 2 {
                panic!("Format: wachit [wachit options] [target or command name]")
            }

            let mut target_index = 1;

            loop {
                if target_index == args.len() - 1 {
                    break;
                }

                if target_index >= args.len() {
                    panic!("{ERR_MESSAGE} V");
                }

                if args[target_index].contains("--inspect") {
                    new_config.inspect = true;
                }

                if args[target_index].contains("--exec") {
                    let exec =
                        get_executable(args[target_index].split("=").collect::<Vec<&str>>()[1]);
                    new_config.executable = exec;
                }

                if args[target_index].contains("--ignore") {
                    let ls = args[target_index].split("=").collect::<Vec<&str>>()[1]
                        .split(",")
                        .map(|str| str.to_string())
                        .collect::<Vec<String>>();
                    new_config.ignore_list = ls;
                }

                if args[target_index].contains("--watch") {
                    let ls = args[target_index].split("=").collect::<Vec<&str>>()[1]
                        .split(",")
                        .map(|str| str.to_string())
                        .collect::<Vec<String>>();
                    new_config.watch_list = ls;
                }

                target_index += 1;
            }

            let target = File::open(&args[target_index]);

            let target_file: String = match target {
                Ok(_) => String::from(&args[target_index]),
                Err(_) => {
                    panic!("Invalid arguments: Target file not specified");
                }
            };

            new_config.target = target_file;
            println!("{:?}", new_config);
            new_config
        }
        pub fn get_command(&self) -> String {
            let mut cmd = String::from(self.executable.to_string());

            if self.inspect {
                cmd.push_str(" --inspect");
            };

            cmd.push_str(" ");
            cmd.push_str(&self.target);

            return cmd;
        }
    }

    fn set_default_executable() -> Executable {
        Executable::NODE
    }
    fn set_default_target() -> String {
        String::new()
    }
    fn set_default_inspect() -> bool {
        false
    }
    fn set_default_ignore_list() -> Vec<String> {
        vec![]
    }
    fn set_default_watch_list() -> Vec<String> {
        vec![]
    }
    fn set_default_delay() -> u64 {
        2000
    }

    fn get_executable(exec: &str) -> Executable {
        match exec {
            "NODE" => Executable::NODE,
            "GOLANG" => Executable::GOLANG,
            "PYTHON" => Executable::PYTHON,
            "CARGO" => Executable::CARGO,
            _ => Executable::NODE,
        }
    }
}

pub mod processor {
    use crate::{constants::CONFIG_FILE, types::Executable};
    use std::{
        fs::File,
        io::Read,
        process::{Child, Command, Stdio},
    };

    use crate::types::Config;

    pub fn load_file_config() -> Option<Config> {
        let conf_file = File::open(&CONFIG_FILE);

        match conf_file {
            Ok(mut config_file) => {
                let mut config_string: String = String::new();
                match config_file.read_to_string(&mut config_string) {
                    Ok(_) => {
                        let conf: Config = serde_json::from_str(&config_string).unwrap();
                        Some(conf)
                    }
                    _ => None,
                }
            }
            _ => None,
        }
    }

    pub fn run_command(config: &Config) -> Option<Child> {
        let cmd_v: Vec<String> = config
            .get_command()
            .split(" ")
            .map(|str| str.trim().to_string())
            .collect();

        println!("Starting wachit process...");
        let base_cmd = &cmd_v[0];
        let args = &cmd_v[1..];
        let mut command = Command::new(base_cmd);
        command.args(args);

        match command.spawn() {
            Ok(child) => Some(child),
            Err(err) => {
                println!("{:?}", err);
                None
            }
        }
    }

    pub fn should_ignore_path(path: &String, config: &Config) -> bool {
        if (path.contains("wachit.json")
            || path.contains("build")
            || path.contains("node_modules")
            || path.ends_with(".env")
            || path.ends_with(".gitignore")
            || path.ends_with(".next")
            || path.ends_with(".git")
            || (config.executable == Executable::CARGO && path.contains("target"))
            || path.contains(".vscode")
            || path.contains("dist"))
            || !config.should_restart(path)
        {
            return true;
        }

        return false;
    }
}