diff --git a/clap_complete/src/shells/shell.rs b/clap_complete/src/shells/shell.rs index 52cb2e9b741..3164fdf5e59 100644 --- a/clap_complete/src/shells/shell.rs +++ b/clap_complete/src/shells/shell.rs @@ -2,6 +2,7 @@ use std::fmt::Display; use std::path::Path; use std::str::FromStr; +use clap::__macro_refs::once_cell::sync::Lazy; use clap::builder::PossibleValue; use clap::ValueEnum; @@ -46,6 +47,16 @@ impl FromStr for Shell { } } +fn shell_possible_value(name: &'static str, paths: &'static [String]) -> PossibleValue { + let mut pv = PossibleValue::new(name); + + for path in paths { + pv = pv.alias(path.as_str()); + } + + pv +} + // Hand-rolled so it can work even when `derive` feature is disabled impl ValueEnum for Shell { fn value_variants<'a>() -> &'a [Self] { @@ -59,12 +70,14 @@ impl ValueEnum for Shell { } fn to_possible_value<'a>(&self) -> Option { + static SHELL_PATHS: Lazy = Lazy::new(ShellPaths::new); + Some(match self { - Shell::Bash => PossibleValue::new("bash"), - Shell::Elvish => PossibleValue::new("elvish"), - Shell::Fish => PossibleValue::new("fish"), - Shell::PowerShell => PossibleValue::new("powershell"), - Shell::Zsh => PossibleValue::new("zsh"), + Shell::Bash => shell_possible_value("bash", &SHELL_PATHS.bash), + Shell::Elvish => shell_possible_value("elvish", &SHELL_PATHS.elvish), + Shell::Fish => shell_possible_value("fish", &SHELL_PATHS.fish), + Shell::PowerShell => shell_possible_value("powershell", &SHELL_PATHS.powershell), + Shell::Zsh => shell_possible_value("zsh", &SHELL_PATHS.zsh), }) } } @@ -153,3 +166,51 @@ fn parse_shell_from_path(path: &Path) -> Option { _ => None, } } + +#[derive(Default)] +struct ShellPaths { + bash: Vec, + elvish: Vec, + fish: Vec, + powershell: Vec, + zsh: Vec, +} + +impl ShellPaths { + fn new() -> Self { + #[cfg(windows)] + const SEPARATOR: char = ';'; + #[cfg(not(windows))] + const SEPARATOR: char = ':'; + + let mut this = Self::default(); + + let path_var = match std::env::var("PATH") { + Ok(path_var) => path_var, + Err(_) => return this, + }; + + for base in path_var.split(SEPARATOR).map(Path::new) { + add_shell_path(&mut this.bash, base, "bash"); + add_shell_path(&mut this.elvish, base, "elvish"); + add_shell_path(&mut this.fish, base, "fish"); + add_shell_path(&mut this.powershell, base, "powershell"); + add_shell_path(&mut this.zsh, base, "zsh"); + } + + this + } +} + +fn add_shell_path(shell_paths: &mut Vec, base: &Path, name: &str) { + let path = base.join(name); + + #[cfg(windows)] + let path = &path.with_extension("exe"); + + if path.exists() { + if let Some(path) = path.to_str() { + shell_paths.push(path.to_string()); + } + } +} diff --git a/clap_complete/tests/general.rs b/clap_complete/tests/general.rs index fd52cb81f9c..3a0f1789f7a 100644 --- a/clap_complete/tests/general.rs +++ b/clap_complete/tests/general.rs @@ -9,3 +9,32 @@ fn infer_value_hint_for_path_buf() { .unwrap(); assert_eq!(input.get_value_hint(), clap::builder::ValueHint::AnyPath); } + +#[cfg(windows)] +#[test] +fn shell_absolute_path() { + use clap_complete::Shell; + + let path = std::path::Path::new(&std::env::var("PSHOME").unwrap()).join("powershell.exe"); + + let matches = clap::Command::new("test") + .arg(clap::Arg::new("shell").value_parser(clap::value_parser!(Shell))) + .get_matches_from(["myprog", path.to_str().unwrap()]); + + assert_eq!( + matches.get_one::("shell").unwrap(), + &Shell::PowerShell + ); +} + +#[cfg(not(windows))] +#[test] +fn shell_absolute_path() { + use clap_complete::Shell; + + let matches = clap::Command::new("test") + .arg(clap::Arg::new("shell").value_parser(clap::value_parser!(Shell))) + .get_matches_from(["myprog", "/bin/bash"]); + + assert_eq!(matches.get_one::("shell").unwrap(), &Shell::Bash); +}