From 184ac9d463f371671e6c5d9eac99b552a2b75072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Sat, 20 Apr 2024 22:15:56 +1200 Subject: [PATCH] Breaking changes for --shell --- crates/cli/src/args.rs | 25 ++++---------- crates/cli/src/config.rs | 71 +++++++++++++++++++++++++--------------- 2 files changed, 51 insertions(+), 45 deletions(-) diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index a11b971..143996d 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -434,18 +434,18 @@ pub struct Args { /// Use a different shell /// - /// By default, Watchexec will use 'sh' on unix and 'cmd' (CMD.EXE) on Windows. With this, you - /// can override that and use a different shell, for example one with more features or one which - /// has your custom aliases and functions. + /// By default, Watchexec will use '$SHELL' if it's defined or a default of 'sh' on Unix-likes, + /// and either 'pwsh', 'powershell', or 'cmd' (CMD.EXE) on Windows, depending on what Watchexec + /// detects is the running shell. + /// + /// With this option, you can override that and use a different shell, for example one with more + /// features or one which has your custom aliases and functions. /// /// If the value has spaces, it is parsed as a command line, and the first word used as the /// shell program, with the rest as arguments to the shell. /// /// The command is run with the '-c' flag (except for 'cmd' on Windows, where it's '/C'). /// - /// Note that the default shell will change at the next major release: the value of '$SHELL' - /// will be respected, falling back to 'sh' on unix and to PowerShell on Windows. - /// /// The special value 'none' can be used to disable shell use entirely. In that case, the /// command provided to Watchexec will be parsed, with the first word being the executable and /// the rest being the arguments, and executed directly. Note that this parsing is rudimentary, @@ -465,7 +465,7 @@ pub struct Args { /// /// $ watchexec --shell=pwsh -- Test-Connection localhost /// - /// Use with cmd (default on Windows): + /// Use with CMD.exe: /// /// $ watchexec --shell=cmd -- dir /// @@ -492,17 +492,6 @@ pub struct Args { )] pub no_shell: bool, - /// Don't use a shell - /// - /// This is a deprecated alias for '--shell=none'. - #[arg( - long, - hide = true, - help_heading = OPTSET_COMMAND, - alias = "no-shell", // deprecated - )] - pub no_shell_long: bool, - /// Shorthand for '--emit-events=none' /// /// This is the old way to disable event emission into the environment. See '--emit-events' for diff --git a/crates/cli/src/config.rs b/crates/cli/src/config.rs index 87aaf15..a018b53 100644 --- a/crates/cli/src/config.rs +++ b/crates/cli/src/config.rs @@ -1,7 +1,7 @@ use std::{ borrow::Cow, collections::HashMap, - env::current_dir, + env::{current_dir, var}, ffi::{OsStr, OsString}, fs::File, io::{IsTerminal, Write}, @@ -469,35 +469,52 @@ fn interpret_command_args(args: &Args) -> Result> { panic!("(clap) Bug: command is not present"); } - let shell = match if args.no_shell || args.no_shell_long { + let shell = if args.no_shell { None } else { - args.shell.as_deref().or(Some("default")) - } { - Some("") => return Err(RuntimeError::CommandShellEmptyShell).into_diagnostic(), - - Some("none") | None => None, - - #[cfg(windows)] - Some("default") | Some("cmd") | Some("cmd.exe") | Some("CMD") | Some("CMD.EXE") => { - Some(Shell::cmd()) - } - - #[cfg(not(windows))] - Some("default") => Some(Shell::new("sh")), - - Some(other) => { - let sh = other.split_ascii_whitespace().collect::>(); - - // UNWRAP: checked by Some("") - #[allow(clippy::unwrap_used)] - let (shprog, shopts) = sh.split_first().unwrap(); - - Some(Shell { - prog: shprog.into(), - options: shopts.iter().map(|s| (*s).to_string()).collect(), - program_option: Some(Cow::Borrowed(OsStr::new("-c"))), + let shell = args.shell.clone().or_else(|| var("SHELL").ok()); + match shell + .as_deref() + .or_else(|| { + if cfg!(not(windows)) { + Some("sh") + } else if var("POWERSHELL_DISTRIBUTION_CHANNEL").is_ok() + && (which::which("pwsh").is_ok() || which::which("pwsh.exe").is_ok()) + { + trace!("detected pwsh"); + Some("pwsh") + } else if var("PSModulePath").is_ok() + && (which::which("powershell").is_ok() + || which::which("powershell.exe").is_ok()) + { + trace!("detected powershell"); + Some("powershell") + } else { + Some("cmd") + } }) + .or(Some("default")) + { + Some("") => return Err(RuntimeError::CommandShellEmptyShell).into_diagnostic(), + + Some("none") | None => None, + + #[cfg(windows)] + Some("cmd") | Some("cmd.exe") | Some("CMD") | Some("CMD.EXE") => Some(Shell::cmd()), + + Some(other) => { + let sh = other.split_ascii_whitespace().collect::>(); + + // UNWRAP: checked by Some("") + #[allow(clippy::unwrap_used)] + let (shprog, shopts) = sh.split_first().unwrap(); + + Some(Shell { + prog: shprog.into(), + options: shopts.iter().map(|s| (*s).to_string()).collect(), + program_option: Some(Cow::Borrowed(OsStr::new("-c"))), + }) + } } };