clap_complete/aot/shells/
shell.rs

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