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(msg:"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: &Shell in Self::value_variants() {
41 if variant.to_possible_value().unwrap().matches(value:s, ignore_case: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<'a>(&self) -> Option<PossibleValue> {
62 Some(match self {
63 Shell::Bash => PossibleValue::new(name:"bash"),
64 Shell::Elvish => PossibleValue::new(name:"elvish"),
65 Shell::Fish => PossibleValue::new(name:"fish"),
66 Shell::PowerShell => PossibleValue::new(name:"powershell"),
67 Shell::Zsh => PossibleValue::new(name:"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 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 /// ```
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: &str = 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