Lift limitations on recipes that take parameters (#137)

Previously, only one recipe with parameters could be passed on the
command line. This was to avoid confusion in case the number of
parameters a recipe took changed, and wound up using as an argument was
was once a recipe.

However, I don't think this is actually particularly confusing in
practice, and could be a slightly annoying limitation.

Now, any number of recipes with parameters may be given on the command
line.

Fixes #70
This commit is contained in:
Casey Rodarmor 2016-12-10 16:35:52 -08:00 committed by GitHub
parent af543d7258
commit c6256333ed
3 changed files with 42 additions and 27 deletions

View File

@ -239,7 +239,7 @@ build target:
cd {{target}} && make cd {{target}} && make
``` ```
Recipes with parameters have limitations. Other recipes may not depend on them, and only one recipe with parameters may be given on the command line. Other recipes may not depend on a recipe with parameters.
To pass arguments, put them after the recipe name: To pass arguments, put them after the recipe name:

View File

@ -911,7 +911,7 @@ foo A B:
", ",
255, 255,
"", "",
"error: Recipe `foo` got 3 arguments but only takes 2\n", "error: Justfile does not contain recipe `THREE`.\n",
); );
} }
@ -939,7 +939,7 @@ foo A B='B':
", ",
255, 255,
"", "",
"error: Recipe `foo` got 3 arguments but takes at most 2\n", "error: Justfile does not contain recipe `THREE`.\n",
); );
} }
@ -1537,3 +1537,23 @@ a x y +z:
"error: Recipe `a` got 2 arguments but takes at least 3\n", "error: Recipe `a` got 2 arguments but takes at least 3\n",
); );
} }
#[test]
fn argument_grouping() {
integration_test(
&["BAR", "0", "FOO", "1", "2", "BAZ", "3", "4", "5"],
"
FOO A B='blarg':
echo foo: {{A}} {{B}}
BAR X:
echo bar: {{X}}
BAZ +Z:
echo baz: {{Z}}
",
0,
"bar: 0\nfoo: 1 2\nbaz: 3 4 5\n",
"echo bar: 0\necho foo: 1 2\necho baz: 3 4 5\n",
);
}

View File

@ -1218,37 +1218,34 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
return Ok(()); return Ok(());
} }
let mut ran = empty(); let mut missing = vec![];
let mut grouped = vec![];
let mut rest = arguments;
for (i, argument) in arguments.iter().enumerate() { while let Some((argument, mut tail)) = rest.split_first() {
if let Some(recipe) = self.recipes.get(argument) { if let Some(recipe) = self.recipes.get(argument) {
if !recipe.parameters.is_empty() { if recipe.parameters.is_empty() {
if i != 0 { grouped.push((recipe, &tail[0..0]));
return Err(RunError::NonLeadingRecipeWithParameters{recipe: recipe.name}); } else {
}
let rest = &arguments[1..];
let argument_range = recipe.argument_range(); let argument_range = recipe.argument_range();
if !contains(&argument_range, rest.len()) { let argument_count = cmp::min(tail.len(), argument_range.end - 1);
if !contains(&argument_range, argument_count) {
return Err(RunError::ArgumentCountMismatch { return Err(RunError::ArgumentCountMismatch {
recipe: recipe.name, recipe: recipe.name,
found: rest.len(), found: tail.len(),
min: argument_range.start, min: argument_range.start,
max: argument_range.end - 1, max: argument_range.end - 1,
}); });
} }
return self.run_recipe(recipe, rest, &scope, &mut ran, options); grouped.push((recipe, &tail[0..argument_count]));
tail = &tail[argument_count..];
} }
} else { } else {
break; missing.push(*argument);
} }
rest = tail;
} }
let mut missing = vec![];
for recipe in arguments {
if !self.recipes.contains_key(recipe) {
missing.push(*recipe);
}
}
if !missing.is_empty() { if !missing.is_empty() {
let suggestion = if missing.len() == 1 { let suggestion = if missing.len() == 1 {
self.suggest(missing.first().unwrap()) self.suggest(missing.first().unwrap())
@ -1257,9 +1254,12 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
}; };
return Err(RunError::UnknownRecipes{recipes: missing, suggestion: suggestion}); return Err(RunError::UnknownRecipes{recipes: missing, suggestion: suggestion});
} }
for recipe in arguments.iter().map(|name| &self.recipes[name]) {
self.run_recipe(recipe, &[], &scope, &mut ran, options)?; let mut ran = empty();
for (recipe, arguments) in grouped {
self.run_recipe(recipe, arguments, &scope, &mut ran, options)?
} }
Ok(()) Ok(())
} }
@ -1312,7 +1312,6 @@ enum RunError<'a> {
Code{recipe: &'a str, line_number: Option<usize>, code: i32}, Code{recipe: &'a str, line_number: Option<usize>, code: i32},
InternalError{message: String}, InternalError{message: String},
IoError{recipe: &'a str, io_error: io::Error}, IoError{recipe: &'a str, io_error: io::Error},
NonLeadingRecipeWithParameters{recipe: &'a str},
Signal{recipe: &'a str, line_number: Option<usize>, signal: i32}, Signal{recipe: &'a str, line_number: Option<usize>, signal: i32},
TmpdirIoError{recipe: &'a str, io_error: io::Error}, TmpdirIoError{recipe: &'a str, io_error: io::Error},
UnknownFailure{recipe: &'a str, line_number: Option<usize>}, UnknownFailure{recipe: &'a str, line_number: Option<usize>},
@ -1347,10 +1346,6 @@ impl<'a> Display for RunError<'a> {
maybe_s(overrides.len()), maybe_s(overrides.len()),
And(&overrides.iter().map(Tick).collect::<Vec<_>>()))?; And(&overrides.iter().map(Tick).collect::<Vec<_>>()))?;
}, },
NonLeadingRecipeWithParameters{recipe} => {
write!(f, "Recipe `{}` takes arguments and so must be the first and only recipe \
specified on the command line", recipe)?;
},
ArgumentCountMismatch{recipe, found, min, max} => { ArgumentCountMismatch{recipe, found, min, max} => {
if min == max { if min == max {
let expected = min; let expected = min;