From d0e813cd8b5c5a4c8106cad6d6d547b2bddeddcc Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 22 Nov 2019 13:33:56 -0600 Subject: [PATCH] Add flags to set and clear shell arguments (#551) Add the `--shell-arg` and `--clear-shell-args` flags, which allow setting and clearing arguments to the shell from the command line. This allows full control over the shell from the command line. Additionally, any shell-related arguments on the command line override `set shell := [...]` in the Justfile, which I think will be the behavior that most people expect. --- src/assignment_evaluator.rs | 2 +- src/config.rs | 132 ++++++++++++++++++++++++++++++++---- src/recipe.rs | 2 +- src/settings.rs | 8 +-- tests/integration.rs | 88 ++++++++++++++++++------ 5 files changed, 193 insertions(+), 39 deletions(-) diff --git a/src/assignment_evaluator.rs b/src/assignment_evaluator.rs index ea9aab4..6c99a1f 100644 --- a/src/assignment_evaluator.rs +++ b/src/assignment_evaluator.rs @@ -164,7 +164,7 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> { raw: &str, token: &Token<'a>, ) -> RunResult<'a, String> { - let mut cmd = self.settings.shell_command(&self.config.shell); + let mut cmd = self.settings.shell_command(self.config); cmd.arg(raw); diff --git a/src/config.rs b/src/config.rs index df8f57e..1f0af00 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,6 +4,7 @@ use clap::{App, AppSettings, Arg, ArgGroup, ArgMatches}; use unicode_width::UnicodeWidthStr; pub(crate) const DEFAULT_SHELL: &str = "sh"; +pub(crate) const DEFAULT_SHELL_ARG: &str = "-cu"; pub(crate) const INIT_JUSTFILE: &str = "default:\n\techo 'Hello, world!'\n"; #[derive(Debug, PartialEq)] @@ -15,6 +16,8 @@ pub(crate) struct Config { pub(crate) quiet: bool, pub(crate) search_config: SearchConfig, pub(crate) shell: String, + pub(crate) shell_args: Vec, + pub(crate) shell_present: bool, pub(crate) subcommand: Subcommand, pub(crate) verbosity: Verbosity, } @@ -34,14 +37,16 @@ mod cmd { mod arg { pub(crate) const ARGUMENTS: &str = "ARGUMENTS"; + pub(crate) const CLEAR_SHELL_ARGS: &str = "CLEAR-SHELL-ARGS"; pub(crate) const COLOR: &str = "COLOR"; pub(crate) const DRY_RUN: &str = "DRY-RUN"; pub(crate) const HIGHLIGHT: &str = "HIGHLIGHT"; - pub(crate) const NO_HIGHLIGHT: &str = "NO-HIGHLIGHT"; pub(crate) const JUSTFILE: &str = "JUSTFILE"; + pub(crate) const NO_HIGHLIGHT: &str = "NO-HIGHLIGHT"; pub(crate) const QUIET: &str = "QUIET"; pub(crate) const SET: &str = "SET"; pub(crate) const SHELL: &str = "SHELL"; + pub(crate) const SHELL_ARG: &str = "SHELL-ARG"; pub(crate) const VERBOSE: &str = "VERBOSE"; pub(crate) const WORKING_DIRECTORY: &str = "WORKING-DIRECTORY"; @@ -114,6 +119,23 @@ impl Config { .default_value(DEFAULT_SHELL) .help("Invoke to run recipes"), ) + .arg( + Arg::with_name(arg::SHELL_ARG) + .long("shell-arg") + .takes_value(true) + .multiple(true) + .number_of_values(1) + .default_value(DEFAULT_SHELL_ARG) + .allow_hyphen_values(true) + .overrides_with(arg::CLEAR_SHELL_ARGS) + .help("Invoke shell with as an argument"), + ) + .arg( + Arg::with_name(arg::CLEAR_SHELL_ARGS) + .long("clear-shell-args") + .overrides_with(arg::SHELL_ARG) + .help("Clear shell arguments"), + ) .arg( Arg::with_name(arg::VERBOSE) .short("v") @@ -318,16 +340,33 @@ impl Config { } }; + let shell_args = if matches.is_present(arg::CLEAR_SHELL_ARGS) { + Vec::new() + } else { + matches + .values_of(arg::SHELL_ARG) + .unwrap() + .into_iter() + .map(str::to_owned) + .collect() + }; + + let shell_present = matches.occurrences_of(arg::CLEAR_SHELL_ARGS) > 0 + || matches.occurrences_of(arg::SHELL) > 0 + || matches.occurrences_of(arg::SHELL_ARG) > 0; + Ok(Config { dry_run: matches.is_present(arg::DRY_RUN), highlight: !matches.is_present(arg::NO_HIGHLIGHT), quiet: matches.is_present(arg::QUIET), shell: matches.value_of(arg::SHELL).unwrap().to_owned(), - search_config, + color, invocation_directory, + search_config, + shell_args, + shell_present, subcommand, verbosity, - color, }) } @@ -594,18 +633,19 @@ USAGE: just [FLAGS] [OPTIONS] [--] [ARGUMENTS]... FLAGS: - --dry-run Print what just would do without doing it - --dump Print entire justfile - -e, --edit \ + --clear-shell-args Clear shell arguments + --dry-run Print what just would do without doing it + --dump Print entire justfile + -e, --edit \ Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim` - --evaluate Print evaluated variables - --highlight Highlight echoed recipe lines in bold - --init Initialize new justfile in project root - -l, --list List available recipes and their arguments - --no-highlight Don't highlight echoed recipe lines in bold - -q, --quiet Suppress all output - --summary List names of available recipes - -v, --verbose Use verbose output + --evaluate Print evaluated variables + --highlight Highlight echoed recipe lines in bold + --init Initialize new justfile in project root + -l, --list List available recipes and their arguments + --no-highlight Don't highlight echoed recipe lines in bold + -q, --quiet Suppress all output + --summary List names of available recipes + -v, --verbose Use verbose output OPTIONS: --color @@ -614,6 +654,8 @@ OPTIONS: -f, --justfile Use as justfile. --set Override with --shell Invoke to run recipes [default: sh] + --shell-arg ... \ + Invoke shell with as an argument [default: -cu] -s, --show Show information about -d, --working-directory Use as working directory. --justfile must also be set @@ -641,6 +683,8 @@ ARGS: $(quiet: $quiet:expr,)? $(search_config: $search_config:expr,)? $(shell: $shell:expr,)? + $(shell_args: $shell_args:expr,)? + $(shell_present: $shell_present:expr,)? $(subcommand: $subcommand:expr,)? $(verbosity: $verbosity:expr,)? } => { @@ -658,6 +702,8 @@ ARGS: $(quiet: $quiet,)? $(search_config: $search_config,)? $(shell: $shell.to_string(),)? + $(shell_args: $shell_args,)? + $(shell_present: $shell_present,)? $(subcommand: $subcommand,)? $(verbosity: $verbosity,)? ..testing::config(&[]) @@ -895,12 +941,15 @@ ARGS: name: shell_default, args: [], shell: "sh", + shell_args: vec!["-cu".to_owned()], + shell_present: false, } test! { name: shell_set, args: ["--shell", "tclsh"], shell: "tclsh", + shell_present: true, } test! { @@ -1050,6 +1099,61 @@ ARGS: }, } + test! { + name: shell_args_default, + args: [], + shell_args: vec!["-cu".to_owned()], + } + + test! { + name: shell_args_set_hyphen, + args: ["--shell-arg", "--foo"], + shell_args: vec!["--foo".to_owned()], + shell_present: true, + } + + test! { + name: shell_args_set_word, + args: ["--shell-arg", "foo"], + shell_args: vec!["foo".to_owned()], + shell_present: true, + } + + test! { + name: shell_args_set_multiple, + args: ["--shell-arg", "foo", "--shell-arg", "bar"], + shell_args: vec!["foo".to_owned(), "bar".to_owned()], + shell_present: true, + } + + test! { + name: shell_args_clear, + args: ["--clear-shell-args"], + shell_args: vec![], + shell_present: true, + } + + test! { + name: shell_args_clear_and_set, + args: ["--clear-shell-args", "--shell-arg", "bar"], + shell_args: vec!["bar".to_owned()], + shell_present: true, + } + + test! { + name: shell_args_set_and_clear, + args: ["--shell-arg", "bar", "--clear-shell-args"], + shell_args: vec![], + shell_present: true, + } + + test! { + name: shell_args_set_multiple_and_clear, + args: ["--shell-arg", "bar", "--shell-arg", "baz", "--clear-shell-args"], + shell_args: vec![], + shell_present: true, + } + test! { name: search_config_default, args: [], diff --git a/src/recipe.rs b/src/recipe.rs index a23561a..250dca8 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -275,7 +275,7 @@ impl<'a, D> Recipe<'a, D> { continue; } - let mut cmd = context.settings.shell_command(&config.shell); + let mut cmd = context.settings.shell_command(config); cmd.current_dir(context.working_directory); diff --git a/src/settings.rs b/src/settings.rs index 7ebd56c..39bdb2e 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -10,8 +10,8 @@ impl<'src> Settings<'src> { Settings { shell: None } } - pub(crate) fn shell_command(&self, default_shell: &str) -> Command { - if let Some(shell) = &self.shell { + pub(crate) fn shell_command(&self, config: &Config) -> Command { + if let (Some(shell), false) = (&self.shell, config.shell_present) { let mut cmd = Command::new(shell.command.cooked.as_ref()); for argument in &shell.arguments { @@ -20,9 +20,9 @@ impl<'src> Settings<'src> { cmd } else { - let mut cmd = Command::new(default_shell); + let mut cmd = Command::new(&config.shell); - cmd.arg("-cu"); + cmd.args(&config.shell_args); cmd } diff --git a/tests/integration.rs b/tests/integration.rs index d32ea42..41c696d 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -20,6 +20,7 @@ macro_rules! test { $(stdout: $stdout:expr,)? $(stderr: $stderr:expr,)? $(status: $status:expr,)? + $(shell: $shell:expr,)? ) => { #[test] fn $name() { @@ -30,6 +31,7 @@ macro_rules! test { $(stdout: $stdout,)? $(stderr: $stderr,)? $(status: $status,)? + $(shell: $shell,)? ..Test::default() }.run(); } @@ -43,6 +45,7 @@ struct Test<'a> { stdout: &'a str, stderr: &'a str, status: i32, + shell: bool, } impl<'a> Default for Test<'a> { @@ -54,6 +57,7 @@ impl<'a> Default for Test<'a> { stdout: "", stderr: "", status: EXIT_SUCCESS, + shell: true, } } } @@ -74,10 +78,15 @@ impl<'a> Test<'a> { dotenv_path.push(".env"); fs::write(dotenv_path, "DOTENV_KEY=dotenv-value").unwrap(); - let mut child = Command::new(&executable_path("just")) - .current_dir(tmp.path()) - .args(&["--shell", "bash"]) + let mut command = Command::new(&executable_path("just")); + + if self.shell { + command.args(&["--shell", "bash"]); + } + + let mut child = command .args(self.args) + .current_dir(tmp.path()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -350,22 +359,6 @@ _y: stdout: "a b c d\n", } -test! { - name: set_shell, - justfile: " - set shell := ['echo', '-n'] - - x := `bar` - - foo: - echo {{x}} - echo foo - ", - args: (), - stdout: "echo barecho foo", - stderr: "echo bar\necho foo\n", -} - test! { name: select, justfile: "b: @@ -2211,3 +2204,60 @@ test! { echo default ", } + +test! { + name: shell_args, + justfile: " + default: + echo A${foo}A + ", + args: ("--shell-arg", "-c"), + stdout: "AA\n", + stderr: "echo A${foo}A\n", + shell: false, +} + +test! { + name: shell_override, + justfile: " + set shell := ['foo-bar-baz'] + + default: + echo hello + ", + args: ("--shell", "bash"), + stdout: "hello\n", + stderr: "echo hello\n", + shell: false, +} + +test! { + name: shell_arg_override, + justfile: " + set shell := ['foo-bar-baz'] + + default: + echo hello + ", + args: ("--shell-arg", "-cu"), + stdout: "hello\n", + stderr: "echo hello\n", + shell: false, +} + +test! { + name: set_shell, + justfile: " + set shell := ['echo', '-n'] + + x := `bar` + + foo: + echo {{x}} + echo foo + ", + args: (), + stdout: "echo barecho foo", + stderr: "echo bar\necho foo\n", + shell: false, +}