diff --git a/justfile b/justfile index 914e6288..2f821385 100644 --- a/justfile +++ b/justfile @@ -1,5 +1,6 @@ test: cargo test + cargo run -- quine # list all recipies list: @@ -8,16 +9,8 @@ list: args: @echo "I got some arguments: ARG0=${ARG0} ARG1=${ARG1} ARG2=${ARG2}" -# make a quine and compile it -quine: create compile - -# create our quine -create: - mkdir -p tmp - echo 'int printf(const char*, ...); int main() { char *s = "int printf(const char*, ...); int main() { char *s = %c%s%c; printf(s, 34, s, 34); return 0; }"; printf(s, 34, s, 34); return 0; }' > tmp/gen0.c - -# make sure it's really a quine -compile: +# make a quine, compile it, and verify it +quine: create cc tmp/gen0.c -o tmp/gen0 ./tmp/gen0 > tmp/gen1.c cc tmp/gen1.c -o tmp/gen1 @@ -25,6 +18,12 @@ compile: diff tmp/gen1.c tmp/gen2.c @echo 'It was a quine!' +# create our quine +create: + mkdir -p tmp + echo 'int printf(const char*, ...); int main() { char *s = "int printf(const char*, ...); int main() { char *s = %c%s%c; printf(s, 34, s, 34); return 0; }"; printf(s, 34, s, 34); return 0; }' > tmp/gen0.c + + # clean up clean: rm -r tmp diff --git a/notes b/notes index 25b43f53..34799379 100644 --- a/notes +++ b/notes @@ -1,6 +1,13 @@ 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 + - look through all justfiles for features of make that I use. so far: . phony . SHELL := zsh @@ -8,9 +15,11 @@ notes . make variables - ask travis for his justfiles +- comment code + command line arguments: - --show recipe: print recipe information -- --list if there's a bad recipe given +- --list recipes if a bad recipe given execution: - indent for line continuation diff --git a/src/lib.rs b/src/lib.rs index 56155294..adf2050c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,7 @@ fn re(pattern: &str) -> Regex { Regex::new(pattern).unwrap() } -pub struct Recipe<'a> { +struct Recipe<'a> { line: usize, name: &'a str, leading_whitespace: &'a str, @@ -49,6 +49,56 @@ pub struct Recipe<'a> { dependencies: BTreeSet<&'a str>, } +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); + } + // Err(RunError::Code{recipe: self.name, code: -1}) + Ok(()) + } +} + +fn resolve<'a>( + text: &'a str, + recipes: &BTreeMap<&str, Recipe<'a>>, + resolved: &mut HashSet<&'a str>, + seen: &mut HashSet<&'a str>, + stack: &mut Vec<&'a str>, + recipe: &Recipe<'a>, +) -> Result<(), Error<'a>> { + if resolved.contains(recipe.name) { + return Ok(()) + } + stack.push(recipe.name); + seen.insert(recipe.name); + for dependency_name in &recipe.dependencies { + match recipes.get(dependency_name) { + Some(dependency) => if !resolved.contains(dependency.name) { + if seen.contains(dependency.name) { + let first = stack[0]; + stack.push(first); + return Err(error(text, recipe.line, ErrorKind::CircularDependency { + circle: stack.iter() + .skip_while(|name| **name != dependency.name) + .cloned().collect() + })); + } + return resolve(text, recipes, resolved, seen, stack, dependency); + }, + None => return Err(error(text, recipe.line, ErrorKind::UnknownDependency { + name: recipe.name, + unknown: dependency_name + })), + } + } + resolved.insert(recipe.name); + stack.pop(); + Ok(()) +} + #[derive(Debug)] pub struct Error<'a> { text: &'a str, @@ -162,7 +212,7 @@ impl<'a> Display for Error<'a> { } pub struct Justfile<'a> { - pub recipes: BTreeMap<&'a str, Recipe<'a>>, + recipes: BTreeMap<&'a str, Recipe<'a>>, } impl<'a> Justfile<'a> { @@ -180,18 +230,67 @@ impl<'a> Justfile<'a> { first.map(|recipe| recipe.name) } - pub fn run(&self, recipes: &[&str]) { - if recipes.len() == 0 { - println!("running first recipe"); - } else { - for recipe in recipes { - println!("running {}...", recipe); - } - } + pub fn count(&self) -> usize { + self.recipes.len() } - pub fn contains(&self, name: &str) -> bool { - self.recipes.contains_key(name) + pub fn recipes(&self) -> Vec<&'a str> { + self.recipes.keys().cloned().collect() + } + + fn run_recipe(&self, recipe: &Recipe<'a>, ran: &mut HashSet<&'a str>) -> Result<(), RunError> { + for dependency_name in &recipe.dependencies { + if !ran.contains(dependency_name) { + try!(self.run_recipe(&self.recipes[dependency_name], ran)); + } + } + try!(recipe.run()); + ran.insert(recipe.name); + Ok(()) + } + + pub fn run<'b>(&'a self, names: &[&'b str]) -> Result<(), RunError<'b>> + where 'a: 'b + { + let mut missing = vec![]; + for recipe in names { + if !self.recipes.contains_key(recipe) { + missing.push(*recipe); + } + } + if missing.len() > 0 { + return Err(RunError::UnknownRecipes{recipes: missing}); + } + let recipes = names.iter().map(|name| self.recipes.get(name).unwrap()).collect::>(); + let mut ran = HashSet::new(); + for recipe in recipes { + try!(self.run_recipe(recipe, &mut ran)); + } + Ok(()) + } +} + +pub enum RunError<'a> { + UnknownRecipes{recipes: Vec<&'a str>}, + // Signal{recipe: &'a str, signal: i32}, + Code{recipe: &'a str, code: i32}, +} + +impl<'a> Display for RunError<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + &RunError::UnknownRecipes{ref recipes} => { + if recipes.len() == 1 { + try!(write!(f, "Justfile does not contain recipe: {}", recipes[0])); + } else { + try!(write!(f, "Justfile does not contain recipes: {}", recipes.join(" "))); + }; + }, + &RunError::Code{recipe, code} => { + try!(write!(f, "Recipe \"{}\" failed with code {}", recipe, code)); + }, + } + Ok(()) } } @@ -296,41 +395,6 @@ pub fn parse<'a>(text: &'a str) -> Result { let mut seen = HashSet::new(); let mut stack = vec![]; - fn resolve<'a>( - text: &'a str, - recipes: &BTreeMap<&str, Recipe<'a>>, - resolved: &mut HashSet<&'a str>, - seen: &mut HashSet<&'a str>, - stack: &mut Vec<&'a str>, - recipe: &Recipe<'a>, - ) -> Result<(), Error<'a>> { - stack.push(recipe.name); - seen.insert(recipe.name); - for dependency_name in &recipe.dependencies { - match recipes.get(dependency_name) { - Some(dependency) => if !resolved.contains(dependency.name) { - if seen.contains(dependency.name) { - let first = stack[0]; - stack.push(first); - return Err(error(text, recipe.line, ErrorKind::CircularDependency { - circle: stack.iter() - .skip_while(|name| **name != dependency.name) - .cloned().collect() - })); - } - return resolve(text, recipes, resolved, seen, stack, dependency); - }, - None => return Err(error(text, recipe.line, ErrorKind::UnknownDependency { - name: recipe.name, - unknown: dependency_name - })), - } - } - resolved.insert(recipe.name); - stack.pop(); - Ok(()) - } - for (_, ref recipe) in &recipes { try!(resolve(text, &recipes, &mut resolved, &mut seen, &mut stack, &recipe)); } diff --git a/src/main.rs b/src/main.rs index 662fa8aa..590fd169 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,38 +61,25 @@ fn main() { let justfile = j::parse(&text).unwrap_or_else(|error| die!("{}", error)); - if let Some(recipes) = matches.values_of("recipe") { - let mut missing = vec![]; - for recipe in recipes { - if !justfile.recipes.contains_key(recipe) { - missing.push(recipe); - } - } - if missing.len() > 0 { - die!("unknown recipe{}: {}", if missing.len() == 1 { "" } else { "s" }, missing.join(" ")); - } - } - if matches.is_present("list") { - if justfile.recipes.len() == 0 { + if justfile.count() == 0 { warn!("Justfile contains no recipes"); } else { - warn!("{}", justfile.recipes.keys().cloned().collect::>().join(" ")); + warn!("{}", justfile.recipes().join(" ")); } std::process::exit(0); } - if let Some(values) = matches.values_of("recipe") { - let names = values.collect::>(); - for name in names.iter() { - if !justfile.contains(name) { - die!("Justfile does not contain recipe \"{}\"", name); - } - } - justfile.run(&names) + let names = if let Some(names) = matches.values_of("recipe") { + names.collect::>() } else if let Some(name) = justfile.first() { - justfile.run(&[name]) + vec![name] } else { die!("Justfile contains no recipes"); + }; + + if let Err(run_error) = justfile.run(&names) { + warn!("{}", run_error); + std::process::exit(if let j::RunError::Code{code, ..} = run_error { code } else { -1 }); } }