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.
This commit is contained in:
Casey Rodarmor 2019-11-22 13:33:56 -06:00 committed by GitHub
parent d2decbfdb8
commit d0e813cd8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 193 additions and 39 deletions

View File

@ -164,7 +164,7 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> {
raw: &str, raw: &str,
token: &Token<'a>, token: &Token<'a>,
) -> RunResult<'a, String> { ) -> 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); cmd.arg(raw);

View File

@ -4,6 +4,7 @@ use clap::{App, AppSettings, Arg, ArgGroup, ArgMatches};
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
pub(crate) const DEFAULT_SHELL: &str = "sh"; 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"; pub(crate) const INIT_JUSTFILE: &str = "default:\n\techo 'Hello, world!'\n";
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -15,6 +16,8 @@ pub(crate) struct Config {
pub(crate) quiet: bool, pub(crate) quiet: bool,
pub(crate) search_config: SearchConfig, pub(crate) search_config: SearchConfig,
pub(crate) shell: String, pub(crate) shell: String,
pub(crate) shell_args: Vec<String>,
pub(crate) shell_present: bool,
pub(crate) subcommand: Subcommand, pub(crate) subcommand: Subcommand,
pub(crate) verbosity: Verbosity, pub(crate) verbosity: Verbosity,
} }
@ -34,14 +37,16 @@ mod cmd {
mod arg { mod arg {
pub(crate) const ARGUMENTS: &str = "ARGUMENTS"; 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 COLOR: &str = "COLOR";
pub(crate) const DRY_RUN: &str = "DRY-RUN"; pub(crate) const DRY_RUN: &str = "DRY-RUN";
pub(crate) const HIGHLIGHT: &str = "HIGHLIGHT"; pub(crate) const HIGHLIGHT: &str = "HIGHLIGHT";
pub(crate) const NO_HIGHLIGHT: &str = "NO-HIGHLIGHT";
pub(crate) const JUSTFILE: &str = "JUSTFILE"; pub(crate) const JUSTFILE: &str = "JUSTFILE";
pub(crate) const NO_HIGHLIGHT: &str = "NO-HIGHLIGHT";
pub(crate) const QUIET: &str = "QUIET"; pub(crate) const QUIET: &str = "QUIET";
pub(crate) const SET: &str = "SET"; pub(crate) const SET: &str = "SET";
pub(crate) const SHELL: &str = "SHELL"; pub(crate) const SHELL: &str = "SHELL";
pub(crate) const SHELL_ARG: &str = "SHELL-ARG";
pub(crate) const VERBOSE: &str = "VERBOSE"; pub(crate) const VERBOSE: &str = "VERBOSE";
pub(crate) const WORKING_DIRECTORY: &str = "WORKING-DIRECTORY"; pub(crate) const WORKING_DIRECTORY: &str = "WORKING-DIRECTORY";
@ -114,6 +119,23 @@ impl Config {
.default_value(DEFAULT_SHELL) .default_value(DEFAULT_SHELL)
.help("Invoke <SHELL> to run recipes"), .help("Invoke <SHELL> 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 <SHELL-ARG> 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(
Arg::with_name(arg::VERBOSE) Arg::with_name(arg::VERBOSE)
.short("v") .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 { Ok(Config {
dry_run: matches.is_present(arg::DRY_RUN), dry_run: matches.is_present(arg::DRY_RUN),
highlight: !matches.is_present(arg::NO_HIGHLIGHT), highlight: !matches.is_present(arg::NO_HIGHLIGHT),
quiet: matches.is_present(arg::QUIET), quiet: matches.is_present(arg::QUIET),
shell: matches.value_of(arg::SHELL).unwrap().to_owned(), shell: matches.value_of(arg::SHELL).unwrap().to_owned(),
search_config, color,
invocation_directory, invocation_directory,
search_config,
shell_args,
shell_present,
subcommand, subcommand,
verbosity, verbosity,
color,
}) })
} }
@ -594,6 +633,7 @@ USAGE:
just [FLAGS] [OPTIONS] [--] [ARGUMENTS]... just [FLAGS] [OPTIONS] [--] [ARGUMENTS]...
FLAGS: FLAGS:
--clear-shell-args Clear shell arguments
--dry-run Print what just would do without doing it --dry-run Print what just would do without doing it
--dump Print entire justfile --dump Print entire justfile
-e, --edit \ -e, --edit \
@ -614,6 +654,8 @@ OPTIONS:
-f, --justfile <JUSTFILE> Use <JUSTFILE> as justfile. -f, --justfile <JUSTFILE> Use <JUSTFILE> as justfile.
--set <VARIABLE> <VALUE> Override <VARIABLE> with <VALUE> --set <VARIABLE> <VALUE> Override <VARIABLE> with <VALUE>
--shell <SHELL> Invoke <SHELL> to run recipes [default: sh] --shell <SHELL> Invoke <SHELL> to run recipes [default: sh]
--shell-arg <SHELL-ARG>... \
Invoke shell with <SHELL-ARG> as an argument [default: -cu]
-s, --show <RECIPE> Show information about <RECIPE> -s, --show <RECIPE> Show information about <RECIPE>
-d, --working-directory <WORKING-DIRECTORY> -d, --working-directory <WORKING-DIRECTORY>
Use <WORKING-DIRECTORY> as working directory. --justfile must also be set Use <WORKING-DIRECTORY> as working directory. --justfile must also be set
@ -641,6 +683,8 @@ ARGS:
$(quiet: $quiet:expr,)? $(quiet: $quiet:expr,)?
$(search_config: $search_config:expr,)? $(search_config: $search_config:expr,)?
$(shell: $shell:expr,)? $(shell: $shell:expr,)?
$(shell_args: $shell_args:expr,)?
$(shell_present: $shell_present:expr,)?
$(subcommand: $subcommand:expr,)? $(subcommand: $subcommand:expr,)?
$(verbosity: $verbosity:expr,)? $(verbosity: $verbosity:expr,)?
} => { } => {
@ -658,6 +702,8 @@ ARGS:
$(quiet: $quiet,)? $(quiet: $quiet,)?
$(search_config: $search_config,)? $(search_config: $search_config,)?
$(shell: $shell.to_string(),)? $(shell: $shell.to_string(),)?
$(shell_args: $shell_args,)?
$(shell_present: $shell_present,)?
$(subcommand: $subcommand,)? $(subcommand: $subcommand,)?
$(verbosity: $verbosity,)? $(verbosity: $verbosity,)?
..testing::config(&[]) ..testing::config(&[])
@ -895,12 +941,15 @@ ARGS:
name: shell_default, name: shell_default,
args: [], args: [],
shell: "sh", shell: "sh",
shell_args: vec!["-cu".to_owned()],
shell_present: false,
} }
test! { test! {
name: shell_set, name: shell_set,
args: ["--shell", "tclsh"], args: ["--shell", "tclsh"],
shell: "tclsh", shell: "tclsh",
shell_present: true,
} }
test! { 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! { test! {
name: search_config_default, name: search_config_default,
args: [], args: [],

View File

@ -275,7 +275,7 @@ impl<'a, D> Recipe<'a, D> {
continue; continue;
} }
let mut cmd = context.settings.shell_command(&config.shell); let mut cmd = context.settings.shell_command(config);
cmd.current_dir(context.working_directory); cmd.current_dir(context.working_directory);

View File

@ -10,8 +10,8 @@ impl<'src> Settings<'src> {
Settings { shell: None } Settings { shell: None }
} }
pub(crate) fn shell_command(&self, default_shell: &str) -> Command { pub(crate) fn shell_command(&self, config: &Config) -> Command {
if let Some(shell) = &self.shell { if let (Some(shell), false) = (&self.shell, config.shell_present) {
let mut cmd = Command::new(shell.command.cooked.as_ref()); let mut cmd = Command::new(shell.command.cooked.as_ref());
for argument in &shell.arguments { for argument in &shell.arguments {
@ -20,9 +20,9 @@ impl<'src> Settings<'src> {
cmd cmd
} else { } else {
let mut cmd = Command::new(default_shell); let mut cmd = Command::new(&config.shell);
cmd.arg("-cu"); cmd.args(&config.shell_args);
cmd cmd
} }

View File

@ -20,6 +20,7 @@ macro_rules! test {
$(stdout: $stdout:expr,)? $(stdout: $stdout:expr,)?
$(stderr: $stderr:expr,)? $(stderr: $stderr:expr,)?
$(status: $status:expr,)? $(status: $status:expr,)?
$(shell: $shell:expr,)?
) => { ) => {
#[test] #[test]
fn $name() { fn $name() {
@ -30,6 +31,7 @@ macro_rules! test {
$(stdout: $stdout,)? $(stdout: $stdout,)?
$(stderr: $stderr,)? $(stderr: $stderr,)?
$(status: $status,)? $(status: $status,)?
$(shell: $shell,)?
..Test::default() ..Test::default()
}.run(); }.run();
} }
@ -43,6 +45,7 @@ struct Test<'a> {
stdout: &'a str, stdout: &'a str,
stderr: &'a str, stderr: &'a str,
status: i32, status: i32,
shell: bool,
} }
impl<'a> Default for Test<'a> { impl<'a> Default for Test<'a> {
@ -54,6 +57,7 @@ impl<'a> Default for Test<'a> {
stdout: "", stdout: "",
stderr: "", stderr: "",
status: EXIT_SUCCESS, status: EXIT_SUCCESS,
shell: true,
} }
} }
} }
@ -74,10 +78,15 @@ impl<'a> Test<'a> {
dotenv_path.push(".env"); dotenv_path.push(".env");
fs::write(dotenv_path, "DOTENV_KEY=dotenv-value").unwrap(); fs::write(dotenv_path, "DOTENV_KEY=dotenv-value").unwrap();
let mut child = Command::new(&executable_path("just")) let mut command = Command::new(&executable_path("just"));
.current_dir(tmp.path())
.args(&["--shell", "bash"]) if self.shell {
command.args(&["--shell", "bash"]);
}
let mut child = command
.args(self.args) .args(self.args)
.current_dir(tmp.path())
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::piped()) .stderr(Stdio::piped())
@ -350,22 +359,6 @@ _y:
stdout: "a b c d\n", 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! { test! {
name: select, name: select,
justfile: "b: justfile: "b:
@ -2211,3 +2204,60 @@ test! {
echo default 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,
}