Lots of work.
This commit is contained in:
parent
1a7a61acbc
commit
c0f58eefe8
19
justfile
19
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
|
||||
|
11
notes
11
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
|
||||
|
154
src/lib.rs
154
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 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::<Vec<_>>();
|
||||
let mut ran = HashSet::new();
|
||||
for recipe in recipes {
|
||||
try!(self.run_recipe(recipe, &mut ran));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, name: &str) -> bool {
|
||||
self.recipes.contains_key(name)
|
||||
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<Justfile, Error> {
|
||||
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));
|
||||
}
|
||||
|
33
src/main.rs
33
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::<Vec<_>>().join(" "));
|
||||
warn!("{}", justfile.recipes().join(" "));
|
||||
}
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
if let Some(values) = matches.values_of("recipe") {
|
||||
let names = values.collect::<Vec<_>>();
|
||||
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::<Vec<_>>()
|
||||
} 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 });
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user