From 67bd318bf9a56be66d4f5bd130a9dbdbd4fe888d Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sat, 24 Apr 2021 18:29:58 -0700 Subject: [PATCH] Add `positional-arguments` setting (#804) Allow recipe arguments to be passed as positional arguments to commands. --- GRAMMAR.md | 1 + README.adoc | 29 +++++++++++++-- src/analyzer.rs | 3 ++ src/justfile.rs | 2 +- src/keyword.rs | 5 +-- src/node.rs | 3 +- src/parser.rs | 24 +++++++++++++ src/recipe.rs | 10 ++++++ src/setting.rs | 5 +-- src/settings.rs | 14 ++++---- tests/lib.rs | 1 + tests/positional_arguments.rs | 66 +++++++++++++++++++++++++++++++++++ 12 files changed, 148 insertions(+), 15 deletions(-) create mode 100644 tests/positional_arguments.rs diff --git a/GRAMMAR.md b/GRAMMAR.md index f0d6fbf..cf907df 100644 --- a/GRAMMAR.md +++ b/GRAMMAR.md @@ -60,6 +60,7 @@ export : 'export' assignment setting : 'set' 'dotenv-load' boolean? | 'set' 'export' boolean? + | 'set' 'positional-arguments' boolean? | 'set' 'shell' ':=' '[' string (',' string)* ','? ']' boolean : ':=' ('true' | 'false') diff --git a/README.adoc b/README.adoc index d364cdf..b8d7020 100644 --- a/README.adoc +++ b/README.adoc @@ -388,9 +388,10 @@ foo: [options="header"] |================= | Name | Value | Description -| `dotenv-load` | `true` or `false` | Load a `.env` file, if present. -| `export` | `true` or `false` | Export all variables as environment variables. -|`shell` | `[COMMAND, ARGS...]` | Set the command used to invoke recipes and evaluate backticks. +| `dotenv-load` | boolean | Load a `.env` file, if present. +| `export` | boolean | Export all variables as environment variables. +| `positional-arguments` | boolean | Pass positional arguments. +| `shell` | `[COMMAND, ARGS...]` | Set the command used to invoke recipes and evaluate backticks. |================= Boolean settings can be written as: @@ -429,6 +430,28 @@ hello goodbye ``` +==== Positional Arguments + +If `positional-arguments` is `true`, recipe arguments will be passed as positional arguments to commands. For linewise recipes, argument `$0` will be the name of the Recipe. + +For example, running this recipe: + +```make +set positional-arguments + +@foo bar: + echo $0 + echo $1 +``` + +Will produce the following output: + +``` +$ just foo hello +foo +hello +``` + ==== Shell The `shell` setting controls the command used to invoke recipe lines and backticks. Shebang recipes are unaffected. diff --git a/src/analyzer.rs b/src/analyzer.rs index 542125e..55cf648 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -71,6 +71,9 @@ impl<'src> Analyzer<'src> { Setting::Export(export) => { settings.export = export; }, + Setting::PositionalArguments(positional_arguments) => { + settings.positional_arguments = positional_arguments; + }, Setting::Shell(shell) => { assert!(settings.shell.is_none()); settings.shell = Some(shell); diff --git a/src/justfile.rs b/src/justfile.rs index 8145d28..749243a 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -266,7 +266,7 @@ impl<'src> Justfile<'src> { } } - recipe.run(context, dotenv, scope.child(), search)?; + recipe.run(context, dotenv, scope.child(), search, arguments)?; let mut invocation = vec![recipe.name().to_owned()]; for argument in arguments.iter().cloned() { diff --git a/src/keyword.rs b/src/keyword.rs index a3f6314..c7c97bf 100644 --- a/src/keyword.rs +++ b/src/keyword.rs @@ -4,14 +4,15 @@ use crate::common::*; #[strum(serialize_all = "kebab_case")] pub(crate) enum Keyword { Alias, + DotenvLoad, Else, Export, - DotenvLoad, - True, False, If, + PositionalArguments, Set, Shell, + True, } impl Keyword { diff --git a/src/node.rs b/src/node.rs index 433d45c..b0413ad 100644 --- a/src/node.rs +++ b/src/node.rs @@ -191,7 +191,8 @@ impl<'src> Node<'src> for Set<'src> { use Setting::*; match &self.value { - DotenvLoad(value) | Export(value) => set.push_mut(value.to_string()), + DotenvLoad(value) | Export(value) | PositionalArguments(value) => + set.push_mut(value.to_string()), Shell(setting::Shell { command, arguments }) => { set.push_mut(Tree::string(&command.cooked)); for argument in arguments { diff --git a/src/parser.rs b/src/parser.rs index 69b5d29..37bcf80 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -717,6 +717,12 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { value: Setting::Export(value), name, }); + } else if Keyword::PositionalArguments == lexeme { + let value = self.parse_set_bool()?; + return Ok(Set { + value: Setting::PositionalArguments(value), + name, + }); } self.expect(ColonEquals)?; @@ -1678,6 +1684,24 @@ mod tests { tree: (justfile (set dotenv_load false)), } + test! { + name: set_positional_arguments_implicit, + text: "set positional-arguments", + tree: (justfile (set positional_arguments true)), + } + + test! { + name: set_positional_arguments_true, + text: "set positional-arguments := true", + tree: (justfile (set positional_arguments true)), + } + + test! { + name: set_positional_arguments_false, + text: "set positional-arguments := false", + tree: (justfile (set positional_arguments false)), + } + test! { name: set_shell_no_arguments, text: "set shell := ['tclsh']", diff --git a/src/recipe.rs b/src/recipe.rs index 48ae7e1..f1fc40e 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -74,6 +74,7 @@ impl<'src, D> Recipe<'src, D> { dotenv: &BTreeMap, scope: Scope<'src, 'run>, search: &'run Search, + arguments: &[&'run str], ) -> RunResult<'src, ()> { let config = &context.config; @@ -176,6 +177,10 @@ impl<'src, D> Recipe<'src, D> { output_error, })?; + if context.settings.positional_arguments { + command.args(arguments); + } + command.export(context.settings, dotenv, &scope); // run it! @@ -260,6 +265,11 @@ impl<'src, D> Recipe<'src, D> { cmd.arg(command); + if context.settings.positional_arguments { + cmd.arg(self.name.lexeme()); + cmd.args(arguments); + } + if config.verbosity.quiet() { cmd.stderr(Stdio::null()); cmd.stdout(Stdio::null()); diff --git a/src/setting.rs b/src/setting.rs index 082baf1..011e3c0 100644 --- a/src/setting.rs +++ b/src/setting.rs @@ -2,9 +2,10 @@ use crate::common::*; #[derive(Debug)] pub(crate) enum Setting<'src> { - Shell(Shell<'src>), - Export(bool), DotenvLoad(bool), + Export(bool), + PositionalArguments(bool), + Shell(Shell<'src>), } #[derive(Debug, PartialEq)] diff --git a/src/settings.rs b/src/settings.rs index b86dc24..8e4e046 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -2,17 +2,19 @@ use crate::common::*; #[derive(Debug, PartialEq)] pub(crate) struct Settings<'src> { - pub(crate) dotenv_load: Option, - pub(crate) export: bool, - pub(crate) shell: Option>, + pub(crate) dotenv_load: Option, + pub(crate) export: bool, + pub(crate) positional_arguments: bool, + pub(crate) shell: Option>, } impl<'src> Settings<'src> { pub(crate) fn new() -> Settings<'src> { Settings { - dotenv_load: None, - export: false, - shell: None, + dotenv_load: None, + export: false, + positional_arguments: false, + shell: None, } } diff --git a/tests/lib.rs b/tests/lib.rs index 2ec5a2f..78cc53f 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -17,6 +17,7 @@ mod init; mod interrupts; mod invocation_directory; mod misc; +mod positional_arguments; mod quiet; mod readme; mod search; diff --git a/tests/positional_arguments.rs b/tests/positional_arguments.rs new file mode 100644 index 0000000..2d24298 --- /dev/null +++ b/tests/positional_arguments.rs @@ -0,0 +1,66 @@ +test! { + name: linewise, + justfile: r#" + set positional-arguments + + foo bar baz: + echo $0 + echo $1 + echo $2 + echo "$@" + "#, + args: ("foo", "hello", "goodbye"), + stdout: " + foo + hello + goodbye + hello goodbye + ", + stderr: r#" + echo $0 + echo $1 + echo $2 + echo "$@" + "#, +} + +test! { + name: variadic_linewise, + justfile: r#" + set positional-arguments + + foo *bar: + echo $1 + echo "$@" + "#, + args: ("foo", "a", "b", "c"), + stdout: "a\na b c\n", + stderr: "echo $1\necho \"$@\"\n", +} + +test! { + name: shebang, + justfile: " + set positional-arguments + + foo bar: + #!/bin/sh + echo $1 + ", + args: ("foo", "hello"), + stdout: "hello\n", +} + +test! { + name: variadic_shebang, + justfile: r#" + set positional-arguments + + foo *bar: + #!/bin/sh + echo $1 + echo "$@" + "#, + args: ("foo", "a", "b", "c"), + stdout: "a\na b c\n", +}