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 { |
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 | |
36 | impl 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 |
50 | impl 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 | |
72 | impl 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 | |
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 | /// ``` |
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 |
145 | fn 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 | |