clap_complete/aot/shells/
shell.rs

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