1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
use std::fmt::Display;
use std::path::Path;
use std::str::FromStr;
use clap::builder::PossibleValue;
use clap::ValueEnum;
use crate::shells;
use crate::Generator;
/// Shell with auto-generated completion script available.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum Shell {
/// Bourne Again `SHell` (bash)
Bash,
/// Elvish shell
Elvish,
/// Friendly Interactive `SHell` (fish)
Fish,
/// `PowerShell`
PowerShell,
/// Z `SHell` (zsh)
Zsh,
}
impl Display for Shell {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_possible_value()
.expect("no values are skipped")
.get_name()
.fmt(f)
}
}
impl FromStr for Shell {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
for variant in Self::value_variants() {
if variant.to_possible_value().unwrap().matches(s, false) {
return Ok(*variant);
}
}
Err(format!("invalid variant: {s}"))
}
}
// Hand-rolled so it can work even when `derive` feature is disabled
impl ValueEnum for Shell {
fn value_variants<'a>() -> &'a [Self] {
&[
Shell::Bash,
Shell::Elvish,
Shell::Fish,
Shell::PowerShell,
Shell::Zsh,
]
}
fn to_possible_value(&self) -> Option<PossibleValue> {
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"),
})
}
}
impl Generator for Shell {
fn file_name(&self, name: &str) -> String {
match self {
Shell::Bash => shells::Bash.file_name(name),
Shell::Elvish => shells::Elvish.file_name(name),
Shell::Fish => shells::Fish.file_name(name),
Shell::PowerShell => shells::PowerShell.file_name(name),
Shell::Zsh => shells::Zsh.file_name(name),
}
}
fn generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write) {
match self {
Shell::Bash => shells::Bash.generate(cmd, buf),
Shell::Elvish => shells::Elvish.generate(cmd, buf),
Shell::Fish => shells::Fish.generate(cmd, buf),
Shell::PowerShell => shells::PowerShell.generate(cmd, buf),
Shell::Zsh => shells::Zsh.generate(cmd, buf),
}
}
}
impl Shell {
/// Parse a shell from a path to the executable for the shell
///
/// # Examples
///
/// ```
/// use clap_complete::shells::Shell;
///
/// assert_eq!(Shell::from_shell_path("/bin/bash"), Some(Shell::Bash));
/// assert_eq!(Shell::from_shell_path("/usr/bin/zsh"), Some(Shell::Zsh));
/// assert_eq!(Shell::from_shell_path("/opt/my_custom_shell"), None);
/// ```
pub fn from_shell_path<P: AsRef<Path>>(path: P) -> Option<Shell> {
parse_shell_from_path(path.as_ref())
}
/// Determine the user's current shell from the environment
///
/// This will read the SHELL environment variable and try to determine which shell is in use
/// from that.
///
/// If SHELL is not set, then on windows, it will default to powershell, and on
/// other operating systems it will return `None`.
///
/// If SHELL is set, but contains a value that doesn't correspond to one of the supported shell
/// types, then return `None`.
///
/// # Example:
///
/// ```no_run
/// # use clap::Command;
/// use clap_complete::{generate, shells::Shell};
/// # fn build_cli() -> Command {
/// # Command::new("compl")
/// # }
/// let mut cmd = build_cli();
/// generate(Shell::from_env().unwrap_or(Shell::Bash), &mut cmd, "myapp", &mut std::io::stdout());
/// ```
pub fn from_env() -> Option<Shell> {
if let Some(env_shell) = std::env::var_os("SHELL") {
Shell::from_shell_path(env_shell)
} else if cfg!(windows) {
Some(Shell::PowerShell)
} else {
None
}
}
}
// use a separate function to avoid having to monomorphize the entire function due
// to from_shell_path being generic
fn parse_shell_from_path(path: &Path) -> Option<Shell> {
let name = path.file_stem()?.to_str()?;
match name {
"bash" => Some(Shell::Bash),
"zsh" => Some(Shell::Zsh),
"fish" => Some(Shell::Fish),
"elvish" => Some(Shell::Elvish),
"powershell" | "powershell_ise" => Some(Shell::PowerShell),
_ => None,
}
}