diff --git a/Cargo.toml b/Cargo.toml index 73e43f6..fab5225 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" authors = ["Thomas Forgione "] [dependencies] +dirs = "1.0.4" +notify-rust = "3.4.2" colored = "1.6.1" hyper = "0.12.10" diff --git a/assets/failure.png b/assets/failure.png new file mode 100644 index 0000000..c581444 Binary files /dev/null and b/assets/failure.png differ diff --git a/assets/success.png b/assets/success.png new file mode 100644 index 0000000..f7d4a49 Binary files /dev/null and b/assets/success.png differ diff --git a/src/lib.rs b/src/lib.rs index 2e1b918..a575454 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,20 +1,30 @@ +extern crate dirs; +extern crate notify_rust; extern crate hyper; use std::io; use std::process::{Command, ExitStatus}; use std::path::PathBuf; -use std::ffi::OsStr; -use std::fs::create_dir_all; +use std::fs::{File, create_dir_all}; -macro_rules! destroy { - ($e: expr) => { - match $e { - Err(_) => { - let err: Result<(), ()> = Err(()); - return err; - }, - _ => (), - } +use notify_rust::Notification; + +/// The different types of error that can occur. +#[derive(Debug)] +pub enum Error { + /// No builder can build this project. + NoBuilderFound, + + /// An io::Error happened while running a build command. + IoError(io::Error), + + /// A command exited with non-zero exit code. + CommandError(String, Vec, ExitStatus), +} + +impl From for Error { + fn from(e: io::Error) -> Error { + Error::IoError(e) } } @@ -26,32 +36,31 @@ pub fn contains_file(path: &str, file: &str) -> bool { } /// Tries to build a certain directory using the specified builders. -pub fn run_command(command: &str, path: &str) -> Result { - let mut child = match Command::new(command) - .current_dir(path) - .spawn() { - Err(x) => return Err(x), - Ok(child) => child, - }; - - child.wait() +pub fn run_command(command: &str, path: &str) -> Result<(), Error> { + run_command_with_args(command, path, vec![]) } /// Run a build commands, and wait untils its finished, returning its result. -pub fn run_command_with_args(command: &str, path: &str, args: I) -> Result where - I: IntoIterator, - S: AsRef { +pub fn run_command_with_args(command: &str, path: &str, args: Vec<&str>) -> Result<(), Error> { + + let new_args: Vec = args.iter().map(|x| String::from(*x)).collect(); let mut child = Command::new(command) .current_dir(path) .args(args) .spawn()?; - child.wait() + let exit = child.wait()?; + + if exit.success() { + Ok(()) + } else { + Err(Error::CommandError(command.to_owned(), new_args, exit)) + } } /// Tries to build a certain directory using the specified builders. -pub fn build(path: &PathBuf, builders: &Vec>) -> Result<(), ()> { +pub fn build(path: &PathBuf, builders: &Vec>) -> Result<(), Error> { let mut path = path.clone(); @@ -59,7 +68,7 @@ pub fn build(path: &PathBuf, builders: &Vec>) -> Result<(), ()> { if path.to_str().unwrap() == "/" { // Couldn't find a buildable directory - return Err(()); + return Err(Error::NoBuilderFound); } for builder in builders { @@ -86,6 +95,12 @@ pub fn destroy(result: Result) -> Result<(), ()> { /// A general builders that contains many builders and can build any type of code. pub struct GeneralBuilder { + /// The path to the success icon. + success: PathBuf, + + /// The path to the failure icon. + failure: PathBuf, + /// The builders contained. builders: Vec>, } @@ -93,7 +108,41 @@ pub struct GeneralBuilder { impl GeneralBuilder { /// Creates a new general builder with the defaults builders. pub fn new() -> GeneralBuilder { + + let mut config = PathBuf::from(dirs::config_dir().unwrap()); + config.push("mars"); + + let mut success = config.clone(); + success.push("success.png"); + + let mut failure = config.clone(); + failure.push("failure.png"); + + if ! success.exists() || ! failure.exists() { + + create_dir_all(&config).unwrap(); + + const SUCCESS_BYTES: &[u8] = include_bytes!("../assets/success.png"); + const FAILURE_BYTES: &[u8] = include_bytes!("../assets/failure.png"); + + use std::io::Write; + + let mut path = config.clone(); + path.push("success.png"); + + let mut success_file = File::create(&success).unwrap(); + success_file.write_all(SUCCESS_BYTES).unwrap(); + + let mut path = config.clone(); + path.push("success.png"); + + let mut failure_file = File::create(&failure).unwrap(); + failure_file.write_all(FAILURE_BYTES).unwrap(); + } + GeneralBuilder { + success: success, + failure: failure, builders: vec![ Box::new(MakeBuilder::new()), Box::new(CMakeBuilder::new()), @@ -103,9 +152,57 @@ impl GeneralBuilder { } /// Triggers a build. - pub fn build(&self, path: &str) -> Result<(), ()> { - build(&PathBuf::from(path), &self.builders) + pub fn build(&self, path: &str) -> Result<(), Error> { + let result = build(&PathBuf::from(path), &self.builders); + + match result { + Ok(_) => self.notify_success(), + Err(ref e) => self.notify_error(&e), + } + + result } + + /// Sends a notification of a successful build. + pub fn notify_success(&self) { + + println!("{}", self.success.to_str().unwrap()); + + let _ = Notification::new() + .appname("Mars") + .summary("Success") + .body("Mars finished successfully") + .icon(self.success.to_str().unwrap()) + .show(); + + } + + /// Sends a notification of an error. + pub fn notify_error(&self, e: &Error) { + + let body = match e { + Error::NoBuilderFound => "No builder was found for this directory".to_string(), + Error::IoError(ref e) => format!("Error while running command: {:?}", e).to_string(), + Error::CommandError(ref command, ref args, ref status) => { + let status = match status.code() { + None => "None".to_owned(), + Some(e) => e.to_string(), + }; + + format!("Command \"{} {}\" failed: {}", + command, args.join(" "), status).to_string() + }, + }; + + let _ = Notification::new() + .appname("Mars") + .summary("Failure") + .body(&body) + .icon(self.failure.to_str().unwrap()) + .show(); + + } + } /// A generic builder. It can build some projects. @@ -115,7 +212,7 @@ pub trait Builder { fn is_buildable(&self, path: &str) -> bool; /// Trigger all the commands to build the project. - fn build(&self, path: &str) -> Result<(), ()>; + fn build(&self, path: &str) -> Result<(), Error>; } /// The builder that looks for makefiles. @@ -133,8 +230,8 @@ impl Builder for MakeBuilder { contains_file(path, "Makefile") } - fn build(&self, path: &str) -> Result<(), ()> { - destroy!(run_command("make", path)); + fn build(&self, path: &str) -> Result<(), Error> { + run_command("make", path)?; Ok(()) } } @@ -154,7 +251,7 @@ impl Builder for CMakeBuilder { contains_file(path, "CMakeLists.txt") } - fn build(&self, path: &str) -> Result<(), ()> { + fn build(&self, path: &str) -> Result<(), Error> { let mut build = PathBuf::from(path); build.push("build"); @@ -163,14 +260,14 @@ impl Builder for CMakeBuilder { if ! contains_file(path, "build/Makefile") { // Create build directory - destroy!(create_dir_all(&build)); + create_dir_all(&build)?; // Run cmake .. in build directory - destroy!(run_command_with_args("cmake", build.to_str().unwrap(), [".."].iter())); + run_command_with_args("cmake", build.to_str().unwrap(), vec![".."])?; } // Run make in build directory - destroy!(run_command("make", build.to_str().unwrap())); + run_command("make", build.to_str().unwrap())?; Ok(()) } @@ -191,7 +288,7 @@ impl Builder for CargoBuilder { contains_file(path, "Cargo.toml") } - fn build(&self, path: &str) -> Result<(), ()> { - destroy(run_command_with_args("cargo", path, ["build"].iter())) + fn build(&self, path: &str) -> Result<(), Error> { + run_command_with_args("cargo", path, vec!["build"]) } }