• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::fmt::Display;
2 use std::path::Path;
3 use std::str::FromStr;
4 
5 use clap::builder::PossibleValue;
6 use clap::ValueEnum;
7 
8 use crate::shells;
9 use crate::Generator;
10 
11 /// Shell with auto-generated completion script available.
12 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
13 #[non_exhaustive]
14 pub 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 
27 impl Display for Shell {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result28     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 
36 impl FromStr for Shell {
37     type Err = String;
38 
from_str(s: &str) -> Result<Self, Self::Err>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
50 impl ValueEnum for Shell {
value_variants<'a>() -> &'a [Self]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 
to_possible_value<'a>(&self) -> Option<PossibleValue>61     fn to_possible_value<'a>(&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 
72 impl Generator for Shell {
file_name(&self, name: &str) -> String73     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 
generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write)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 
94 impl 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     /// ```
from_shell_path<P: AsRef<Path>>(path: P) -> Option<Shell>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 OSes 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     /// ```
from_env() -> Option<Shell>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
parse_shell_from_path(path: &Path) -> Option<Shell>145 fn 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 }
156