diff --git a/Cargo.lock b/Cargo.lock index b76d1f3..b037b57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,7 +102,7 @@ dependencies = [ [[package]] name = "siren" -version = "1.0.3" +version = "1.1.0" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.19.3 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 892c929..cfbfffb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "siren" -version = "1.0.3" +version = "1.1.0" authors = ["Alessio Biancalana "] description = "Your friendly neighborhood monitoring CLI tool." homepage = "https://github.com/dottorblaster/siren" diff --git a/src/execute.rs b/src/execute.rs index 973a964..436a61c 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -1,6 +1,10 @@ extern crate ansi_term; +extern crate serde_json; use parse_config::Task; +use task_output::SerializableOutput; +use task_output; +use std::sync::{Mutex, Arc}; use std::thread; use std::process::Command; use std::process::Output; @@ -8,47 +12,66 @@ use std::process::Output; use self::ansi_term::Colour::{Red, Green, Yellow, Black}; use self::ansi_term::ANSIString; -fn task_success(task: Task, output: Output) { - let stdout = ANSIString::from(String::from_utf8(output.stdout).unwrap()); - println!( - "{} {}\n{}\n", - Black.bold().on(Green).paint(" SUCCESS "), - Yellow.paint(format!("{}", task.name)), - stdout - ); +fn task_success(task: Task, output: Output, json: bool) { + if json == false { + let stdout = ANSIString::from(String::from_utf8(output.stdout).unwrap()); + println!( + "{} {}\n{}\n", + Black.bold().on(Green).paint(" SUCCESS "), + Yellow.paint(format!("{}", task.name)), + stdout + ); + } } -fn task_failure(task: Task, output: Output) { - let stderr = ANSIString::from(String::from_utf8(output.stderr).unwrap()); - println!( - "{} {}\n{}\n", - Black.bold().on(Red).paint(" FAIL "), - Yellow.paint(format!("{}", task.name)), - stderr - ); +fn task_failure(task: Task, output: Output, json: bool) { + if json == false { + let stderr = ANSIString::from(String::from_utf8(output.stderr).unwrap()); + println!( + "{} {}\n{}\n", + Black.bold().on(Red).paint(" FAIL "), + Yellow.paint(format!("{}", task.name)), + stderr + ); + } +} + +fn print_json(outputs: Arc>>, json: bool) { + if json == true { + let slice = &*outputs.lock().unwrap(); + let serializable_output = SerializableOutput { tasks: slice.to_vec() }; + println!("{}", serde_json::to_string(&serializable_output).unwrap()); + } } -pub fn run(tasks: Vec, cwd_path: String) -> bool { +pub fn run(tasks: Vec, cwd_path: String, json_output: bool) -> bool { + let outputs = Arc::new(Mutex::new(task_output::Tasks::with_capacity(tasks.len()))); let mut handles = Vec::with_capacity(tasks.len()); println!("\n"); for task in &tasks { let (data, path) = (task.clone(), cwd_path.clone()); + let outputs = Arc::clone(&outputs); let child = thread::spawn(move || { let local_task = data.clone(); + let task_data = data.clone(); let mut iter = local_task.command.split_whitespace(); - let output = Command::new(iter.nth(0).unwrap()) + let mut list = outputs.lock().unwrap(); + let command_output = Command::new(iter.nth(0).unwrap()) .args(iter) .current_dir(path) .output() .expect("command failed"); - match output.status.code() { - Some(0) => task_success(data, output), - Some(_) => task_failure(data, output), + let cloned_output = command_output.clone(); + list.push(task_output::build_task_output(cloned_output, task_data)); + match command_output.status.code() { + Some(0) => task_success(data, command_output, json_output), + Some(_) => task_failure(data, command_output, json_output), None => println!("Process terminated by signal") } }); handles.push(child); } for handle in handles { handle.join().unwrap(); } + print_json(outputs, json_output); true } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index dafb598..3961af0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ use clap::{Arg, App}; mod parse_config; mod execute; +mod task_output; fn parentpath(path: String) -> String { let mut v: Vec<&str> = path.split("/").collect(); @@ -30,7 +31,7 @@ fn read_sirenfile(sirenfile_path: String) -> Result { fn main() { let matches = App::new("Siren") - .version("1.0.3") + .version("1.1.0") .author("Alessio Biancalana ") .about("Your tiny friendly rusty neighborhood monitoring CLI tool") .arg(Arg::with_name("file") @@ -39,9 +40,16 @@ fn main() { .value_name("FILE") .help("Sets a custom Sirenfile") .takes_value(true)) + .arg(Arg::with_name("json-output") + .short("j") + .long("json-output") + .value_name("JSON") + .help("Enable JSON output") + .takes_value(false)) .get_matches(); let sirenfile_path = matches.value_of("file").unwrap_or("./Sirenfile.json").to_owned(); + let output_json = matches.is_present("json-output"); let configstring = match read_sirenfile(sirenfile_path) { Ok(jsoncontent) => jsoncontent, @@ -55,5 +63,5 @@ fn main() { true => parentpath(matches.value_of("file").unwrap_or("./Sirenfile.json").to_owned()), false => String::from(".") }; - execute::run(conf.tasks, cwd_path); + execute::run(conf.tasks, cwd_path, output_json); } diff --git a/src/task_output.rs b/src/task_output.rs new file mode 100644 index 0000000..ee17976 --- /dev/null +++ b/src/task_output.rs @@ -0,0 +1,31 @@ +extern crate serde; +extern crate serde_json; + +use parse_config::Task; +use std::process::Output; + +#[derive(Serialize, Clone)] +pub struct TaskOutput { + pub outcome: String, + pub code: String, + pub name: String, + pub description: String, + pub command: String, +} + +pub type Tasks = Vec; + +#[derive(Serialize, Clone)] +pub struct SerializableOutput { + pub tasks: Vec, +} + +pub fn build_task_output(output: Output, task: Task) -> TaskOutput { + TaskOutput { + outcome: String::from_utf8(output.stdout).unwrap(), + code: output.status.code().unwrap().to_string(), + name: task.name, + description: task.description, + command: task.command, + } +}