Add windows-powershell setting (#1057)

This commit is contained in:
Michael Lohr 2022-01-18 20:02:15 +01:00 committed by GitHub
parent edf1fbd83b
commit 6cf95a7337
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 276 additions and 58 deletions

View File

@ -559,6 +559,7 @@ foo:
| `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.
| `windows-powershell` | boolean | Use PowerShell on Windows as default shell.
|=================
Boolean settings can be written as:
@ -637,7 +638,7 @@ $ just test foo "bar baz"
- foo
- bar baz
```
==== Shell
The `shell` setting controls the command used to invoke recipe lines and backticks. Shebang recipes are unaffected.
@ -656,6 +657,17 @@ foo:
`just` passes the command to be executed as an argument. Many shells will need an additional flag, often `-c`, to make them evaluate the first argument.
==== Windows PowerShell
`just` uses `sh` on Windows by default. To use PowerShell instead, set `windows-powershell` to true.
```make
set windows-powershell := true
hello:
Write-Host "Hello, world!"
```
===== Python 3
```make

View File

@ -76,6 +76,9 @@ impl<'src> Analyzer<'src> {
assert!(settings.shell.is_none());
settings.shell = Some(shell);
}
Setting::WindowsPowerShell(windows_powershell) => {
settings.windows_powershell = windows_powershell;
}
}
}

View File

@ -9,9 +9,6 @@ pub(crate) const CHOOSE_HELP: &str = "Select one or more recipes to run using a
`--chooser` is not passed the chooser defaults to the value \
of $JUST_CHOOSER, falling back to `fzf`";
pub(crate) const DEFAULT_SHELL: &str = "sh";
pub(crate) const DEFAULT_SHELL_ARG: &str = "-cu";
#[derive(Debug, PartialEq)]
pub(crate) struct Config {
pub(crate) check: bool,
@ -26,10 +23,9 @@ pub(crate) struct Config {
pub(crate) list_prefix: String,
pub(crate) load_dotenv: bool,
pub(crate) search_config: SearchConfig,
pub(crate) shell: String,
pub(crate) shell_args: Vec<String>,
pub(crate) shell: Option<String>,
pub(crate) shell_args: Option<Vec<String>>,
pub(crate) shell_command: bool,
pub(crate) shell_present: bool,
pub(crate) subcommand: Subcommand,
pub(crate) unsorted: bool,
pub(crate) unstable: bool,
@ -217,7 +213,6 @@ impl Config {
Arg::with_name(arg::SHELL)
.long("shell")
.takes_value(true)
.default_value(DEFAULT_SHELL)
.help("Invoke <SHELL> to run recipes"),
)
.arg(
@ -226,7 +221,6 @@ impl Config {
.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"),
@ -558,26 +552,26 @@ impl Config {
}
};
let shell_args = if matches.is_present(arg::CLEAR_SHELL_ARGS) {
Vec::new()
let shell_args = if matches.occurrences_of(arg::SHELL_ARG) > 0
|| matches.occurrences_of(arg::CLEAR_SHELL_ARGS) > 0
{
Some(
matches
.values_of(arg::SHELL_ARG)
.map_or(Vec::new(), |shell_args| {
shell_args.map(str::to_owned).collect()
}),
)
} else {
matches
.values_of(arg::SHELL_ARG)
.unwrap()
.map(str::to_owned)
.collect()
None
};
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(Self {
check: matches.is_present(arg::CHECK),
dry_run: matches.is_present(arg::DRY_RUN),
dump_format: Self::dump_format_from_matches(matches)?,
highlight: !matches.is_present(arg::NO_HIGHLIGHT),
shell: matches.value_of(arg::SHELL).unwrap().to_owned(),
shell: matches.value_of(arg::SHELL).map(str::to_owned),
load_dotenv: !matches.is_present(arg::NO_DOTENV),
shell_command: matches.is_present(arg::SHELL_COMMAND),
unsorted: matches.is_present(arg::UNSORTED),
@ -594,7 +588,6 @@ impl Config {
invocation_directory,
search_config,
shell_args,
shell_present,
subcommand,
dotenv_filename: matches.value_of(arg::DOTENV_FILENAME).map(str::to_owned),
dotenv_path: matches.value_of(arg::DOTENV_PATH).map(PathBuf::from),
@ -697,11 +690,9 @@ OPTIONS:
--set <VARIABLE> <VALUE>
Override <VARIABLE> with <VALUE>
--shell <SHELL>
Invoke <SHELL> to run recipes [default: sh]
--shell <SHELL> Invoke <SHELL> to run recipes
--shell-arg <SHELL-ARG>...
Invoke shell with <SHELL-ARG> as an argument [default: -cu]
Invoke shell with <SHELL-ARG> as an argument
-s, --show <RECIPE>
Show information about <RECIPE>
@ -735,7 +726,6 @@ ARGS:
$(search_config: $search_config:expr,)?
$(shell: $shell:expr,)?
$(shell_args: $shell_args:expr,)?
$(shell_present: $shell_present:expr,)?
$(subcommand: $subcommand:expr,)?
$(unsorted: $unsorted:expr,)?
$(verbosity: $verbosity:expr,)?
@ -753,9 +743,8 @@ ARGS:
$(dump_format: $dump_format,)?
$(highlight: $highlight,)?
$(search_config: $search_config,)?
$(shell: $shell.to_owned(),)?
$(shell: $shell,)?
$(shell_args: $shell_args,)?
$(shell_present: $shell_present,)?
$(subcommand: $subcommand,)?
$(unsorted: $unsorted,)?
$(verbosity: $verbosity,)?
@ -1016,16 +1005,21 @@ ARGS:
test! {
name: shell_default,
args: [],
shell: "sh",
shell_args: vec!["-cu".to_owned()],
shell_present: false,
shell: None,
shell_args: None,
}
test! {
name: shell_set,
args: ["--shell", "tclsh"],
shell: "tclsh",
shell_present: true,
shell: Some("tclsh".to_owned()),
}
test! {
name: shell_args_set,
args: ["--shell-arg", "hello"],
shell: None,
shell_args: Some(vec!["hello".into()]),
}
test! {
@ -1262,56 +1256,53 @@ 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,
shell_args: Some(vec!["--foo".to_owned()]),
}
test! {
name: shell_args_set_word,
args: ["--shell-arg", "foo"],
shell_args: vec!["foo".to_owned()],
shell_present: true,
shell_args: Some(vec!["foo".to_owned()]),
}
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,
shell_args: Some(vec!["foo".to_owned(), "bar".to_owned()]),
}
test! {
name: shell_args_clear,
args: ["--clear-shell-args"],
shell_args: vec![],
shell_present: true,
shell_args: Some(vec![]),
}
test! {
name: shell_args_clear_and_set,
args: ["--clear-shell-args", "--shell-arg", "bar"],
shell_args: vec!["bar".to_owned()],
shell_present: true,
shell_args: Some(vec!["bar".to_owned()]),
}
test! {
name: shell_args_set_and_clear,
args: ["--shell-arg", "bar", "--clear-shell-args"],
shell_args: vec![],
shell_present: true,
shell_args: Some(vec![]),
}
test! {
name: shell_args_set_multiple_and_clear,
args: ["--shell-arg", "bar", "--shell-arg", "baz", "--clear-shell-args"],
shell_args: vec![],
shell_present: true,
shell_args: Some(vec![]),
}
test! {

View File

@ -13,6 +13,7 @@ pub(crate) enum Keyword {
Set,
Shell,
True,
WindowsPowershell,
}
impl Keyword {

View File

@ -221,7 +221,7 @@ impl<'src> Node<'src> for Set<'src> {
set.push_mut(self.name.lexeme().replace('-', "_"));
match &self.value {
DotenvLoad(value) | Export(value) | PositionalArguments(value) => {
DotenvLoad(value) | Export(value) | PositionalArguments(value) | WindowsPowerShell(value) => {
set.push_mut(value.to_string());
}
Shell(setting::Shell { command, arguments }) => {

View File

@ -747,6 +747,12 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
value: Setting::PositionalArguments(value),
name,
});
} else if Keyword::WindowsPowershell == lexeme {
let value = self.parse_set_bool()?;
return Ok(Set {
value: Setting::WindowsPowerShell(value),
name,
});
}
self.expect(ColonEquals)?;
@ -1774,6 +1780,24 @@ mod tests {
tree: (justfile (set shell "bash" "-cu" "-l")),
}
test! {
name: set_windows_powershell_implicit,
text: "set windows-powershell",
tree: (justfile (set windows_powershell true)),
}
test! {
name: set_windows_powershell_true,
text: "set windows-powershell := true",
tree: (justfile (set windows_powershell true)),
}
test! {
name: set_windows_powershell_false,
text: "set windows-powershell := false",
tree: (justfile (set windows_powershell false)),
}
test! {
name: conditional,
text: "a := if b == c { d } else { e }",

View File

@ -6,6 +6,7 @@ pub(crate) enum Setting<'src> {
Export(bool),
PositionalArguments(bool),
Shell(Shell<'src>),
WindowsPowerShell(bool),
}
#[derive(Debug, Clone, PartialEq, Serialize)]
@ -17,9 +18,10 @@ pub(crate) struct Shell<'src> {
impl<'src> Display for Setting<'src> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
match self {
Setting::DotenvLoad(value) | Setting::Export(value) | Setting::PositionalArguments(value) => {
write!(f, "{}", value)
}
Setting::DotenvLoad(value)
| Setting::Export(value)
| Setting::PositionalArguments(value)
| Setting::WindowsPowerShell(value) => write!(f, "{}", value),
Setting::Shell(shell) => write!(f, "{}", shell),
}
}

View File

@ -1,11 +1,17 @@
use crate::common::*;
pub(crate) const DEFAULT_SHELL: &str = "sh";
pub(crate) const DEFAULT_SHELL_ARGS: &[&str] = &["-cu"];
pub(crate) const WINDOWS_POWERSHELL_SHELL: &str = "powershell.exe";
pub(crate) const WINDOWS_POWERSHELL_ARGS: &[&str] = &["-NoLogo", "-Command"];
#[derive(Debug, PartialEq, Serialize)]
pub(crate) struct Settings<'src> {
pub(crate) dotenv_load: Option<bool>,
pub(crate) export: bool,
pub(crate) positional_arguments: bool,
pub(crate) shell: Option<setting::Shell<'src>>,
pub(crate) windows_powershell: bool,
}
impl<'src> Settings<'src> {
@ -15,6 +21,7 @@ impl<'src> Settings<'src> {
export: false,
positional_arguments: false,
shell: None,
windows_powershell: false,
}
}
@ -27,22 +34,166 @@ impl<'src> Settings<'src> {
}
pub(crate) fn shell_binary<'a>(&'a self, config: &'a Config) -> &'a str {
if let (Some(shell), false) = (&self.shell, config.shell_present) {
let shell_or_args_present = config.shell.is_some() || config.shell_args.is_some();
if let (Some(shell), false) = (&self.shell, shell_or_args_present) {
shell.command.cooked.as_ref()
} else if let Some(shell) = &config.shell {
shell
} else if cfg!(windows) && self.windows_powershell {
WINDOWS_POWERSHELL_SHELL
} else {
&config.shell
DEFAULT_SHELL
}
}
pub(crate) fn shell_arguments<'a>(&'a self, config: &'a Config) -> Vec<&'a str> {
if let (Some(shell), false) = (&self.shell, config.shell_present) {
let shell_or_args_present = config.shell.is_some() || config.shell_args.is_some();
if let (Some(shell), false) = (&self.shell, shell_or_args_present) {
shell
.arguments
.iter()
.map(|argument| argument.cooked.as_ref())
.collect()
} else if let Some(shell_args) = &config.shell_args {
shell_args.iter().map(String::as_ref).collect()
} else if cfg!(windows) && self.windows_powershell {
WINDOWS_POWERSHELL_ARGS.to_vec()
} else {
config.shell_args.iter().map(String::as_ref).collect()
DEFAULT_SHELL_ARGS.to_vec()
}
}
}
#[cfg(test)]
mod tests {
use crate::setting::Shell;
use super::*;
#[test]
fn default_shell() {
let settings = Settings::new();
let config = Config {
shell_command: false,
..testing::config(&[])
};
assert_eq!(settings.shell_binary(&config), "sh");
assert_eq!(settings.shell_arguments(&config), vec!["-cu"]);
}
#[test]
fn default_shell_powershell() {
let mut settings = Settings::new();
settings.windows_powershell = true;
let config = Config {
shell_command: false,
..testing::config(&[])
};
if cfg!(windows) {
assert_eq!(settings.shell_binary(&config), "powershell.exe");
assert_eq!(
settings.shell_arguments(&config),
vec!["-NoLogo", "-Command"]
);
} else {
assert_eq!(settings.shell_binary(&config), "sh");
assert_eq!(settings.shell_arguments(&config), vec!["-cu"]);
}
}
#[test]
fn overwrite_shell() {
let settings = Settings::new();
let config = Config {
shell_command: true,
shell: Some("lol".to_string()),
shell_args: Some(vec!["-nice".to_string()]),
..testing::config(&[])
};
assert_eq!(settings.shell_binary(&config), "lol");
assert_eq!(settings.shell_arguments(&config), vec!["-nice"]);
}
#[test]
fn overwrite_shell_powershell() {
let mut settings = Settings::new();
settings.windows_powershell = true;
let config = Config {
shell_command: true,
shell: Some("lol".to_string()),
shell_args: Some(vec!["-nice".to_string()]),
..testing::config(&[])
};
assert_eq!(settings.shell_binary(&config), "lol");
assert_eq!(settings.shell_arguments(&config), vec!["-nice"]);
}
#[test]
fn shell_cooked() {
let mut settings = Settings::new();
settings.shell = Some(Shell {
command: StringLiteral {
kind: StringKind::from_token_start("\"").unwrap(),
raw: "asdf.exe",
cooked: "asdf.exe".to_string(),
},
arguments: vec![StringLiteral {
kind: StringKind::from_token_start("\"").unwrap(),
raw: "-nope",
cooked: "-nope".to_string(),
}],
});
let config = Config {
shell_command: false,
..testing::config(&[])
};
assert_eq!(settings.shell_binary(&config), "asdf.exe");
assert_eq!(settings.shell_arguments(&config), vec!["-nope"]);
}
#[test]
fn shell_present_but_not_shell_args() {
let mut settings = Settings::new();
settings.windows_powershell = true;
let config = Config {
shell: Some("lol".to_string()),
..testing::config(&[])
};
assert_eq!(settings.shell_binary(&config), "lol");
}
#[test]
fn shell_args_present_but_not_shell() {
let mut settings = Settings::new();
settings.windows_powershell = true;
let config = Config {
shell_command: false,
shell_args: Some(vec!["-nice".to_string()]),
..testing::config(&[])
};
if cfg!(windows) {
assert_eq!(settings.shell_binary(&config), "powershell.exe");
assert_eq!(settings.shell_arguments(&config), vec!["-nice"]);
} else {
assert_eq!(settings.shell_binary(&config), "sh");
assert_eq!(settings.shell_arguments(&config), vec!["-nice"]);
}
}
}

View File

@ -31,7 +31,7 @@ test! {
error: The argument '--command <COMMAND>' requires a value but none was supplied
USAGE:
just{} --color <COLOR> --dump-format <FORMAT> --shell <SHELL> --shell-arg <SHELL-ARG>... \
just{} --color <COLOR> --dump-format <FORMAT> --shell <SHELL> \
<--changelog|--choose|--command <COMMAND>|--completions <SHELL>|--dump|--edit|\
--evaluate|--fmt|--init|--list|--show <RECIPE>|--summary|--variables>

View File

@ -43,6 +43,7 @@ fn alias() {
"export": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
},
"warnings": [],
}),
@ -69,6 +70,7 @@ fn assignment() {
"export": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
},
"warnings": [],
}),
@ -108,6 +110,7 @@ fn body() {
"export": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
},
"warnings": [],
}),
@ -157,6 +160,7 @@ fn dependencies() {
"export": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
},
"warnings": [],
}),
@ -243,6 +247,7 @@ fn dependency_argument() {
"export": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
},
"warnings": [],
}),
@ -275,6 +280,7 @@ fn doc_comment() {
"export": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
},
"warnings": [],
}),
@ -295,6 +301,7 @@ fn empty_justfile() {
"export": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
},
"warnings": [],
}),
@ -424,6 +431,7 @@ fn parameters() {
"export": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
},
"warnings": [],
}),
@ -492,6 +500,7 @@ fn priors() {
"export": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
},
"warnings": [],
}),
@ -524,6 +533,7 @@ fn private() {
"export": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
},
"warnings": [],
}),
@ -556,6 +566,7 @@ fn quiet() {
"export": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
},
"warnings": [],
}),
@ -609,6 +620,7 @@ fn settings() {
"arguments": ["b", "c"],
"command": "a",
},
"windows_powershell": false,
},
"warnings": [],
}),
@ -644,6 +656,7 @@ fn shebang() {
"export": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
},
"warnings": [],
}),
@ -676,6 +689,7 @@ fn simple() {
"export": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
},
"warnings": [],
}),

View File

@ -40,4 +40,6 @@ mod sublime_syntax;
mod subsequents;
mod tempdir;
mod undefined_variables;
#[cfg(target_family = "windows")]
mod windows_powershell;
mod working_directory;

View File

@ -0,0 +1,18 @@
use crate::common::*;
#[test]
fn windows_poweshell_setting_uses_powershell() {
Test::new()
.justfile(
r#"
set windows-powershell
foo:
Write-Output bar
"#,
)
.shell(false)
.stdout("bar\r\n")
.stderr("Write-Output bar\n")
.run();
}