Lots of work.
This commit is contained in:
parent
1a7a61acbc
commit
c0f58eefe8
19
justfile
19
justfile
@ -1,5 +1,6 @@
|
|||||||
test:
|
test:
|
||||||
cargo test
|
cargo test
|
||||||
|
cargo run -- quine
|
||||||
|
|
||||||
# list all recipies
|
# list all recipies
|
||||||
list:
|
list:
|
||||||
@ -8,16 +9,8 @@ list:
|
|||||||
args:
|
args:
|
||||||
@echo "I got some arguments: ARG0=${ARG0} ARG1=${ARG1} ARG2=${ARG2}"
|
@echo "I got some arguments: ARG0=${ARG0} ARG1=${ARG1} ARG2=${ARG2}"
|
||||||
|
|
||||||
# make a quine and compile it
|
# make a quine, compile it, and verify it
|
||||||
quine: create compile
|
quine: create
|
||||||
|
|
||||||
# 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:
|
|
||||||
cc tmp/gen0.c -o tmp/gen0
|
cc tmp/gen0.c -o tmp/gen0
|
||||||
./tmp/gen0 > tmp/gen1.c
|
./tmp/gen0 > tmp/gen1.c
|
||||||
cc tmp/gen1.c -o tmp/gen1
|
cc tmp/gen1.c -o tmp/gen1
|
||||||
@ -25,6 +18,12 @@ compile:
|
|||||||
diff tmp/gen1.c tmp/gen2.c
|
diff tmp/gen1.c tmp/gen2.c
|
||||||
@echo 'It was a quine!'
|
@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 up
|
||||||
clean:
|
clean:
|
||||||
rm -r tmp
|
rm -r tmp
|
||||||
|
11
notes
11
notes
@ -1,6 +1,13 @@
|
|||||||
notes
|
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:
|
- look through all justfiles for features of make that I use. so far:
|
||||||
. phony
|
. phony
|
||||||
. SHELL := zsh
|
. SHELL := zsh
|
||||||
@ -8,9 +15,11 @@ notes
|
|||||||
. make variables
|
. make variables
|
||||||
- ask travis for his justfiles
|
- ask travis for his justfiles
|
||||||
|
|
||||||
|
- comment code
|
||||||
|
|
||||||
command line arguments:
|
command line arguments:
|
||||||
- --show recipe: print recipe information
|
- --show recipe: print recipe information
|
||||||
- --list if there's a bad recipe given
|
- --list recipes if a bad recipe given
|
||||||
|
|
||||||
execution:
|
execution:
|
||||||
- indent for line continuation
|
- 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()
|
Regex::new(pattern).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Recipe<'a> {
|
struct Recipe<'a> {
|
||||||
line: usize,
|
line: usize,
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
leading_whitespace: &'a str,
|
leading_whitespace: &'a str,
|
||||||
@ -49,6 +49,56 @@ pub struct Recipe<'a> {
|
|||||||
dependencies: BTreeSet<&'a str>,
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct Error<'a> {
|
pub struct Error<'a> {
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
@ -162,7 +212,7 @@ impl<'a> Display for Error<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Justfile<'a> {
|
pub struct Justfile<'a> {
|
||||||
pub recipes: BTreeMap<&'a str, Recipe<'a>>,
|
recipes: BTreeMap<&'a str, Recipe<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Justfile<'a> {
|
impl<'a> Justfile<'a> {
|
||||||
@ -180,18 +230,67 @@ impl<'a> Justfile<'a> {
|
|||||||
first.map(|recipe| recipe.name)
|
first.map(|recipe| recipe.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self, recipes: &[&str]) {
|
pub fn count(&self) -> usize {
|
||||||
if recipes.len() == 0 {
|
self.recipes.len()
|
||||||
println!("running first recipe");
|
|
||||||
} else {
|
|
||||||
for recipe in recipes {
|
|
||||||
println!("running {}...", recipe);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
pub enum RunError<'a> {
|
||||||
self.recipes.contains_key(name)
|
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 seen = HashSet::new();
|
||||||
let mut stack = vec![];
|
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 {
|
for (_, ref recipe) in &recipes {
|
||||||
try!(resolve(text, &recipes, &mut resolved, &mut seen, &mut stack, &recipe));
|
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));
|
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 matches.is_present("list") {
|
||||||
if justfile.recipes.len() == 0 {
|
if justfile.count() == 0 {
|
||||||
warn!("Justfile contains no recipes");
|
warn!("Justfile contains no recipes");
|
||||||
} else {
|
} else {
|
||||||
warn!("{}", justfile.recipes.keys().cloned().collect::<Vec<_>>().join(" "));
|
warn!("{}", justfile.recipes().join(" "));
|
||||||
}
|
}
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(values) = matches.values_of("recipe") {
|
let names = if let Some(names) = matches.values_of("recipe") {
|
||||||
let names = values.collect::<Vec<_>>();
|
names.collect::<Vec<_>>()
|
||||||
for name in names.iter() {
|
|
||||||
if !justfile.contains(name) {
|
|
||||||
die!("Justfile does not contain recipe \"{}\"", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
justfile.run(&names)
|
|
||||||
} else if let Some(name) = justfile.first() {
|
} else if let Some(name) = justfile.first() {
|
||||||
justfile.run(&[name])
|
vec![name]
|
||||||
} else {
|
} else {
|
||||||
die!("Justfile contains no recipes");
|
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