summaryrefslogtreecommitdiff
path: root/src/shells/shell.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/shells/shell.rs')
-rw-r--r--src/shells/shell.rs155
1 files changed, 155 insertions, 0 deletions
diff --git a/src/shells/shell.rs b/src/shells/shell.rs
new file mode 100644
index 0000000..52cb2e9
--- /dev/null
+++ b/src/shells/shell.rs
@@ -0,0 +1,155 @@
+use std::fmt::Display;
+use std::path::Path;
+use std::str::FromStr;
+
+use clap::builder::PossibleValue;
+use clap::ValueEnum;
+
+use crate::shells;
+use crate::Generator;
+
+/// Shell with auto-generated completion script available.
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+#[non_exhaustive]
+pub enum Shell {
+ /// Bourne Again SHell (bash)
+ Bash,
+ /// Elvish shell
+ Elvish,
+ /// Friendly Interactive SHell (fish)
+ Fish,
+ /// PowerShell
+ PowerShell,
+ /// Z SHell (zsh)
+ Zsh,
+}
+
+impl Display for Shell {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.to_possible_value()
+ .expect("no values are skipped")
+ .get_name()
+ .fmt(f)
+ }
+}
+
+impl FromStr for Shell {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ for variant in Self::value_variants() {
+ if variant.to_possible_value().unwrap().matches(s, false) {
+ return Ok(*variant);
+ }
+ }
+ Err(format!("invalid variant: {s}"))
+ }
+}
+
+// Hand-rolled so it can work even when `derive` feature is disabled
+impl ValueEnum for Shell {
+ fn value_variants<'a>() -> &'a [Self] {
+ &[
+ Shell::Bash,
+ Shell::Elvish,
+ Shell::Fish,
+ Shell::PowerShell,
+ Shell::Zsh,
+ ]
+ }
+
+ fn to_possible_value<'a>(&self) -> Option<PossibleValue> {
+ Some(match self {
+ Shell::Bash => PossibleValue::new("bash"),
+ Shell::Elvish => PossibleValue::new("elvish"),
+ Shell::Fish => PossibleValue::new("fish"),
+ Shell::PowerShell => PossibleValue::new("powershell"),
+ Shell::Zsh => PossibleValue::new("zsh"),
+ })
+ }
+}
+
+impl Generator for Shell {
+ fn file_name(&self, name: &str) -> String {
+ match self {
+ Shell::Bash => shells::Bash.file_name(name),
+ Shell::Elvish => shells::Elvish.file_name(name),
+ Shell::Fish => shells::Fish.file_name(name),
+ Shell::PowerShell => shells::PowerShell.file_name(name),
+ Shell::Zsh => shells::Zsh.file_name(name),
+ }
+ }
+
+ fn generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write) {
+ match self {
+ Shell::Bash => shells::Bash.generate(cmd, buf),
+ Shell::Elvish => shells::Elvish.generate(cmd, buf),
+ Shell::Fish => shells::Fish.generate(cmd, buf),
+ Shell::PowerShell => shells::PowerShell.generate(cmd, buf),
+ Shell::Zsh => shells::Zsh.generate(cmd, buf),
+ }
+ }
+}
+
+impl Shell {
+ /// Parse a shell from a path to the executable for the shell
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use clap_complete::shells::Shell;
+ ///
+ /// assert_eq!(Shell::from_shell_path("/bin/bash"), Some(Shell::Bash));
+ /// assert_eq!(Shell::from_shell_path("/usr/bin/zsh"), Some(Shell::Zsh));
+ /// assert_eq!(Shell::from_shell_path("/opt/my_custom_shell"), None);
+ /// ```
+ pub fn from_shell_path<P: AsRef<Path>>(path: P) -> Option<Shell> {
+ parse_shell_from_path(path.as_ref())
+ }
+
+ /// Determine the user's current shell from the environment
+ ///
+ /// This will read the SHELL environment variable and try to determine which shell is in use
+ /// from that.
+ ///
+ /// If SHELL is not set, then on windows, it will default to powershell, and on
+ /// other OSes it will return `None`.
+ ///
+ /// If SHELL is set, but contains a value that doesn't correspond to one of the supported shell
+ /// types, then return `None`.
+ ///
+ /// # Example:
+ ///
+ /// ```no_run
+ /// # use clap::Command;
+ /// use clap_complete::{generate, shells::Shell};
+ /// # fn build_cli() -> Command {
+ /// # Command::new("compl")
+ /// # }
+ /// let mut cmd = build_cli();
+ /// generate(Shell::from_env().unwrap_or(Shell::Bash), &mut cmd, "myapp", &mut std::io::stdout());
+ /// ```
+ pub fn from_env() -> Option<Shell> {
+ if let Some(env_shell) = std::env::var_os("SHELL") {
+ Shell::from_shell_path(env_shell)
+ } else if cfg!(windows) {
+ Some(Shell::PowerShell)
+ } else {
+ None
+ }
+ }
+}
+
+// use a separate function to avoid having to monomorphize the entire function due
+// to from_shell_path being generic
+fn parse_shell_from_path(path: &Path) -> Option<Shell> {
+ let name = path.file_stem()?.to_str()?;
+ match name {
+ "bash" => Some(Shell::Bash),
+ "zsh" => Some(Shell::Zsh),
+ "fish" => Some(Shell::Fish),
+ "elvish" => Some(Shell::Elvish),
+ "powershell" | "powershell_ise" => Some(Shell::PowerShell),
+ _ => None,
+ }
+}