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