diff --git a/README.md b/README.md index f13d009..0803bb8 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,7 @@ build target: 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: diff --git a/src/integration.rs b/src/integration.rs index 08c8c24..14f8a61 100644 --- a/src/integration.rs +++ b/src/integration.rs @@ -911,7 +911,7 @@ foo A B: ", 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, "", - "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", ); } + +#[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", + ); +} diff --git a/src/lib.rs b/src/lib.rs index 67eb625..c40c0ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1218,37 +1218,34 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b { 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 !recipe.parameters.is_empty() { - if i != 0 { - return Err(RunError::NonLeadingRecipeWithParameters{recipe: recipe.name}); - } - let rest = &arguments[1..]; + if recipe.parameters.is_empty() { + grouped.push((recipe, &tail[0..0])); + } else { 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 { recipe: recipe.name, - found: rest.len(), + found: tail.len(), min: argument_range.start, 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 { - 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() { let suggestion = if missing.len() == 1 { 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}); } - 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(()) } @@ -1312,7 +1312,6 @@ enum RunError<'a> { Code{recipe: &'a str, line_number: Option, code: i32}, InternalError{message: String}, IoError{recipe: &'a str, io_error: io::Error}, - NonLeadingRecipeWithParameters{recipe: &'a str}, Signal{recipe: &'a str, line_number: Option, signal: i32}, TmpdirIoError{recipe: &'a str, io_error: io::Error}, UnknownFailure{recipe: &'a str, line_number: Option}, @@ -1347,10 +1346,6 @@ impl<'a> Display for RunError<'a> { maybe_s(overrides.len()), And(&overrides.iter().map(Tick).collect::>()))?; }, - 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} => { if min == max { let expected = min;