diff --git a/src/lib.rs b/src/lib.rs index 7cf6430..3813561 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,17 @@ #[macro_use] extern crate log; -use std::{io, thread, fmt}; use std::env::current_dir; -use std::process::{Command, ExitStatus}; +use std::fs::{create_dir_all, File}; use std::path::{Path, PathBuf}; -use std::fs::{File, create_dir_all}; +use std::process::{Command, ExitStatus}; use std::sync::mpsc::channel; use std::time::Duration; +use std::{fmt, io, thread}; use colored::*; -use notify::{Watcher, RecursiveMode, watcher, DebouncedEvent}; +use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use notify_rust::{Notification, NotificationHandle}; @@ -39,7 +39,11 @@ impl fmt::Display for Error { match self { Error::NoBuilderFound => write!(fmt, "no builder found"), Error::IoError(e) => write!(fmt, "i/o error occured: {}", e), - Error::CommandError(command, _, status) => write!(fmt, "command {} exited with status code {}", command, status), + Error::CommandError(command, _, status) => write!( + fmt, + "command {} exited with status code {}", + command, status + ), } } } @@ -58,11 +62,7 @@ pub fn run_command(command: &str, path: &str) -> Result<(), Error> { /// Run a build commands, and wait untils its finished, returning its result. pub fn run_command_with_args(command: &str, path: &str, args: &Vec) -> Result<(), Error> { - - let mut child = Command::new(command) - .current_dir(path) - .args(args) - .spawn()?; + let mut child = Command::new(command).current_dir(path).args(args).spawn()?; let exit = child.wait()?; @@ -74,29 +74,28 @@ pub fn run_command_with_args(command: &str, path: &str, args: &Vec) -> R } /// Tries to build a certain directory using the specified builders. -pub fn build(path: &PathBuf, args: &Vec, builders: &Vec>) -> Result<(), Error> { - +pub fn build( + path: &PathBuf, + args: &Vec, + builders: &Vec>, +) -> Result<(), Error> { let mut path = path.clone(); loop { - if path.to_str().unwrap() == "/" { // Couldn't find a buildable directory return Err(Error::NoBuilderFound); } for builder in builders { - if builder.is_buildable(path.to_str().unwrap()) { builder.build(path.to_str().unwrap(), &args)?; return Ok(()); } - } path.pop(); - }; - + } } /// Destucture a Result to return a Result<(), ()> @@ -121,9 +120,13 @@ pub fn builder_arguments_from_string(uri: &str) -> (PathBuf, Vec) { } /// Watches a directory and builds it when a modification occurs. -pub fn watch>(p: P) -> Result<(), Error> { - let mut path = current_dir()?; - path.push(p.as_ref()); +pub fn watch>(p: P, ignore: Vec) -> Result<(), Error> { + let path = PathBuf::from(p.as_ref()).canonicalize()?; + + let ignore = ignore + .into_iter() + .map(|x| PathBuf::from(x).canonicalize()) + .collect::, _>>()?; thread::spawn(move || { let mut builder = GeneralBuilder::new(); @@ -134,7 +137,7 @@ pub fn watch>(p: P) -> Result<(), Error> { Err(e) => { warn!("couldn't watch directory {}: {}", path.display(), e); return; - }, + } }; if let Err(e) = watcher.watch(&path, RecursiveMode::Recursive) { @@ -146,11 +149,14 @@ pub fn watch>(p: P) -> Result<(), Error> { loop { match rx.recv() { - Ok(DebouncedEvent::NoticeWrite(_)) | - Ok(DebouncedEvent::Write(_)) | - Ok(DebouncedEvent::Create(_)) | - Ok(DebouncedEvent::Rename(_, _)) | - Ok(DebouncedEvent::Chmod(_)) => { + Ok(DebouncedEvent::NoticeWrite(x)) + | Ok(DebouncedEvent::Write(x)) + | Ok(DebouncedEvent::Create(x)) + | Ok(DebouncedEvent::Rename(_, x)) + | Ok(DebouncedEvent::Chmod(x)) => { + if ignore.iter().any(|y| x.starts_with(y)) { + continue; + } let start_string = format!("---- STARTING BUILD ---- from {}", path.display()); println!("{}", start_string.bold().green()); @@ -158,13 +164,13 @@ pub fn watch>(p: P) -> Result<(), Error> { match builder.build(&path, &vec![]) { Err(_) => { println!("{}", "--------- FAIL ---------".bold().red()); - }, + } Ok(_) => { println!("{}", "----- SUCCESSFUL -----".bold().green()); - }, + } }; println!(); - }, + } Err(e) => error!("watch error: {:?}", e), _ => (), } @@ -183,7 +189,7 @@ pub struct GeneralBuilder { failure: PathBuf, /// The builders contained. - builders: Vec>, + builders: Vec>, /// The id of the notification to update a notification if possible. notification_handle: Option, @@ -192,7 +198,6 @@ 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"); @@ -202,8 +207,7 @@ impl GeneralBuilder { let mut failure = config.clone(); failure.push("failure.png"); - if ! success.exists() || ! failure.exists() { - + if !success.exists() || !failure.exists() { create_dir_all(&config).unwrap(); const SUCCESS_BYTES: &[u8] = include_bytes!("../assets/success.png"); @@ -243,14 +247,13 @@ impl GeneralBuilder { 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(&mut self) { - match self.notification_handle.as_mut() { Some(handle) => { handle @@ -260,7 +263,7 @@ impl GeneralBuilder { .icon(self.success.to_str().unwrap()); handle.update(); - }, + } None => { let handle = Notification::new() @@ -271,17 +274,14 @@ impl GeneralBuilder { .show(); self.notification_handle = handle.ok(); - }, + } } - let _ = Notification::new() - .show(); - + let _ = Notification::new().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(), @@ -291,9 +291,14 @@ impl GeneralBuilder { Some(e) => e.to_string(), }; - format!("Command \"{} {}\" failed: {}", - command, args.join(" "), status).to_string() - }, + format!( + "Command \"{} {}\" failed: {}", + command, + args.join(" "), + status + ) + .to_string() + } }; let _ = Notification::new() @@ -302,9 +307,7 @@ impl GeneralBuilder { .body(&body) .icon(self.failure.to_str().unwrap()) .show(); - } - } /// A generic builder. It can build some projects. @@ -354,13 +357,11 @@ impl Builder for CMakeBuilder { } fn build(&self, path: &str, args: &Vec) -> Result<(), Error> { - let mut build = PathBuf::from(path); build.push("build"); // If there's a build/Makefile, we'll only make - if ! contains_file(path, "build/Makefile") { - + if !contains_file(path, "build/Makefile") { // Create build directory create_dir_all(&build)?; @@ -372,7 +373,6 @@ impl Builder for CMakeBuilder { run_command_with_args("make", build.to_str().unwrap(), args)?; Ok(()) } - } /// The builder that looks for Cargo.toml. diff --git a/src/server.rs b/src/server.rs index 9331bdd..5851676 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,28 +1,86 @@ #[macro_use] extern crate log; -use std::{thread, env}; -use std::sync::{Mutex, Arc, mpsc}; +use std::process::exit; +use std::sync::{mpsc, Arc, Mutex}; +use std::{env, thread}; use colored::*; use percent_encoding::percent_decode; -use hyper::{Body, Response, Server}; -use hyper::service::service_fn_ok; use hyper::rt::Future; +use hyper::service::service_fn_ok; +use hyper::{Body, Response, Server}; -use mars::{GeneralBuilder, builder_arguments_from_string, watch}; +use mars::{builder_arguments_from_string, watch, GeneralBuilder}; fn main() { - beautylog::init(log::LevelFilter::Info).ok(); - let args = env::args().collect::>(); + let mut args = env::args() + .skip(1) + .collect::>() + .into_iter() + .peekable(); - if args.len() == 3 && args[1] == "-w" { - if let Err(e) = watch(&args[2]) { + let mut watching = None; + let mut ignore = vec![]; + + let exit_code = loop { + let arg = args.next(); + let arg = arg.as_deref(); + + let next = args.peek(); + let next = next.as_deref(); + + let watch_is_some = watching.is_some(); + + match (arg, next, watch_is_some) { + (None, _, _) => { + break None; + } + + (Some("-w") | Some("--watch"), Some(value), false) => { + watching = Some(value.to_string()); + } + + (Some("-w") | Some("--watch"), Some(_), true) => { + error!("argument watch present multiple times"); + break Some(1); + } + + (Some("-w") | Some("--watch"), None, _) => { + error!("argument watch without value"); + break Some(1); + } + + (Some("-i") | Some("--ignore"), Some(value), _) => { + ignore.push(value.to_string()); + } + + (Some("-i") | Some("--ignore"), None, _) => { + error!("argument ignore without value"); + break Some(1); + } + + (Some(value), _, _) => { + error!("argument \"{}\" unknown", value); + break Some(1); + } + } + + args.next(); + }; + + if let Some(exit_code) = exit_code { + exit(exit_code); + } + + if let Some(watching) = watching { + if let Err(e) = watch(&watching, ignore) { error!("{}", e); + exit(1); } } @@ -32,7 +90,6 @@ fn main() { let clone = tasks.clone(); thread::spawn(move || { - let addr = ([127, 0, 0, 1], 1500).into(); let server = Server::bind(&addr) @@ -50,37 +107,37 @@ fn main() { info!("mars server listening on {}...", addr); hyper::rt::run(server); - }); let mut builder = GeneralBuilder::new(); for _ in rx { - let uri = { let mut tasks = tasks.lock().unwrap(); tasks.pop() }; if let Some(uri) = uri { - let uri = percent_decode(uri.path().as_bytes()).decode_utf8().unwrap(); let (path, args) = builder_arguments_from_string(&*uri); let path_string = path.to_str().unwrap(); - let start_string = format!("---- STARTING BUILD ---- from {} {}", path_string, args.join(" ")); + let start_string = format!( + "---- STARTING BUILD ---- from {} {}", + path_string, + args.join(" ") + ); println!("{}", start_string.bold().green()); match builder.build(&path, &args) { Err(_) => { println!("{}", "--------- FAIL ---------".bold().red()); - }, + } Ok(_) => { println!("{}", "----- SUCCESSFUL -----".bold().green()); - }, + } }; println!(); - } } }