diff --git a/clap_complete/src/dynamic/candidate.rs b/clap_complete/src/dynamic/candidate.rs index 517e2879..8123964d 100644 --- a/clap_complete/src/dynamic/candidate.rs +++ b/clap_complete/src/dynamic/candidate.rs @@ -4,9 +4,6 @@ use std::ffi::OsString; use clap::builder::StyledStr; /// A shell-agnostic completion candidate -/// -/// [`Shell`][crate::dynamic::shells::Shell] will adapt what it can to the -/// current shell. #[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct CompletionCandidate { content: OsString, diff --git a/clap_complete/src/dynamic/command/mod.rs b/clap_complete/src/dynamic/command/mod.rs new file mode 100644 index 00000000..799d56b6 --- /dev/null +++ b/clap_complete/src/dynamic/command/mod.rs @@ -0,0 +1,230 @@ +//! [` complete`][CompleteCommand] completion integration +//! +//! - If you aren't using a subcommand, see [`CompleteCommand`] +//! - If you are using subcommands, see [`CompleteArgs`] +//! +//! To source your completions: +//! +//! Bash +//! ```bash +//! echo "source <(your_program complete bash)" >> ~/.bashrc +//! ``` +//! +//! Elvish +//! ```elvish +//! echo "eval (your_program complete elvish)" >> ~/.elvish/rc.elv +//! ``` +//! +//! Fish +//! ```fish +//! echo "source (your_program complete fish | psub)" >> ~/.config/fish/config.fish +//! ``` +//! +//! Powershell +//! ```powershell +//! echo "your_program complete powershell | Invoke-Expression" >> $PROFILE +//! ``` +//! +//! Zsh +//! ```zsh +//! echo "source <(your_program complete zsh)" >> ~/.zshrc +//! ``` + +mod shells; + +use std::ffi::OsString; +use std::io::Write as _; + +pub use shells::*; + +/// A completion subcommand to add to your CLI +/// +/// **Warning:** `stdout` should not be written to before [`CompleteCommand::complete`] has had a +/// chance to run. +/// +/// # Examples +/// +/// To integrate completions into an application without subcommands: +/// ```no_run +/// // src/main.rs +/// use clap::{CommandFactory, FromArgMatches, Parser, Subcommand}; +/// use clap_complete::dynamic::CompleteCommand; +/// +/// #[derive(Parser, Debug)] +/// #[clap(name = "dynamic", about = "A dynamic command line tool")] +/// struct Cli { +/// /// The subcommand to run complete +/// #[command(subcommand)] +/// complete: Option, +/// +/// /// Input file path +/// #[clap(short, long, value_hint = clap::ValueHint::FilePath)] +/// input: Option, +/// /// Output format +/// #[clap(short = 'F', long, value_parser = ["json", "yaml", "toml"])] +/// format: Option, +/// } +/// +/// fn main() { +/// let cli = Cli::parse(); +/// if let Some(completions) = cli.complete { +/// completions.complete(&mut Cli::command()); +/// } +/// +/// // normal logic continues... +/// } +///``` +#[derive(clap::Subcommand)] +#[allow(missing_docs)] +#[derive(Clone, Debug)] +#[command(about = None, long_about = None)] +pub enum CompleteCommand { + /// Register shell completions for this program + #[command(hide = true)] + Complete(CompleteArgs), +} + +impl CompleteCommand { + /// Process the completion request and exit + /// + /// **Warning:** `stdout` should not be written to before this has had a + /// chance to run. + pub fn complete(&self, cmd: &mut clap::Command) -> std::convert::Infallible { + self.try_complete(cmd).unwrap_or_else(|e| e.exit()); + std::process::exit(0) + } + + /// Process the completion request + /// + /// **Warning:** `stdout` should not be written to before or after this has run. + pub fn try_complete(&self, cmd: &mut clap::Command) -> clap::error::Result<()> { + debug!("CompleteCommand::try_complete: {self:?}"); + let CompleteCommand::Complete(args) = self; + args.try_complete(cmd) + } +} + +/// A completion subcommand to add to your CLI +/// +/// **Warning:** `stdout` should not be written to before [`CompleteArgs::complete`] has had a +/// chance to run. +/// +/// # Examples +/// +/// To integrate completions into an application without subcommands: +/// ```no_run +/// // src/main.rs +/// use clap::{CommandFactory, FromArgMatches, Parser, Subcommand}; +/// use clap_complete::dynamic::CompleteArgs; +/// +/// #[derive(Parser, Debug)] +/// #[clap(name = "dynamic", about = "A dynamic command line tool")] +/// struct Cli { +/// #[command(subcommand)] +/// complete: Command, +/// } +/// +/// #[derive(Subcommand, Debug)] +/// enum Command { +/// Complete(CompleteArgs), +/// Print, +/// } +/// +/// fn main() { +/// let cli = Cli::parse(); +/// match cli.complete { +/// Command::Complete(completions) => { +/// completions.complete(&mut Cli::command()); +/// }, +/// Command::Print => { +/// println!("Hello world!"); +/// } +/// } +/// } +///``` +#[derive(clap::Args, Clone, Debug)] +#[command(about = None, long_about = None)] +pub struct CompleteArgs { + /// Specify shell to complete for + #[arg(value_name = "NAME")] + shell: Option, + + #[arg(raw = true, value_name = "ARG", hide = true)] + comp_words: Option>, +} + +impl CompleteArgs { + /// Process the completion request and exit + /// + /// **Warning:** `stdout` should not be written to before this has had a + /// chance to run. + pub fn complete(&self, cmd: &mut clap::Command) -> std::convert::Infallible { + self.try_complete(cmd).unwrap_or_else(|e| e.exit()); + std::process::exit(0) + } + + /// Process the completion request + /// + /// **Warning:** `stdout` should not be written to before or after this has run. + pub fn try_complete(&self, cmd: &mut clap::Command) -> clap::error::Result<()> { + debug!("CompleteCommand::try_complete: {self:?}"); + + let shell = self.shell.or_else(Shell::from_env).ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::Other, + "unknown shell, please specify the name of your shell", + ) + })?; + + if let Some(comp_words) = self.comp_words.as_ref() { + let current_dir = std::env::current_dir().ok(); + + let mut buf = Vec::new(); + shell.write_complete(cmd, comp_words.clone(), current_dir.as_deref(), &mut buf)?; + std::io::stdout().write_all(&buf)?; + } else { + let name = cmd.get_name(); + let bin = cmd.get_bin_name().unwrap_or_else(|| cmd.get_name()); + + let mut buf = Vec::new(); + shell.write_registration(name, bin, bin, &mut buf)?; + std::io::stdout().write_all(&buf)?; + } + + Ok(()) + } +} + +/// Shell-integration for completions +/// +/// This will generally be called by [`CompleteCommand`] or [`CompleteArgs`]. +/// +/// This handles adapting between the shell and [`completer`][crate::dynamic::complete()]. +/// A `CommandCompleter` can choose how much of that lives within the registration script and or +/// lives in [`CommandCompleter::write_complete`]. +pub trait CommandCompleter { + /// Register for completions + /// + /// Write the `buf` the logic needed for calling into ` complete`, passing needed + /// arguments to [`CommandCompleter::write_complete`] through the environment. + fn write_registration( + &self, + name: &str, + bin: &str, + completer: &str, + buf: &mut dyn std::io::Write, + ) -> Result<(), std::io::Error>; + /// Complete the given command + /// + /// Adapt information from arguments and [`CommandCompleter::write_registration`]-defined env + /// variables to what is needed for [`completer`][crate::dynamic::complete()]. + /// + /// Write out the [`CompletionCandidate`][crate::dynamic::CompletionCandidate]s in a way the shell will understand. + fn write_complete( + &self, + cmd: &mut clap::Command, + args: Vec, + current_dir: Option<&std::path::Path>, + buf: &mut dyn std::io::Write, + ) -> Result<(), std::io::Error>; +} diff --git a/clap_complete/src/dynamic/shells/command.rs b/clap_complete/src/dynamic/command/shells.rs similarity index 56% rename from clap_complete/src/dynamic/shells/command.rs rename to clap_complete/src/dynamic/command/shells.rs index 05664586..c7ce3ffd 100644 --- a/clap_complete/src/dynamic/shells/command.rs +++ b/clap_complete/src/dynamic/command/shells.rs @@ -1,264 +1,84 @@ use std::ffi::OsString; -use std::io::Write as _; +use std::fmt::Display; +use std::str::FromStr; +use clap::builder::PossibleValue; +use clap::ValueEnum; use unicode_xid::UnicodeXID as _; -use super::Shell; +use super::CommandCompleter; -/// A completion subcommand to add to your CLI -/// -/// If you aren't using a subcommand, you can annotate a field with this type as `#[command(subcommand)]`. -/// -/// If you are using subcommands, see [`CompleteArgs`]. -/// -/// **Warning:** `stdout` should not be written to before [`CompleteCommand::complete`] has had a -/// chance to run. -/// -/// # Examples -/// -/// To integrate completions into an application without subcommands: -/// ```no_run -/// // src/main.rs -/// use clap::{CommandFactory, FromArgMatches, Parser, Subcommand}; -/// use clap_complete::dynamic::CompleteCommand; -/// -/// #[derive(Parser, Debug)] -/// #[clap(name = "dynamic", about = "A dynamic command line tool")] -/// struct Cli { -/// /// The subcommand to run complete -/// #[command(subcommand)] -/// complete: Option, -/// /// Input file path -/// #[clap(short, long, value_hint = clap::ValueHint::FilePath)] -/// input: Option, -/// /// Output format -/// #[clap(short = 'F', long, value_parser = ["json", "yaml", "toml"])] -/// format: Option, -/// } -/// -/// fn main() { -/// let cli = Cli::parse(); -/// if let Some(completions) = cli.complete { -/// completions.complete(&mut Cli::command()); -/// } -/// -/// // normal logic continues... -/// } -///``` -/// -/// To source your completions: -/// -/// Bash -/// ```bash -/// echo "source <(your_program complete bash --register -)" >> ~/.bashrc -/// ``` -/// -/// Elvish -/// ```elvish -/// echo "eval (your_program complete elvish --register -)" >> ~/.elvish/rc.elv -/// ``` -/// -/// Fish -/// ```fish -/// echo "source (your_program complete fish --register - | psub)" >> ~/.config/fish/config.fish -/// ``` -/// -/// Powershell -/// ```powershell -/// echo "your_program complete powershell --register - | Invoke-Expression" >> $PROFILE -/// ``` -/// -/// Zsh -/// ```zsh -/// echo "source <(your_program complete zsh --register -)" >> ~/.zshrc -/// ``` -#[derive(clap::Subcommand)] -#[allow(missing_docs)] -#[derive(Clone, Debug)] -#[command(about = None, long_about = None)] -#[cfg(feature = "unstable-command")] -pub enum CompleteCommand { - /// Register shell completions for this program - #[command(hide = true)] - Complete(CompleteArgs), +/// Completion support for built-in shells +#[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 CompleteCommand { - /// Process the completion request and exit +impl Shell { + /// Parse a shell from a path to the executable for the shell /// - /// **Warning:** `stdout` should not be written to before this has had a - /// chance to run. - pub fn complete(&self, cmd: &mut clap::Command) -> std::convert::Infallible { - self.try_complete(cmd).unwrap_or_else(|e| e.exit()); - std::process::exit(0) + /// # 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(path: impl AsRef) -> Option { + parse_shell_from_path(path.as_ref()) } - /// Process the completion request + /// Determine the user's current shell from the environment /// - /// **Warning:** `stdout` should not be written to before or after this has run. - pub fn try_complete(&self, cmd: &mut clap::Command) -> clap::error::Result<()> { - debug!("CompleteCommand::try_complete: {self:?}"); - let CompleteCommand::Complete(args) = self; - args.try_complete(cmd) - } -} - -/// A completion subcommand to add to your CLI -/// -/// If you are using subcommands, add a `Complete(CompleteArgs)` variant. -/// -/// If you aren't using subcommands, generally you will want [`CompleteCommand`]. -/// -/// **Warning:** `stdout` should not be written to before [`CompleteArgs::complete`] has had a -/// chance to run. -/// -/// # Examples -/// -/// To integrate completions into an application without subcommands: -/// ```no_run -/// // src/main.rs -/// use clap::{CommandFactory, FromArgMatches, Parser, Subcommand}; -/// use clap_complete::dynamic::CompleteArgs; -/// -/// #[derive(Parser, Debug)] -/// #[clap(name = "dynamic", about = "A dynamic command line tool")] -/// struct Cli { -/// #[command(subcommand)] -/// complete: Command, -/// } -/// -/// #[derive(Subcommand, Debug)] -/// enum Command { -/// Complete(CompleteArgs), -/// Print, -/// } -/// -/// fn main() { -/// let cli = Cli::parse(); -/// match cli.complete { -/// Command::Complete(completions) => { -/// completions.complete(&mut Cli::command()); -/// }, -/// Command::Print => { -/// println!("Hello world!"); -/// } -/// } -/// } -///``` -/// -/// To source your completions: -/// -/// Bash -/// ```bash -/// echo "source <(your_program complete bash)" >> ~/.bashrc -/// ``` -/// -/// Elvish -/// ```elvish -/// echo "eval (your_program complete elvish)" >> ~/.elvish/rc.elv -/// ``` -/// -/// Fish -/// ```fish -/// echo "source (your_program complete fish | psub)" >> ~/.config/fish/config.fish -/// ``` -/// -/// Powershell -/// ```powershell -/// echo "your_program complete powershell | Invoke-Expression" >> $PROFILE -/// ``` -/// -/// Zsh -/// ```zsh -/// echo "source <(your_program complete zsh)" >> ~/.zshrc -/// ``` -#[derive(clap::Args, Clone, Debug)] -#[command(about = None, long_about = None)] -#[cfg(feature = "unstable-command")] -pub struct CompleteArgs { - /// Specify shell to complete for - #[arg(value_name = "NAME")] - shell: Option, - - #[arg(raw = true, value_name = "ARG", hide = true)] - comp_words: Option>, -} - -impl CompleteArgs { - /// Process the completion request and exit + /// This will read the SHELL environment variable and try to determine which shell is in use + /// from that. /// - /// **Warning:** `stdout` should not be written to before this has had a - /// chance to run. - pub fn complete(&self, cmd: &mut clap::Command) -> std::convert::Infallible { - self.try_complete(cmd).unwrap_or_else(|e| e.exit()); - std::process::exit(0) - } - - /// Process the completion request + /// If SHELL is not set, then on windows, it will default to powershell, and on + /// other operating systems it will return `None`. /// - /// **Warning:** `stdout` should not be written to before or after this has run. - pub fn try_complete(&self, cmd: &mut clap::Command) -> clap::error::Result<()> { - debug!("CompleteCommand::try_complete: {self:?}"); - - let shell = self.shell.or_else(|| Shell::from_env()).ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::Other, - "unknown shell, please specify the name of your shell", - ) - })?; - - if let Some(comp_words) = self.comp_words.as_ref() { - let current_dir = std::env::current_dir().ok(); - - let mut buf = Vec::new(); - shell.write_complete(cmd, comp_words.clone(), current_dir.as_deref(), &mut buf)?; - std::io::stdout().write_all(&buf)?; + /// 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 shell = Shell::from_env(); + /// println!("{shell:?}"); + /// ``` + pub fn from_env() -> Option { + if let Some(env_shell) = std::env::var_os("SHELL") { + Shell::from_shell_path(env_shell) } else { - let name = cmd.get_name(); - let bin = cmd.get_bin_name().unwrap_or_else(|| cmd.get_name()); - - let mut buf = Vec::new(); - shell.write_registration(name, bin, bin, &mut buf)?; - std::io::stdout().write_all(&buf)?; + None } - - Ok(()) } -} -/// Shell-integration for completions -/// -/// This will generally be called by [`CompleteCommand`] or [`CompleteArgs`]. -/// -/// This handles adapting between the shell and [`completer`][crate::dynamic::complete()]. -/// A `CommandCompleter` can choose how much of that lives within the registration script and or -/// lives in [`CommandCompleter::write_complete`]. -#[cfg(feature = "unstable-command")] -pub trait CommandCompleter { - /// Register for completions - /// - /// Write the `buf` the logic needed for calling into ` complete`, passing needed - /// arguments to [`CommandCompleter::write_complete`] through the environment. - fn write_registration( - &self, - name: &str, - bin: &str, - completer: &str, - buf: &mut dyn std::io::Write, - ) -> Result<(), std::io::Error>; - /// Complete the given command - /// - /// Adapt information from arguments and [`CommandCompleter::write_registration`]-defined env - /// variables to what is needed for [`completer`][crate::dynamic::complete()]. - /// - /// Write out the [`CompletionCandidate`][crate::dynamic::CompletionCandidate]s in a way the shell will understand. - fn write_complete( - &self, - cmd: &mut clap::Command, - args: Vec, - current_dir: Option<&std::path::Path>, - buf: &mut dyn std::io::Write, - ) -> Result<(), std::io::Error>; + fn completer(&self) -> &dyn CommandCompleter { + match self { + Self::Bash => &Bash, + Self::Elvish => &Elvish, + Self::Fish => &Fish, + Self::Powershell => &Powershell, + Self::Zsh => &Zsh, + } + } } impl CommandCompleter for Shell { @@ -269,7 +89,8 @@ impl CommandCompleter for Shell { completer: &str, buf: &mut dyn std::io::Write, ) -> Result<(), std::io::Error> { - shell_completer(self).write_registration(name, bin, completer, buf) + self.completer() + .write_registration(name, bin, completer, buf) } fn write_complete( &self, @@ -278,21 +99,74 @@ impl CommandCompleter for Shell { current_dir: Option<&std::path::Path>, buf: &mut dyn std::io::Write, ) -> Result<(), std::io::Error> { - shell_completer(self).write_complete(cmd, args, current_dir, buf) + self.completer().write_complete(cmd, args, current_dir, buf) } } -fn shell_completer(shell: &Shell) -> &dyn CommandCompleter { - match shell { - Shell::Bash => &super::Bash, - Shell::Elvish => &super::Elvish, - Shell::Fish => &super::Fish, - Shell::Powershell => &super::Powershell, - Shell::Zsh => &super::Zsh, +// 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: &std::path::Path) -> Option { + let name = path.file_stem()?.to_str()?; + match name { + "bash" => Some(Shell::Bash), + "elvish" => Some(Shell::Elvish), + "fish" => Some(Shell::Fish), + "powershell" | "powershell_ise" => Some(Shell::Powershell), + "zsh" => Some(Shell::Zsh), + _ => None, } } -impl CommandCompleter for super::Bash { +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 { + 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(&self) -> Option { + 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"), + }) + } +} + +/// Bash completion adapter +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Bash; + +impl CommandCompleter for Bash { fn write_registration( &self, name: &str, @@ -389,7 +263,7 @@ enum CompType { Menu, } -impl std::str::FromStr for CompType { +impl FromStr for CompType { type Err = String; fn from_str(s: &str) -> Result { @@ -409,7 +283,12 @@ impl Default for CompType { Self::Normal } } -impl CommandCompleter for super::Elvish { + +/// Elvish completion adapter +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Elvish; + +impl CommandCompleter for Elvish { fn write_registration( &self, _name: &str, @@ -462,7 +341,11 @@ set edit:completion:arg-completer[BIN] = { |@words| } } -impl CommandCompleter for super::Fish { +/// Fish completion adapter +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Fish; + +impl CommandCompleter for Fish { fn write_registration( &self, _name: &str, @@ -504,7 +387,11 @@ impl CommandCompleter for super::Fish { } } -impl CommandCompleter for super::Powershell { +/// Powershell completion adapter +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Powershell; + +impl CommandCompleter for Powershell { fn write_registration( &self, _name: &str, @@ -533,7 +420,7 @@ Register-ArgumentCompleter -Native -CommandName {bin} -ScriptBlock {{ else {{ $help = $split[0]; }} - + [System.Management.Automation.CompletionResult]::new($cmd, $cmd, 'ParameterValue', $help) }} }}; @@ -566,7 +453,11 @@ Register-ArgumentCompleter -Native -CommandName {bin} -ScriptBlock {{ } } -impl CommandCompleter for super::Zsh { +/// Zsh completion adapter +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Zsh; + +impl CommandCompleter for Zsh { fn write_registration( &self, _name: &str, diff --git a/clap_complete/src/dynamic/complete.rs b/clap_complete/src/dynamic/complete.rs index 214e3d65..256eb26b 100644 --- a/clap_complete/src/dynamic/complete.rs +++ b/clap_complete/src/dynamic/complete.rs @@ -7,11 +7,6 @@ use super::ArgValueCompleter; use super::CompletionCandidate; /// Complete the given command, shell-agnostic -/// -/// For integration with clap and shells, see [`CompleteCommand`][crate::dynamic::shells::CompleteCommand]. -/// -/// This is generally called by a [`Shell`][crate::dynamic::shells::Shell] which -/// handles the shell-specific logic. pub fn complete( cmd: &mut clap::Command, args: Vec, diff --git a/clap_complete/src/dynamic/mod.rs b/clap_complete/src/dynamic/mod.rs index 3ccb879e..6adc7931 100644 --- a/clap_complete/src/dynamic/mod.rs +++ b/clap_complete/src/dynamic/mod.rs @@ -1,7 +1,5 @@ //! Complete commands within shells //! -//! For quick-start, see [`CompleteCommand`] -//! //! To customize completions, see //! - [`ValueHint`][crate::ValueHint] //! - [`ValueEnum`][clap::ValueEnum] @@ -11,15 +9,15 @@ mod candidate; mod complete; mod custom; -pub mod shells; +#[cfg(feature = "unstable-command")] +pub mod command; pub use candidate::CompletionCandidate; pub use complete::complete; pub use custom::ArgValueCompleter; pub use custom::CustomCompleter; -// These live in `shells` because they are tightly coupled with the `ShellCompleter`s #[cfg(feature = "unstable-command")] -pub use shells::CompleteArgs; +pub use command::CompleteArgs; #[cfg(feature = "unstable-command")] -pub use shells::CompleteCommand; +pub use command::CompleteCommand; diff --git a/clap_complete/src/dynamic/shells/mod.rs b/clap_complete/src/dynamic/shells/mod.rs deleted file mode 100644 index 147d95ae..00000000 --- a/clap_complete/src/dynamic/shells/mod.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Shell completion support, see [`CompleteCommand`] for more details - -#[cfg(feature = "unstable-command")] -mod command; - -#[cfg(feature = "unstable-command")] -pub use command::*; - -use std::fmt::Display; -use std::str::FromStr; - -use clap::builder::PossibleValue; -use clap::ValueEnum; - -/// Completion support for built-in shells -#[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 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(path: impl AsRef) -> Option { - 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 operating systems 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 shell = Shell::from_env(); - /// println!("{shell:?}"); - /// ``` - pub fn from_env() -> Option { - if let Some(env_shell) = std::env::var_os("SHELL") { - Shell::from_shell_path(env_shell) - } 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: &std::path::Path) -> Option { - let name = path.file_stem()?.to_str()?; - match name { - "bash" => Some(Shell::Bash), - "elvish" => Some(Shell::Elvish), - "fish" => Some(Shell::Fish), - "powershell" | "powershell_ise" => Some(Shell::Powershell), - "zsh" => Some(Shell::Zsh), - _ => None, - } -} - -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 { - 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(&self) -> Option { - 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"), - }) - } -} - -/// Bash completion adapter -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Bash; - -/// Elvish completion adapter -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Elvish; - -/// Fish completion adapter -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Fish; - -/// Powershell completion adapter -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Powershell; - -/// Zsh completion adapter -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Zsh; diff --git a/clap_complete/tests/testsuite/bash.rs b/clap_complete/tests/testsuite/bash.rs index c5d9267f..8de094fe 100644 --- a/clap_complete/tests/testsuite/bash.rs +++ b/clap_complete/tests/testsuite/bash.rs @@ -114,14 +114,14 @@ fn value_terminator() { #[cfg(feature = "unstable-command")] #[test] fn register_minimal() { - use clap_complete::dynamic::shells::CommandCompleter as _; + use clap_complete::dynamic::command::CommandCompleter as _; let name = "my-app"; let bin = name; let completer = name; let mut buf = Vec::new(); - clap_complete::dynamic::shells::Bash + clap_complete::dynamic::command::Bash .write_registration(name, bin, completer, &mut buf) .unwrap(); snapbox::Assert::new()