diff --git a/Cargo.lock b/Cargo.lock index 97fe81a..4727635 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,6 +4,7 @@ version = "0.1.5" dependencies = [ "clap 2.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)", + "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -61,6 +62,14 @@ dependencies = [ "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "regex" version = "0.1.77" @@ -83,6 +92,14 @@ name = "strsim" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "tempdir" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "term_size" version = "0.2.1" @@ -148,9 +165,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "408014cace30ee0f767b1c4517980646a573ec61a57957aeeabcac8ac0a02e8d" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" +"checksum rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5" "checksum regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)" = "64b03446c466d35b42f2a8b203c8e03ed8b91c0f17b56e1f84f7210a257aa665" "checksum regex-syntax 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279401017ae31cf4e15344aa3f085d0e2e5c1e70067289ef906906fdbe92c8fd" "checksum strsim 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "50c069df92e4b01425a8bf3576d5d417943a6a7272fbabaf5bd80b1aaa76442e" +"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" "checksum term_size 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f7f5f3f71b0040cecc71af239414c23fd3c73570f5ff54cf50e03cef637f2a0" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" diff --git a/Cargo.toml b/Cargo.toml index a6abcd5..1d08ba8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ homepage = "https://github.com/casey/j" regex = "^0.1.77" clap = "^2.0.0" +tempdir = "^0.3.5" diff --git a/justfile b/justfile index b01bbda..bfb2985 100644 --- a/justfile +++ b/justfile @@ -1,6 +1,6 @@ test: cargo test --lib - cargo run -- quine + cargo run -- quine clean > /dev/null 2> /dev/null # list all recipies list: diff --git a/notes b/notes index 3479937..5bfc695 100644 --- a/notes +++ b/notes @@ -2,12 +2,11 @@ notes ----- -- report double compile error --- actually run recipes --- actually parse recipe and validate contents --- think about maybe using multiple cores -- should pre-requisite order really be arbitrary? -- test plan order +-- use multiple cores + - look through all justfiles for features of make that I use. so far: . phony . SHELL := zsh diff --git a/src/lib.rs b/src/lib.rs index adf2050..8f17aae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ extern crate regex; use std::io::prelude::*; -use std::{fs, fmt}; +use std::{fs, fmt, process, io}; use std::collections::{BTreeMap, BTreeSet, HashSet}; use std::fmt::Display; use regex::Regex; @@ -49,14 +49,46 @@ struct Recipe<'a> { dependencies: BTreeSet<&'a str>, } +#[cfg(unix)] +fn error_from_signal<'a>(recipe: &'a str, exit_status: process::ExitStatus) -> RunError<'a> { + use std::os::unix::process::ExitStatusExt; + match exit_status.signal() { + Some(signal) => RunError::Signal{recipe: recipe, signal: signal}, + None => RunError::UnknownFailure{recipe: recipe}, + } +} + +#[cfg(windows)] +fn error_from_signal<'a>(recipe: &'a str, exit_status: process::ExitStatus) -> RunError<'a> { + RunError::UnknownFailure{recipe: recipe} +} + impl<'a> Recipe<'a> { fn run(&self) -> Result<(), RunError<'a>> { - // TODO: actually run recipes - warn!("running {}", self.name); for command in &self.commands { - warn!("{}", command); + let mut command = *command; + if !command.starts_with("@") { + warn!("{}", command); + } else { + command = &command[1..]; + } + let status = process::Command::new("sh") + .arg("-c") + .arg(command) + .status(); + try!(match status { + Ok(exit_status) => if let Some(code) = exit_status.code() { + if code == 0 { + Ok(()) + } else { + Err(RunError::Code{recipe: self.name, code: code}) + } + } else { + Err(error_from_signal(self.name, exit_status)) + }, + Err(io_error) => Err(RunError::IoError{recipe: self.name, io_error: io_error}) + }); } - // Err(RunError::Code{recipe: self.name, code: -1}) Ok(()) } } @@ -270,10 +302,13 @@ impl<'a> Justfile<'a> { } } +#[derive(Debug)] pub enum RunError<'a> { UnknownRecipes{recipes: Vec<&'a str>}, - // Signal{recipe: &'a str, signal: i32}, + Signal{recipe: &'a str, signal: i32}, Code{recipe: &'a str, code: i32}, + UnknownFailure{recipe: &'a str}, + IoError{recipe: &'a str, io_error: io::Error}, } impl<'a> Display for RunError<'a> { @@ -289,6 +324,19 @@ impl<'a> Display for RunError<'a> { &RunError::Code{recipe, code} => { try!(write!(f, "Recipe \"{}\" failed with code {}", recipe, code)); }, + &RunError::Signal{recipe, signal} => { + try!(write!(f, "Recipe \"{}\" wast terminated by signal {}", recipe, signal)); + } + &RunError::UnknownFailure{recipe} => { + try!(write!(f, "Recipe \"{}\" failed for an unknown reason", recipe)); + }, + &RunError::IoError{recipe, ref io_error} => { + try!(match io_error.kind() { + io::ErrorKind::NotFound => write!(f, "Recipe \"{}\" could not be run because j could not find `sh` the command:\n{}", recipe, io_error), + io::ErrorKind::PermissionDenied => write!(f, "Recipe \"{}\" could not be run because j could not run `sh`:\n{}", recipe, io_error), + _ => write!(f, "Recipe \"{}\" could not be run because of an IO error while launching the `sh`:\n{}", recipe, io_error), + }); + }, } Ok(()) } diff --git a/src/tests.rs b/src/tests.rs index 1ef2d85..fd3602f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,3 +1,5 @@ +extern crate tempdir; + use super::{ErrorKind, Justfile}; fn expect_error(text: &str, line: usize, expected_error_kind: ErrorKind) { @@ -130,3 +132,43 @@ fn first() { let justfile = expect_success("#hello\n#goodbye\na:\nb:\nc:\n"); assert!(justfile.first().unwrap() == "a"); } + +#[test] +fn unknown_recipes() { + match expect_success("a:\nb:\nc:").run(&["a", "x", "y", "z"]).unwrap_err() { + super::RunError::UnknownRecipes{recipes} => assert_eq!(recipes, &["x", "y", "z"]), + other @ _ => panic!("expected an unknown recipe error, but got: {}", other), + } +} + +#[test] +fn code_error() { + match expect_success("fail:\n @function x { return 100; }; x").run(&["fail"]).unwrap_err() { + super::RunError::Code{recipe, code} => { + assert_eq!(recipe, "fail"); + assert_eq!(code, 100); + }, + other @ _ => panic!("expected an code run error, but got: {}", other), + } +} + +#[test] +fn run_order() { + let tmp = tempdir::TempDir::new("run_order").unwrap_or_else(|err| panic!("tmpdir: failed to create temporary directory: {}", err)); + let path = tmp.path().to_str().unwrap_or_else(|| panic!("tmpdir: path was not valid UTF-8")).to_owned(); + let text = r" +a: + @touch a + +b: a + @mv a b + +c: b + @mv b c + +d: c + @rm c +"; + super::std::env::set_current_dir(path).expect("failed to set current directory"); + expect_success(text).run(&["a", "d"]).unwrap(); +}