mirror of
https://github.com/clap-rs/clap
synced 2024-11-10 06:44:16 +00:00
fix(complete)!: Rename shells to command
This commit is contained in:
parent
2d8138a471
commit
f75251f5ae
7 changed files with 387 additions and 431 deletions
|
@ -4,9 +4,6 @@ use std::ffi::OsString;
|
||||||
use clap::builder::StyledStr;
|
use clap::builder::StyledStr;
|
||||||
|
|
||||||
/// A shell-agnostic completion candidate
|
/// 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)]
|
#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct CompletionCandidate {
|
pub struct CompletionCandidate {
|
||||||
content: OsString,
|
content: OsString,
|
||||||
|
|
230
clap_complete/src/dynamic/command/mod.rs
Normal file
230
clap_complete/src/dynamic/command/mod.rs
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
//! [`<bin> 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<CompleteCommand>,
|
||||||
|
///
|
||||||
|
/// /// Input file path
|
||||||
|
/// #[clap(short, long, value_hint = clap::ValueHint::FilePath)]
|
||||||
|
/// input: Option<String>,
|
||||||
|
/// /// Output format
|
||||||
|
/// #[clap(short = 'F', long, value_parser = ["json", "yaml", "toml"])]
|
||||||
|
/// format: Option<String>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// 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<Shell>,
|
||||||
|
|
||||||
|
#[arg(raw = true, value_name = "ARG", hide = true)]
|
||||||
|
comp_words: Option<Vec<OsString>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 `<cmd> 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<OsString>,
|
||||||
|
current_dir: Option<&std::path::Path>,
|
||||||
|
buf: &mut dyn std::io::Write,
|
||||||
|
) -> Result<(), std::io::Error>;
|
||||||
|
}
|
|
@ -1,264 +1,84 @@
|
||||||
use std::ffi::OsString;
|
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 unicode_xid::UnicodeXID as _;
|
||||||
|
|
||||||
use super::Shell;
|
use super::CommandCompleter;
|
||||||
|
|
||||||
/// A completion subcommand to add to your CLI
|
/// Completion support for built-in shells
|
||||||
///
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
/// If you aren't using a subcommand, you can annotate a field with this type as `#[command(subcommand)]`.
|
#[non_exhaustive]
|
||||||
///
|
pub enum Shell {
|
||||||
/// If you are using subcommands, see [`CompleteArgs`].
|
/// Bourne Again `SHell` (bash)
|
||||||
///
|
Bash,
|
||||||
/// **Warning:** `stdout` should not be written to before [`CompleteCommand::complete`] has had a
|
/// Elvish shell
|
||||||
/// chance to run.
|
Elvish,
|
||||||
///
|
/// Friendly Interactive `SHell` (fish)
|
||||||
/// # Examples
|
Fish,
|
||||||
///
|
/// `PowerShell`
|
||||||
/// To integrate completions into an application without subcommands:
|
Powershell,
|
||||||
/// ```no_run
|
/// Z `SHell` (zsh)
|
||||||
/// // src/main.rs
|
Zsh,
|
||||||
/// 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<CompleteCommand>,
|
|
||||||
/// /// Input file path
|
|
||||||
/// #[clap(short, long, value_hint = clap::ValueHint::FilePath)]
|
|
||||||
/// input: Option<String>,
|
|
||||||
/// /// Output format
|
|
||||||
/// #[clap(short = 'F', long, value_parser = ["json", "yaml", "toml"])]
|
|
||||||
/// format: Option<String>,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// 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),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompleteCommand {
|
impl Shell {
|
||||||
/// Process the completion request and exit
|
/// Parse a shell from a path to the executable for the shell
|
||||||
///
|
///
|
||||||
/// **Warning:** `stdout` should not be written to before this has had a
|
/// # Examples
|
||||||
/// chance to run.
|
///
|
||||||
pub fn complete(&self, cmd: &mut clap::Command) -> std::convert::Infallible {
|
/// ```
|
||||||
self.try_complete(cmd).unwrap_or_else(|e| e.exit());
|
/// use clap_complete::shells::Shell;
|
||||||
std::process::exit(0)
|
///
|
||||||
|
/// 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<std::path::Path>) -> Option<Shell> {
|
||||||
|
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.
|
/// This will read the SHELL environment variable and try to determine which shell is in use
|
||||||
pub fn try_complete(&self, cmd: &mut clap::Command) -> clap::error::Result<()> {
|
/// from that.
|
||||||
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<Shell>,
|
|
||||||
|
|
||||||
#[arg(raw = true, value_name = "ARG", hide = true)]
|
|
||||||
comp_words: Option<Vec<OsString>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CompleteArgs {
|
|
||||||
/// Process the completion request and exit
|
|
||||||
///
|
///
|
||||||
/// **Warning:** `stdout` should not be written to before this has had a
|
/// If SHELL is not set, then on windows, it will default to powershell, and on
|
||||||
/// chance to run.
|
/// other operating systems it will return `None`.
|
||||||
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.
|
/// If SHELL is set, but contains a value that doesn't correspond to one of the supported shell
|
||||||
pub fn try_complete(&self, cmd: &mut clap::Command) -> clap::error::Result<()> {
|
/// types, then return `None`.
|
||||||
debug!("CompleteCommand::try_complete: {self:?}");
|
///
|
||||||
|
/// # Example:
|
||||||
let shell = self.shell.or_else(|| Shell::from_env()).ok_or_else(|| {
|
///
|
||||||
std::io::Error::new(
|
/// ```no_run
|
||||||
std::io::ErrorKind::Other,
|
/// # use clap::Command;
|
||||||
"unknown shell, please specify the name of your shell",
|
/// use clap_complete::{generate, shells::Shell};
|
||||||
)
|
/// # fn build_cli() -> Command {
|
||||||
})?;
|
/// # Command::new("compl")
|
||||||
|
/// # }
|
||||||
if let Some(comp_words) = self.comp_words.as_ref() {
|
/// let shell = Shell::from_env();
|
||||||
let current_dir = std::env::current_dir().ok();
|
/// println!("{shell:?}");
|
||||||
|
/// ```
|
||||||
let mut buf = Vec::new();
|
pub fn from_env() -> Option<Shell> {
|
||||||
shell.write_complete(cmd, comp_words.clone(), current_dir.as_deref(), &mut buf)?;
|
if let Some(env_shell) = std::env::var_os("SHELL") {
|
||||||
std::io::stdout().write_all(&buf)?;
|
Shell::from_shell_path(env_shell)
|
||||||
} else {
|
} else {
|
||||||
let name = cmd.get_name();
|
None
|
||||||
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
|
fn completer(&self) -> &dyn CommandCompleter {
|
||||||
///
|
match self {
|
||||||
/// This will generally be called by [`CompleteCommand`] or [`CompleteArgs`].
|
Self::Bash => &Bash,
|
||||||
///
|
Self::Elvish => &Elvish,
|
||||||
/// This handles adapting between the shell and [`completer`][crate::dynamic::complete()].
|
Self::Fish => &Fish,
|
||||||
/// A `CommandCompleter` can choose how much of that lives within the registration script and or
|
Self::Powershell => &Powershell,
|
||||||
/// lives in [`CommandCompleter::write_complete`].
|
Self::Zsh => &Zsh,
|
||||||
#[cfg(feature = "unstable-command")]
|
}
|
||||||
pub trait CommandCompleter {
|
}
|
||||||
/// Register for completions
|
|
||||||
///
|
|
||||||
/// Write the `buf` the logic needed for calling into `<cmd> 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<OsString>,
|
|
||||||
current_dir: Option<&std::path::Path>,
|
|
||||||
buf: &mut dyn std::io::Write,
|
|
||||||
) -> Result<(), std::io::Error>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandCompleter for Shell {
|
impl CommandCompleter for Shell {
|
||||||
|
@ -269,7 +89,8 @@ impl CommandCompleter for Shell {
|
||||||
completer: &str,
|
completer: &str,
|
||||||
buf: &mut dyn std::io::Write,
|
buf: &mut dyn std::io::Write,
|
||||||
) -> Result<(), std::io::Error> {
|
) -> Result<(), std::io::Error> {
|
||||||
shell_completer(self).write_registration(name, bin, completer, buf)
|
self.completer()
|
||||||
|
.write_registration(name, bin, completer, buf)
|
||||||
}
|
}
|
||||||
fn write_complete(
|
fn write_complete(
|
||||||
&self,
|
&self,
|
||||||
|
@ -278,21 +99,74 @@ impl CommandCompleter for Shell {
|
||||||
current_dir: Option<&std::path::Path>,
|
current_dir: Option<&std::path::Path>,
|
||||||
buf: &mut dyn std::io::Write,
|
buf: &mut dyn std::io::Write,
|
||||||
) -> Result<(), std::io::Error> {
|
) -> 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 {
|
// use a separate function to avoid having to monomorphize the entire function due
|
||||||
match shell {
|
// to from_shell_path being generic
|
||||||
Shell::Bash => &super::Bash,
|
fn parse_shell_from_path(path: &std::path::Path) -> Option<Shell> {
|
||||||
Shell::Elvish => &super::Elvish,
|
let name = path.file_stem()?.to_str()?;
|
||||||
Shell::Fish => &super::Fish,
|
match name {
|
||||||
Shell::Powershell => &super::Powershell,
|
"bash" => Some(Shell::Bash),
|
||||||
Shell::Zsh => &super::Zsh,
|
"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<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(&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"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bash completion adapter
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Bash;
|
||||||
|
|
||||||
|
impl CommandCompleter for Bash {
|
||||||
fn write_registration(
|
fn write_registration(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
@ -389,7 +263,7 @@ enum CompType {
|
||||||
Menu,
|
Menu,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for CompType {
|
impl FromStr for CompType {
|
||||||
type Err = String;
|
type Err = String;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
@ -409,7 +283,12 @@ impl Default for CompType {
|
||||||
Self::Normal
|
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(
|
fn write_registration(
|
||||||
&self,
|
&self,
|
||||||
_name: &str,
|
_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(
|
fn write_registration(
|
||||||
&self,
|
&self,
|
||||||
_name: &str,
|
_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(
|
fn write_registration(
|
||||||
&self,
|
&self,
|
||||||
_name: &str,
|
_name: &str,
|
||||||
|
@ -533,7 +420,7 @@ Register-ArgumentCompleter -Native -CommandName {bin} -ScriptBlock {{
|
||||||
else {{
|
else {{
|
||||||
$help = $split[0];
|
$help = $split[0];
|
||||||
}}
|
}}
|
||||||
|
|
||||||
[System.Management.Automation.CompletionResult]::new($cmd, $cmd, 'ParameterValue', $help)
|
[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(
|
fn write_registration(
|
||||||
&self,
|
&self,
|
||||||
_name: &str,
|
_name: &str,
|
|
@ -7,11 +7,6 @@ use super::ArgValueCompleter;
|
||||||
use super::CompletionCandidate;
|
use super::CompletionCandidate;
|
||||||
|
|
||||||
/// Complete the given command, shell-agnostic
|
/// 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(
|
pub fn complete(
|
||||||
cmd: &mut clap::Command,
|
cmd: &mut clap::Command,
|
||||||
args: Vec<OsString>,
|
args: Vec<OsString>,
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
//! Complete commands within shells
|
//! Complete commands within shells
|
||||||
//!
|
//!
|
||||||
//! For quick-start, see [`CompleteCommand`]
|
|
||||||
//!
|
|
||||||
//! To customize completions, see
|
//! To customize completions, see
|
||||||
//! - [`ValueHint`][crate::ValueHint]
|
//! - [`ValueHint`][crate::ValueHint]
|
||||||
//! - [`ValueEnum`][clap::ValueEnum]
|
//! - [`ValueEnum`][clap::ValueEnum]
|
||||||
|
@ -11,15 +9,15 @@ mod candidate;
|
||||||
mod complete;
|
mod complete;
|
||||||
mod custom;
|
mod custom;
|
||||||
|
|
||||||
pub mod shells;
|
#[cfg(feature = "unstable-command")]
|
||||||
|
pub mod command;
|
||||||
|
|
||||||
pub use candidate::CompletionCandidate;
|
pub use candidate::CompletionCandidate;
|
||||||
pub use complete::complete;
|
pub use complete::complete;
|
||||||
pub use custom::ArgValueCompleter;
|
pub use custom::ArgValueCompleter;
|
||||||
pub use custom::CustomCompleter;
|
pub use custom::CustomCompleter;
|
||||||
|
|
||||||
// These live in `shells` because they are tightly coupled with the `ShellCompleter`s
|
|
||||||
#[cfg(feature = "unstable-command")]
|
#[cfg(feature = "unstable-command")]
|
||||||
pub use shells::CompleteArgs;
|
pub use command::CompleteArgs;
|
||||||
#[cfg(feature = "unstable-command")]
|
#[cfg(feature = "unstable-command")]
|
||||||
pub use shells::CompleteCommand;
|
pub use command::CompleteCommand;
|
||||||
|
|
|
@ -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<std::path::Path>) -> 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 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<Shell> {
|
|
||||||
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<Shell> {
|
|
||||||
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<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(&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"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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;
|
|
|
@ -114,14 +114,14 @@ fn value_terminator() {
|
||||||
#[cfg(feature = "unstable-command")]
|
#[cfg(feature = "unstable-command")]
|
||||||
#[test]
|
#[test]
|
||||||
fn register_minimal() {
|
fn register_minimal() {
|
||||||
use clap_complete::dynamic::shells::CommandCompleter as _;
|
use clap_complete::dynamic::command::CommandCompleter as _;
|
||||||
|
|
||||||
let name = "my-app";
|
let name = "my-app";
|
||||||
let bin = name;
|
let bin = name;
|
||||||
let completer = name;
|
let completer = name;
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
clap_complete::dynamic::shells::Bash
|
clap_complete::dynamic::command::Bash
|
||||||
.write_registration(name, bin, completer, &mut buf)
|
.write_registration(name, bin, completer, &mut buf)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
snapbox::Assert::new()
|
snapbox::Assert::new()
|
||||||
|
|
Loading…
Reference in a new issue