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("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 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 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("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 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 = 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