jarn 0.1.0

A package manager for jaseci applications
Documentation
use console::{style, Key, Style};
use dialoguer::theme::ColorfulTheme;
use dialoguer::{History, Input};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::collections::VecDeque;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::{env, fs};

#[macro_use]
extern crate log;

#[derive(Deserialize, Serialize, Debug)]
pub struct JaseciConfig {
    scripts: Scripts,
    actions: Actions,
    dependencies: HashMap<String, DependencyConfig>,
}

#[derive(Deserialize, Serialize, Debug)]
struct DependencyConfig {
    version: String,
    path: Option<String>,
}

#[derive(Deserialize, Serialize, Debug)]
struct Actions {
    local: Vec<String>,
    remote: Vec<String>,
    modules: Vec<String>,
}

#[derive(Deserialize, Serialize, Debug)]
struct Scripts {
    build: String,
    post_build: String,
}

pub fn intro() {
    let args: Vec<String> = env::args().collect();
    dbg!(&args);

    let root_path = match args.get(1) {
        Some(root_path) => root_path,
        None => ".",
    };

    dbg!(&root_path);

    start(&root_path);
}

pub fn start(dir: &str) {
    let mut history = MyHistory::default();

    'main: loop {
        println!(
            "{} \n{}\n{}\n{}\n{}",
            style("Hit b to run build").yellow(),
            style("Hit a to load actions").yellow(),
            style("Hit s to run setup").yellow(),
            style("Hit > or r to run jsctl command").yellow(),
            style("Hit q or CTRL-C to quit").bold().red().italic(),
        );

        match console::Term::stdout().read_key() {
            Ok(key_event) => {
                match key_event {
                    Key::Char('b') => {
                        rebuild(&dir);
                    }
                    Key::Char('a') => {
                        reload_actions(&dir);
                    }
                    Key::Char('>') | Key::Char('r') => {
                        run_arbitrary_command(&dir, &mut history);
                    }
                    Key::Char('q') => {
                        break 'main;
                    }
                    _ => {
                        // Handle other key events
                    }
                }
            }
            Err(e) => {
                log::error!("{}", e);
            }
        }
    }
}

// Rebuild the jac file, and register it with the server by default
// Use the build script in the config file to determine what to do
pub fn rebuild(dir: &str) {
    println!("{}", style("Running build script...").bold().green());

    let config_path = PathBuf::from(&dir).join("jaseci.toml");
    let config = fs::read_to_string(&config_path).expect("Something went wrong reading the file");
    let jaseci_config: JaseciConfig = toml::from_str(&config).unwrap();

    let build_script = jaseci_config.scripts.build;
    let post_build_script = jaseci_config.scripts.post_build;

    let build_script_args = build_script
        .trim()
        .split_whitespace()
        .collect::<Vec<&str>>();

    let post_build_script_args = post_build_script
        .trim()
        .split_whitespace()
        .collect::<Vec<&str>>();

    let output = Command::new("jsctl")
        .current_dir(&dir)
        .args(build_script_args)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("Unable to rebuild jac file");

    let reader = BufReader::new(output.stdout.unwrap());
    let err_reader = BufReader::new(output.stderr.unwrap());
    for line in reader.lines() {
        info!("{}", line.unwrap());
    }

    for line in err_reader.lines() {
        error!("{}", line.unwrap());
    }

    println!("{}", style("Running post_build script...").bold().green());

    let output = Command::new("jsctl")
        .current_dir(&dir)
        .args(post_build_script_args)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("Unable to run post_build script...");

    let reader = BufReader::new(output.stdout.unwrap());
    let err_reader = BufReader::new(output.stderr.unwrap());
    for line in reader.lines() {
        info!("{}", line.unwrap());
    }

    for line in err_reader.lines() {
        error!("{}", line.unwrap());
    }
}

// Reload all actions from the config file
pub fn reload_actions(dir: &str) {
    // read the actions from the config file
    let config_path = PathBuf::from(&dir).join("jaseci.toml");
    let config = fs::read_to_string(config_path).expect("Something went wrong reading the file");

    let deserialized_data: JaseciConfig = toml::from_str(&config).unwrap();
    dbg!(&deserialized_data);

    let text_style = Style::new().green().bright();
    for dependency in deserialized_data.dependencies.keys() {
        let action_file_path = PathBuf::from(&dir)
            .join("packages")
            .join(dependency)
            .join("action.py")
            .into_os_string()
            .into_string()
            .unwrap();

        println!(
            "{}",
            text_style.apply_to(format!(
                "Loading installed action {} from {}",
                style(&dependency).bold().italic(),
                action_file_path
            ))
        );

        load_action(&action_file_path.to_string(), ActionType::Local, &dir);
    }

    // for each action, load it into the server
    for action in deserialized_data.actions.local {
        println!(
            "{}",
            text_style.apply_to(format!(
                "Loading local action {}",
                style(&action).bold().italic()
            ))
        );
        load_action(&action, ActionType::Local, &dir)
    }

    for action in deserialized_data.actions.modules {
        println!(
            "{}",
            text_style.apply_to(format!(
                "Loading action module {}",
                style(&action).bold().italic()
            ))
        );
        load_action(&action, ActionType::Module, &dir)
    }
    for action in deserialized_data.actions.remote {
        println!(
            "{}",
            text_style.apply_to(format!(
                "Loading remote action {}",
                style(&action).bold().italic()
            ))
        );
        load_action(&action, ActionType::Remote, &dir)
    }
}

// Load an action into the jaseci
pub enum ActionType {
    Local,
    Remote,
    Module,
}

pub fn load_action(action: &str, act_type: ActionType, dir: &str) {
    let args = match act_type {
        ActionType::Local => ["actions", "load", "local", &action],
        ActionType::Module => ["actions", "load", "module", &action],
        ActionType::Remote => ["actions", "load", "remote", &action],
    };

    let output = Command::new("jsctl")
        .current_dir(&dir)
        .args(args)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("Unable to load action");

    let reader = BufReader::new(output.stdout.unwrap());
    let err_reader = BufReader::new(output.stderr.unwrap());
    for line in reader.lines() {
        info!("{}", line.unwrap());
    }

    for line in err_reader.lines() {
        error!("{}", line.unwrap());
    }
}

fn run_arbitrary_command(dir: &str, history: &mut MyHistory) {
    loop {
        if let Ok(cmd) = Input::<String>::with_theme(&ColorfulTheme::default())
            .with_prompt("@jsctl")
            .history_with(history)
            .interact_text()
        {
            if cmd == "exit" || cmd == "quit" || cmd == "<" {
                break;
            }

            if cmd == "clear" || cmd == "cls" {
                println!("{}[2J", 27 as char);
                continue;
            }

            println!("Running {}", cmd);

            let output = Command::new("jsctl")
                .current_dir(&dir)
                .args(cmd.trim().split(" "))
                .stdout(Stdio::piped())
                .stderr(Stdio::piped())
                .spawn()
                .expect("Unable to load action");

            let reader = BufReader::new(output.stdout.unwrap());
            let err_reader = BufReader::new(output.stderr.unwrap());
            for line in reader.lines() {
                info!("{}", line.unwrap());
            }

            for line in err_reader.lines() {
                error!("{}", line.unwrap());
            }
        }
    }
}

struct MyHistory {
    max: usize,
    history: VecDeque<String>,
}

impl Default for MyHistory {
    fn default() -> Self {
        MyHistory {
            max: 4,
            history: VecDeque::new(),
        }
    }
}

impl<T: ToString> History<T> for MyHistory {
    fn read(&self, pos: usize) -> Option<String> {
        self.history.get(pos).cloned()
    }

    fn write(&mut self, val: &T) {
        if self.history.len() == self.max {
            self.history.pop_back();
        }
        self.history.push_front(val.to_string());
    }
}