Arguments working but still ugly
This commit is contained in:
parent
b956ce2397
commit
ac5433248e
5
justfile
5
justfile
@ -1,12 +1,9 @@
|
|||||||
test:
|
test: build
|
||||||
cargo test --lib
|
cargo test --lib
|
||||||
|
|
||||||
test-quine:
|
test-quine:
|
||||||
cargo run -- quine clean
|
cargo run -- quine clean
|
||||||
|
|
||||||
test-integ:
|
|
||||||
cargo run -- --justfile integration-tests/justfile --working-directory integration-tests
|
|
||||||
|
|
||||||
backtrace:
|
backtrace:
|
||||||
RUST_BACKTRACE=1 cargo test --lib
|
RUST_BACKTRACE=1 cargo test --lib
|
||||||
|
|
||||||
|
28
notes
28
notes
@ -1,19 +1,18 @@
|
|||||||
notes
|
notes
|
||||||
-----
|
-----
|
||||||
|
|
||||||
- figure out argument passing:
|
- arguments:
|
||||||
. flag: j build --set a=hello
|
. change evaluate_expression to evaluate_lines
|
||||||
. by export: A=HELLO j build
|
. fast errors when arguments are not passed
|
||||||
. by export 2: BUILD.A=HELLO j build
|
. don't assume that argument count is correct
|
||||||
. by name: j build a=hello
|
. don't unwrap errors in evaluate line
|
||||||
. by position: j build hello
|
|
||||||
. with marker: j build hello : clean hello :
|
|
||||||
. after -- : j build -- foo baz
|
|
||||||
. fast errors when arguments are missing
|
|
||||||
. could also allow this to override variables
|
|
||||||
although maybe only after a '--': j build -- a=hello
|
|
||||||
. sub arguments into recipes
|
. sub arguments into recipes
|
||||||
|
|
||||||
|
- save result of commands in variables: `hello`
|
||||||
|
|
||||||
|
- set variables from the command line: j --set build linux
|
||||||
|
|
||||||
|
|
||||||
- before release:
|
- before release:
|
||||||
|
|
||||||
- rewrite grammar.txt
|
- rewrite grammar.txt
|
||||||
@ -56,15 +55,12 @@ notes
|
|||||||
enhancements:
|
enhancements:
|
||||||
|
|
||||||
- colored error messages
|
- colored error messages
|
||||||
- save result of commands in variables
|
|
||||||
- multi line strings (maybe not in recipe interpolations)
|
- multi line strings (maybe not in recipe interpolations)
|
||||||
- raw strings
|
- raw strings with ''
|
||||||
- iteration: {{x for x in y}}
|
- iteration: {{x for x in y}}
|
||||||
- allow calling recipes in a justfile in a different directory:
|
- allow calling recipes in a justfile in a different directory:
|
||||||
. just ../foo # ../justfile:foo
|
. just ../foo # ../justfile:foo
|
||||||
. just xyz/foo # xyz/justfile:foo
|
. just xyz/foo # xyz/justfile:foo
|
||||||
. just xyz/ # xyz/justfile:DEFAULT
|
. just xyz/ # xyz/justfile:DEFAULT
|
||||||
- allow setting and exporting environment variables
|
- allow setting and exporting environment variables
|
||||||
- indentation or slash for line continuation
|
- indentation or slash for line continuation in plain recipes
|
||||||
- figure out some way to allow changing directories in
|
|
||||||
plain recipes
|
|
||||||
|
12
src/app.rs
12
src/app.rs
@ -45,7 +45,7 @@ pub fn app() {
|
|||||||
.long("justfile")
|
.long("justfile")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Use <justfile> as justfile. --working-directory must also be set"))
|
.help("Use <justfile> as justfile. --working-directory must also be set"))
|
||||||
.arg(Arg::with_name("recipe")
|
.arg(Arg::with_name("arguments")
|
||||||
.multiple(true)
|
.multiple(true)
|
||||||
.help("recipe(s) to run, defaults to the first recipe in the justfile"))
|
.help("recipe(s) to run, defaults to the first recipe in the justfile"))
|
||||||
.get_matches();
|
.get_matches();
|
||||||
@ -123,15 +123,15 @@ pub fn app() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let names = if let Some(names) = matches.values_of("recipe") {
|
let arguments = if let Some(arguments) = matches.values_of("arguments") {
|
||||||
names.collect::<Vec<_>>()
|
arguments.collect::<Vec<_>>()
|
||||||
} else if let Some(name) = justfile.first() {
|
} else if let Some(recipe) = justfile.first() {
|
||||||
vec![name]
|
vec![recipe]
|
||||||
} else {
|
} else {
|
||||||
die!("Justfile contains no recipes");
|
die!("Justfile contains no recipes");
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(run_error) = justfile.run(&names) {
|
if let Err(run_error) = justfile.run(&arguments) {
|
||||||
warn!("{}", run_error);
|
warn!("{}", run_error);
|
||||||
process::exit(if let super::RunError::Code{code, ..} = run_error { code } else { -1 });
|
process::exit(if let super::RunError::Code{code, ..} = run_error { code } else { -1 });
|
||||||
}
|
}
|
||||||
|
230
src/lib.rs
230
src/lib.rs
@ -134,7 +134,34 @@ fn error_from_signal(recipe: &str, exit_status: process::ExitStatus) -> RunError
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Recipe<'a> {
|
impl<'a> Recipe<'a> {
|
||||||
fn run(&self) -> Result<(), RunError<'a>> {
|
fn run(&self, arguments: &[&'a str], scope: &BTreeMap<&'a str, String>) -> Result<(), RunError<'a>> {
|
||||||
|
let mut evaluated_lines = vec![];
|
||||||
|
for fragments in &self.lines {
|
||||||
|
let mut line = String::new();
|
||||||
|
for fragment in fragments.iter() {
|
||||||
|
match *fragment {
|
||||||
|
Fragment::Text{ref text} => line += text.lexeme,
|
||||||
|
Fragment::Expression{value: Some(ref value), ..} => {
|
||||||
|
line += &value;
|
||||||
|
}
|
||||||
|
Fragment::Expression{ref expression, value: None} => {
|
||||||
|
let mut arg_map = BTreeMap::new();
|
||||||
|
for (i, argument) in arguments.iter().enumerate() {
|
||||||
|
arg_map.insert(*self.arguments.get(i).unwrap(), *argument);
|
||||||
|
}
|
||||||
|
line += &evaluate_expression(
|
||||||
|
expression,
|
||||||
|
&scope,
|
||||||
|
&BTreeMap::new(),
|
||||||
|
&BTreeMap::new(),
|
||||||
|
&arg_map,
|
||||||
|
).unwrap().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
evaluated_lines.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
if self.shebang {
|
if self.shebang {
|
||||||
let tmp = try!(
|
let tmp = try!(
|
||||||
tempdir::TempDir::new("j")
|
tempdir::TempDir::new("j")
|
||||||
@ -149,7 +176,7 @@ impl<'a> Recipe<'a> {
|
|||||||
);
|
);
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
// add the shebang
|
// add the shebang
|
||||||
text += &self.evaluated_lines[0];
|
text += &evaluated_lines[0];
|
||||||
text += "\n";
|
text += "\n";
|
||||||
// add blank lines so that lines in the generated script
|
// add blank lines so that lines in the generated script
|
||||||
// have the same line number as the corresponding lines
|
// have the same line number as the corresponding lines
|
||||||
@ -157,7 +184,7 @@ impl<'a> Recipe<'a> {
|
|||||||
for _ in 1..(self.line_number + 2) {
|
for _ in 1..(self.line_number + 2) {
|
||||||
text += "\n"
|
text += "\n"
|
||||||
}
|
}
|
||||||
for line in &self.evaluated_lines[1..] {
|
for line in &evaluated_lines[1..] {
|
||||||
text += line;
|
text += line;
|
||||||
text += "\n";
|
text += "\n";
|
||||||
}
|
}
|
||||||
@ -193,7 +220,7 @@ impl<'a> Recipe<'a> {
|
|||||||
Err(io_error) => Err(RunError::TmpdirIoError{recipe: self.name, io_error: io_error})
|
Err(io_error) => Err(RunError::TmpdirIoError{recipe: self.name, io_error: io_error})
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
for command in &self.evaluated_lines {
|
for command in &evaluated_lines {
|
||||||
let mut command = &command[0..];
|
let mut command = &command[0..];
|
||||||
if !command.starts_with('@') {
|
if !command.starts_with('@') {
|
||||||
warn!("{}", command);
|
warn!("{}", command);
|
||||||
@ -323,41 +350,60 @@ fn evaluate<'a>(
|
|||||||
assignment_tokens: &BTreeMap<&'a str, Token<'a>>,
|
assignment_tokens: &BTreeMap<&'a str, Token<'a>>,
|
||||||
recipes: &mut BTreeMap<&'a str, Recipe<'a>>,
|
recipes: &mut BTreeMap<&'a str, Recipe<'a>>,
|
||||||
) -> Result<BTreeMap<&'a str, String>, Error<'a>> {
|
) -> Result<BTreeMap<&'a str, String>, Error<'a>> {
|
||||||
let mut evaluator = Evaluator{
|
let mut evaluated = BTreeMap::new();
|
||||||
seen: HashSet::new(),
|
|
||||||
stack: vec![],
|
|
||||||
evaluated: BTreeMap::new(),
|
|
||||||
assignments: assignments,
|
|
||||||
assignment_tokens: assignment_tokens,
|
|
||||||
};
|
|
||||||
for name in assignments.keys() {
|
|
||||||
try!(evaluator.evaluate_assignment(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
for recipe in recipes.values_mut() {
|
{
|
||||||
for fragments in &mut recipe.lines {
|
let mut evaluator = Evaluator{
|
||||||
let mut line = String::new();
|
seen: HashSet::new(),
|
||||||
for mut fragment in fragments.iter_mut() {
|
stack: vec![],
|
||||||
match *fragment {
|
evaluated: &mut evaluated,
|
||||||
Fragment::Text{ref text} => line += text.lexeme,
|
scope: &BTreeMap::new(),
|
||||||
Fragment::Expression{ref expression, ref mut value} => {
|
assignments: assignments,
|
||||||
let evaluated = &try!(evaluator.evaluate_expression(&expression));
|
assignment_tokens: assignment_tokens,
|
||||||
*value = Some(evaluated.clone());
|
};
|
||||||
line += evaluated;
|
for name in assignments.keys() {
|
||||||
}
|
try!(evaluator.evaluate_assignment(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for recipe in recipes.values_mut() {
|
||||||
|
let mut arguments = BTreeMap::new();
|
||||||
|
for argument in &recipe.arguments {
|
||||||
|
arguments.insert(*argument, None);
|
||||||
}
|
}
|
||||||
recipe.evaluated_lines.push(line);
|
try!(evaluator.evaluate_recipe(recipe, &arguments));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(evaluator.evaluated)
|
Ok(evaluated)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate_expression<'a: 'b, 'b> (
|
||||||
|
expression: &Expression<'a>,
|
||||||
|
scope: &BTreeMap<&'a str, String>,
|
||||||
|
assignments: &'b BTreeMap<&'a str, Expression<'a>>,
|
||||||
|
assignment_tokens: &'b BTreeMap<&'a str, Token<'a>>,
|
||||||
|
arguments: &BTreeMap<&str, &str>,
|
||||||
|
) -> Result<Option<String>, Error<'a>> {
|
||||||
|
let mut evaluator = Evaluator{
|
||||||
|
seen: HashSet::new(),
|
||||||
|
stack: vec![],
|
||||||
|
evaluated: &mut BTreeMap::new(),
|
||||||
|
scope: scope,
|
||||||
|
assignments: assignments,
|
||||||
|
assignment_tokens: assignment_tokens,
|
||||||
|
};
|
||||||
|
let mut argument_options = BTreeMap::new();
|
||||||
|
for (name, value) in arguments.iter() {
|
||||||
|
argument_options.insert(*name, Some(*value));
|
||||||
|
}
|
||||||
|
evaluator.evaluate_expression(expression, &argument_options)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Evaluator<'a: 'b, 'b> {
|
struct Evaluator<'a: 'b, 'b> {
|
||||||
stack: Vec<&'a str>,
|
stack: Vec<&'a str>,
|
||||||
seen: HashSet<&'a str>,
|
seen: HashSet<&'a str>,
|
||||||
evaluated: BTreeMap<&'a str, String>,
|
evaluated: &'b mut BTreeMap<&'a str, String>,
|
||||||
|
scope: &'b BTreeMap<&'a str, String>,
|
||||||
assignments: &'b BTreeMap<&'a str, Expression<'a>>,
|
assignments: &'b BTreeMap<&'a str, Expression<'a>>,
|
||||||
assignment_tokens: &'b BTreeMap<&'a str, Token<'a>>,
|
assignment_tokens: &'b BTreeMap<&'a str, Token<'a>>,
|
||||||
}
|
}
|
||||||
@ -372,7 +418,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
|||||||
self.seen.insert(name);
|
self.seen.insert(name);
|
||||||
|
|
||||||
if let Some(expression) = self.assignments.get(name) {
|
if let Some(expression) = self.assignments.get(name) {
|
||||||
let value = try!(self.evaluate_expression(expression));
|
let value = try!(self.evaluate_expression(expression, &BTreeMap::new())).unwrap();
|
||||||
self.evaluated.insert(name, value);
|
self.evaluated.insert(name, value);
|
||||||
} else {
|
} else {
|
||||||
let token = self.assignment_tokens.get(name).unwrap();
|
let token = self.assignment_tokens.get(name).unwrap();
|
||||||
@ -383,11 +429,35 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate_expression(&mut self, expression: &Expression<'a>) -> Result<String, Error<'a>> {
|
fn evaluate_recipe(
|
||||||
|
&mut self,
|
||||||
|
recipe: &mut Recipe<'a>,
|
||||||
|
arguments: &BTreeMap<&str, Option<&str>>,
|
||||||
|
) -> Result<(), Error<'a>> {
|
||||||
|
for fragments in &mut recipe.lines {
|
||||||
|
for mut fragment in fragments.iter_mut() {
|
||||||
|
match *fragment {
|
||||||
|
Fragment::Text{..} => {},
|
||||||
|
Fragment::Expression{ref expression, ref mut value} => {
|
||||||
|
*value = try!(self.evaluate_expression(&expression, arguments));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate_expression(
|
||||||
|
&mut self,
|
||||||
|
expression: &Expression<'a>,
|
||||||
|
arguments: &BTreeMap<&str, Option<&str>>
|
||||||
|
) -> Result<Option<String>, Error<'a>> {
|
||||||
Ok(match *expression {
|
Ok(match *expression {
|
||||||
Expression::Variable{name, ref token} => {
|
Expression::Variable{name, ref token} => {
|
||||||
if self.evaluated.contains_key(name) {
|
if self.evaluated.contains_key(name) {
|
||||||
self.evaluated.get(name).unwrap().clone()
|
Some(self.evaluated.get(name).unwrap().clone())
|
||||||
|
} else if self.scope.contains_key(name) {
|
||||||
|
Some(self.scope.get(name).unwrap().clone())
|
||||||
} else if self.seen.contains(name) {
|
} else if self.seen.contains(name) {
|
||||||
let token = self.assignment_tokens.get(name).unwrap();
|
let token = self.assignment_tokens.get(name).unwrap();
|
||||||
self.stack.push(name);
|
self.stack.push(name);
|
||||||
@ -395,20 +465,26 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
|||||||
variable: name,
|
variable: name,
|
||||||
circle: self.stack.clone(),
|
circle: self.stack.clone(),
|
||||||
}));
|
}));
|
||||||
} else if !self.assignments.contains_key(name) {
|
} else if self.assignments.contains_key(name) {
|
||||||
return Err(token.error(ErrorKind::UnknownVariable{variable: name}));
|
|
||||||
} else {
|
|
||||||
try!(self.evaluate_assignment(name));
|
try!(self.evaluate_assignment(name));
|
||||||
self.evaluated.get(name).unwrap().clone()
|
Some(self.evaluated.get(name).unwrap().clone())
|
||||||
|
} else if arguments.contains_key(name) {
|
||||||
|
arguments.get(name).unwrap().map(|s| s.to_string())
|
||||||
|
} else {
|
||||||
|
return Err(token.error(ErrorKind::UnknownVariable{variable: name}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::String{ref cooked, ..} => {
|
Expression::String{ref cooked, ..} => {
|
||||||
cooked.clone()
|
Some(cooked.clone())
|
||||||
}
|
}
|
||||||
Expression::Concatination{ref lhs, ref rhs} => {
|
Expression::Concatination{ref lhs, ref rhs} => {
|
||||||
try!(self.evaluate_expression(lhs))
|
let lhs = try!(self.evaluate_expression(lhs, arguments));
|
||||||
+
|
let rhs = try!(self.evaluate_expression(rhs, arguments));
|
||||||
&try!(self.evaluate_expression(rhs))
|
if let (Some(lhs), Some(rhs)) = (lhs, rhs) {
|
||||||
|
Some(lhs + &rhs)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -434,6 +510,7 @@ enum ErrorKind<'a> {
|
|||||||
DuplicateDependency{recipe: &'a str, dependency: &'a str},
|
DuplicateDependency{recipe: &'a str, dependency: &'a str},
|
||||||
DuplicateRecipe{recipe: &'a str, first: usize},
|
DuplicateRecipe{recipe: &'a str, first: usize},
|
||||||
DuplicateVariable{variable: &'a str},
|
DuplicateVariable{variable: &'a str},
|
||||||
|
DependencyHasArguments{recipe: &'a str, dependency: &'a str},
|
||||||
ExtraLeadingWhitespace,
|
ExtraLeadingWhitespace,
|
||||||
InconsistentLeadingWhitespace{expected: &'a str, found: &'a str},
|
InconsistentLeadingWhitespace{expected: &'a str, found: &'a str},
|
||||||
InternalError{message: String},
|
InternalError{message: String},
|
||||||
@ -517,6 +594,9 @@ impl<'a> Display for Error<'a> {
|
|||||||
recipe, first, self.line));
|
recipe, first, self.line));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
ErrorKind::DependencyHasArguments{recipe, dependency} => {
|
||||||
|
try!(writeln!(f, "recipe `{}` depends on `{}` which takes arguments. dependencies may not take arguments", recipe, dependency));
|
||||||
|
}
|
||||||
ErrorKind::ArgumentShadowsVariable{argument} => {
|
ErrorKind::ArgumentShadowsVariable{argument} => {
|
||||||
try!(writeln!(f, "argument `{}` shadows variable of the same name", argument));
|
try!(writeln!(f, "argument `{}` shadows variable of the same name", argument));
|
||||||
}
|
}
|
||||||
@ -578,7 +658,7 @@ struct Justfile<'a> {
|
|||||||
values: BTreeMap<&'a str, String>,
|
values: BTreeMap<&'a str, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Justfile<'a> {
|
impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
||||||
fn first(&self) -> Option<&'a str> {
|
fn first(&self) -> Option<&'a str> {
|
||||||
let mut first: Option<&Recipe<'a>> = None;
|
let mut first: Option<&Recipe<'a>> = None;
|
||||||
for recipe in self.recipes.values() {
|
for recipe in self.recipes.values() {
|
||||||
@ -601,22 +681,43 @@ impl<'a> Justfile<'a> {
|
|||||||
self.recipes.keys().cloned().collect()
|
self.recipes.keys().cloned().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_recipe(&self, recipe: &Recipe<'a>, ran: &mut HashSet<&'a str>) -> Result<(), RunError> {
|
fn run_recipe(&self, recipe: &Recipe<'a>, arguments: &[&'a str], ran: &mut HashSet<&'a str>) -> Result<(), RunError> {
|
||||||
for dependency_name in &recipe.dependencies {
|
for dependency_name in &recipe.dependencies {
|
||||||
if !ran.contains(dependency_name) {
|
if !ran.contains(dependency_name) {
|
||||||
try!(self.run_recipe(&self.recipes[dependency_name], ran));
|
try!(self.run_recipe(&self.recipes[dependency_name], &[], ran));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try!(recipe.run());
|
try!(recipe.run(arguments, &self.values));
|
||||||
ran.insert(recipe.name);
|
ran.insert(recipe.name);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run<'b>(&'a self, names: &[&'b str]) -> Result<(), RunError<'b>>
|
fn run(&'a self, arguments: &[&'b str]) -> Result<(), RunError<'b>> {
|
||||||
where 'a: 'b
|
for (i, argument) in arguments.iter().enumerate() {
|
||||||
{
|
if let Some(recipe) = self.recipes.get(argument) {
|
||||||
|
if !recipe.arguments.is_empty() {
|
||||||
|
if i != 0 {
|
||||||
|
return Err(RunError::NonLeadingRecipeWithArguments{recipe: recipe.name});
|
||||||
|
}
|
||||||
|
let rest = &arguments[1..];
|
||||||
|
if recipe.arguments.len() != rest.len() {
|
||||||
|
return Err(RunError::ArgumentCountMismatch {
|
||||||
|
recipe: recipe.name,
|
||||||
|
found: rest.len(),
|
||||||
|
expected: recipe.arguments.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let mut ran = HashSet::new();
|
||||||
|
try!(self.run_recipe(recipe, rest, &mut ran));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut missing = vec![];
|
let mut missing = vec![];
|
||||||
for recipe in names {
|
for recipe in arguments {
|
||||||
if !self.recipes.contains_key(recipe) {
|
if !self.recipes.contains_key(recipe) {
|
||||||
missing.push(*recipe);
|
missing.push(*recipe);
|
||||||
}
|
}
|
||||||
@ -624,15 +725,10 @@ impl<'a> Justfile<'a> {
|
|||||||
if !missing.is_empty() {
|
if !missing.is_empty() {
|
||||||
return Err(RunError::UnknownRecipes{recipes: missing});
|
return Err(RunError::UnknownRecipes{recipes: missing});
|
||||||
}
|
}
|
||||||
let recipes = names.iter().map(|name| self.recipes.get(name).unwrap()).collect::<Vec<_>>();
|
let recipes: Vec<_> = arguments.iter().map(|name| self.recipes.get(name).unwrap()).collect();
|
||||||
let mut ran = HashSet::new();
|
let mut ran = HashSet::new();
|
||||||
for recipe in &recipes {
|
|
||||||
if !recipe.arguments.is_empty() {
|
|
||||||
return Err(RunError::MissingArguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for recipe in recipes {
|
for recipe in recipes {
|
||||||
try!(self.run_recipe(recipe, &mut ran));
|
try!(self.run_recipe(recipe, &[], &mut ran));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -674,7 +770,8 @@ impl<'a> Display for Justfile<'a> {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum RunError<'a> {
|
enum RunError<'a> {
|
||||||
UnknownRecipes{recipes: Vec<&'a str>},
|
UnknownRecipes{recipes: Vec<&'a str>},
|
||||||
MissingArguments,
|
NonLeadingRecipeWithArguments{recipe: &'a str},
|
||||||
|
ArgumentCountMismatch{recipe: &'a str, found: usize, expected: usize},
|
||||||
Signal{recipe: &'a str, signal: i32},
|
Signal{recipe: &'a str, signal: i32},
|
||||||
Code{recipe: &'a str, code: i32},
|
Code{recipe: &'a str, code: i32},
|
||||||
UnknownFailure{recipe: &'a str},
|
UnknownFailure{recipe: &'a str},
|
||||||
@ -692,8 +789,13 @@ impl<'a> Display for RunError<'a> {
|
|||||||
try!(write!(f, "Justfile does not contain recipes: {}", recipes.join(" ")));
|
try!(write!(f, "Justfile does not contain recipes: {}", recipes.join(" ")));
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
RunError::MissingArguments => {
|
RunError::NonLeadingRecipeWithArguments{recipe} => {
|
||||||
try!(write!(f, "Running recipes with arguments is not yet supported"));
|
try!(write!(f, "Recipe `{}` takes arguments and so must be the first and only recipe specified on the command line", recipe));
|
||||||
|
},
|
||||||
|
RunError::ArgumentCountMismatch{recipe, found, expected} => {
|
||||||
|
try!(write!(f, "Recipe `{}` takes {} argument{}, but {}{} were found",
|
||||||
|
recipe, expected, if expected == 1 { "" } else { "s" },
|
||||||
|
if found < expected { "only " } else { "" }, found));
|
||||||
},
|
},
|
||||||
RunError::Code{recipe, code} => {
|
RunError::Code{recipe, code} => {
|
||||||
try!(write!(f, "Recipe \"{}\" failed with exit code {}", recipe, code));
|
try!(write!(f, "Recipe \"{}\" failed with exit code {}", recipe, code));
|
||||||
@ -1305,6 +1407,15 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for dependency in &recipe.dependency_tokens {
|
||||||
|
if !recipes.get(dependency.lexeme).unwrap().arguments.is_empty() {
|
||||||
|
return Err(dependency.error(ErrorKind::DependencyHasArguments {
|
||||||
|
recipe: recipe.name,
|
||||||
|
dependency: dependency.lexeme,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for line in &recipe.lines {
|
for line in &recipe.lines {
|
||||||
for piece in line {
|
for piece in line {
|
||||||
if let Fragment::Expression{ref expression, ..} = *piece {
|
if let Fragment::Expression{ref expression, ..} = *piece {
|
||||||
@ -1338,7 +1449,8 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let values = try!(evaluate(&assignments, &assignment_tokens, &mut recipes));
|
let values =
|
||||||
|
try!(evaluate(&assignments, &assignment_tokens, &mut recipes));
|
||||||
|
|
||||||
Ok(Justfile {
|
Ok(Justfile {
|
||||||
recipes: recipes,
|
recipes: recipes,
|
||||||
|
50
src/unit.rs
50
src/unit.rs
@ -338,6 +338,19 @@ fn argument_shadows_varible() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dependency_with_arguments() {
|
||||||
|
let text = "foo arg:\nb: foo";
|
||||||
|
parse_error(text, Error {
|
||||||
|
text: text,
|
||||||
|
index: 12,
|
||||||
|
line: 1,
|
||||||
|
column: 3,
|
||||||
|
width: Some(3),
|
||||||
|
kind: ErrorKind::DependencyHasArguments{recipe: "b", dependency: "foo"}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn duplicate_dependency() {
|
fn duplicate_dependency() {
|
||||||
let text = "a b c: b c z z";
|
let text = "a b c: b c z z";
|
||||||
@ -445,6 +458,16 @@ fn string_escapes() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arguments() {
|
||||||
|
parse_summary(
|
||||||
|
"a b c:
|
||||||
|
{{b}} {{c}}",
|
||||||
|
"a b c:
|
||||||
|
{{b # ? }} {{c # ? }}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn self_recipe_dependency() {
|
fn self_recipe_dependency() {
|
||||||
let text = "a: a";
|
let text = "a: a";
|
||||||
@ -640,7 +663,7 @@ fn unknown_second_interpolation_variable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn run_order() {
|
fn tokenize_order() {
|
||||||
let text = r"
|
let text = r"
|
||||||
b: a
|
b: a
|
||||||
@mv a b
|
@mv a b
|
||||||
@ -657,15 +680,6 @@ c: b
|
|||||||
tokenize_success(text, "$N:N$>^_$$<N:$>^_$^_$$<N:N$>^_$$<N:N$>^_<.");
|
tokenize_success(text, "$N:N$>^_$$<N:$>^_$^_$$<N:N$>^_$$<N:N$>^_<.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn run_arguments_not_supported() {
|
|
||||||
let text = "a foo:";
|
|
||||||
match parse_success(text).run(&["a"]) {
|
|
||||||
Err(super::RunError::MissingArguments) => {}
|
|
||||||
result => panic!("Expecting MissingArguments from run() but got {:?}", result),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn run_shebang() {
|
fn run_shebang() {
|
||||||
// this test exists to make sure that shebang recipes
|
// this test exists to make sure that shebang recipes
|
||||||
@ -703,3 +717,19 @@ fn code_error() {
|
|||||||
other @ _ => panic!("expected a code run error, but got: {}", other),
|
other @ _ => panic!("expected a code run error, but got: {}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn run_args() {
|
||||||
|
let text = r#"
|
||||||
|
a return code:
|
||||||
|
@function x { {{return}} {{code + "0"}}; }; x"#;
|
||||||
|
|
||||||
|
match parse_success(text).run(&["a", "return", "15"]).unwrap_err() {
|
||||||
|
super::RunError::Code{recipe, code} => {
|
||||||
|
assert_eq!(recipe, "a");
|
||||||
|
assert_eq!(code, 150);
|
||||||
|
},
|
||||||
|
other => panic!("expected an code run error, but got: {}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user