Skip to content

Commit

Permalink
fix(complete)!: Rename shells to command
Browse files Browse the repository at this point in the history
  • Loading branch information
epage committed Aug 12, 2024
1 parent 2d8138a commit f75251f
Show file tree
Hide file tree
Showing 7 changed files with 388 additions and 432 deletions.
3 changes: 0 additions & 3 deletions clap_complete/src/dynamic/candidate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
230 changes: 230 additions & 0 deletions clap_complete/src/dynamic/command/mod.rs
Original file line number Diff line number Diff line change
@@ -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>;
}
Loading

0 comments on commit f75251f

Please sign in to comment.