justrun 1.0.0

Just run it! A simple init system
Documentation
use anyhow::anyhow;
use clap::{Parser, Subcommand};
use justrun::{
    ipc::*,
    paths::{CONFIG, DEFAULT_CONFIG, RESULT_BASE},
};
use log::error;
use yaml_rust2 as yaml;

#[derive(Subcommand, Debug, PartialEq)]
enum Command {
    Start {
        #[arg(short, long)]
        name: String,
    },
    Stop {
        #[arg(short, long)]
        name: String,
    },
    Restart {
        #[arg(short, long)]
        name: String,
    },
    Status {
        #[arg(short, long)]
        name: String,
    },
    Reg,
    Unreg,
}

#[derive(Parser, Debug)]
#[command(name = "justrun")]
#[command(about = "Control your justrun services", long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Command,
}

fn main() {
    let args = Cli::parse();

    if args.command == Command::Reg || args.command == Command::Unreg {
        if !std::path::Path::new(CONFIG).exists() {
            println!("Config file not found, creating default config...");
            let default_config = DEFAULT_CONFIG;
            let config_parent = std::path::Path::new(CONFIG).parent().unwrap();
            std::fs::create_dir_all(config_parent)
                .map_err(|e| anyhow!("Failed to create directory: {}", e))
                .unwrap();
            std::fs::write(CONFIG, default_config)
                .map_err(|e| anyhow!("Failed to create config file: {}", e))
                .unwrap();
        }

        let config_str = std::fs::read_to_string(CONFIG)
            .map_err(|e| anyhow!("Failed to read config file: {}", e))
            .unwrap();
        let mut config = yaml::YamlLoader::load_from_str(&config_str)
            .map_err(|e| anyhow!("Failed to parse config file: {}", e))
            .unwrap()[0]
            .clone();

        let current_dir = std::env::current_dir()
            .map_err(|e| anyhow!("Failed to get current directory: {}", e))
            .unwrap();
        let current_dir = current_dir
            .into_os_string()
            .into_string()
            .map_err(|err| anyhow!("Failed to convert path to string: {:?}", err))
            .unwrap();
        let mut services = config["services"].as_vec().unwrap().clone();
        if args.command == Command::Reg {
            services.push(yaml::Yaml::String(current_dir));
        } else if args.command == Command::Unreg {
            services.retain(|s| {
                if let Some(path) = s.as_str() {
                    path != current_dir
                } else {
                    true
                }
            });
        }
        config["services"] = yaml::Yaml::Array(services);

        let mut config_out_str = String::new();
        let mut emitter = yaml::YamlEmitter::new(&mut config_out_str);
        emitter
            .dump(&config)
            .map_err(|e| anyhow!("Failed to dump config: {}", e))
            .unwrap();
        std::fs::write(CONFIG, config_out_str)
            .map_err(|e| anyhow!("Failed to write config file: {}", e))
            .unwrap();
    }

    let mut transmitter = Transmitter::new().unwrap_or_else(|err| {
        error!("Failed to create Transmitter: {}", err);
        std::process::exit(1);
    });

    let ts = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_secs();

    let command = match args.command {
        Command::Start { name } => format!("{} start {}", ts, name),
        Command::Stop { name } => format!("{} stop {}", ts, name),
        Command::Restart { name } => format!("{} restart {}", ts, name),
        Command::Status { name } => format!("{} status {}", ts, name),
        _ => {
            error!("Unsupported command to send: {:?}", args.command);
            std::process::exit(1);
        }
    };

    transmitter.send(command.as_str()).unwrap_or_else(|err| {
        eprintln!("Failed to send command: {}", err);
        std::process::exit(1);
    });

    let path = std::path::Path::new(RESULT_BASE).join(ts.to_string());
    loop {
        if path.exists() {
            let result = std::fs::read_to_string(&path).unwrap_or_else(|err| {
                error!("Failed to read result file: {}", err);
                std::process::exit(1);
            });
            println!("{}", result);
            std::fs::remove_file(&path).unwrap_or_else(|err| {
                error!("Failed to remove result file: {}", err);
            });
            break;
        } else {
            std::thread::sleep(std::time::Duration::from_millis(300));
        }
    }
}