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;
}
_ => {
}
}
}
Err(e) => {
log::error!("{}", e);
}
}
}
}
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());
}
}
pub fn reload_actions(dir: &str) {
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 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)
}
}
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());
}
}