diff --git a/clap_complete/examples/dynamic.rs b/clap_complete/examples/dynamic.rs index f22b7adac3b..ba61f5fdc35 100644 --- a/clap_complete/examples/dynamic.rs +++ b/clap_complete/examples/dynamic.rs @@ -16,15 +16,15 @@ fn command() -> clap::Command { .value_parser(["json", "yaml", "toml"]), ) .args_conflicts_with_subcommands(true); - clap_complete::dynamic::CompleteCommand::augment_subcommands(cmd) + clap_complete::CompleteCommand::augment_subcommands(cmd) } fn main() { - clap_complete::dynamic::CompleteEnv::with_factory(command).complete(); + clap_complete::CompleteEnv::with_factory(command).complete(); let cmd = command(); let matches = cmd.get_matches(); - if let Ok(completions) = clap_complete::dynamic::CompleteCommand::from_arg_matches(&matches) { + if let Ok(completions) = clap_complete::CompleteCommand::from_arg_matches(&matches) { completions.complete(&mut command()); } else { println!("{matches:#?}"); diff --git a/clap_complete/examples/exhaustive.rs b/clap_complete/examples/exhaustive.rs index 1f63cadb5c8..83050bdd56f 100644 --- a/clap_complete/examples/exhaustive.rs +++ b/clap_complete/examples/exhaustive.rs @@ -5,7 +5,7 @@ use clap_complete::{generate, Generator, Shell}; fn main() { #[cfg(feature = "unstable-dynamic")] - clap_complete::dynamic::CompleteEnv::with_factory(cli).complete(); + clap_complete::CompleteEnv::with_factory(cli).complete(); let matches = cli().get_matches(); if let Some(generator) = matches.get_one::("generate") { @@ -16,7 +16,7 @@ fn main() { } #[cfg(feature = "unstable-command")] - if let Ok(completions) = clap_complete::dynamic::CompleteCommand::from_arg_matches(&matches) { + if let Ok(completions) = clap_complete::CompleteCommand::from_arg_matches(&matches) { completions.complete(&mut cli()); return; }; @@ -199,6 +199,6 @@ fn cli() -> clap::Command { ]), ]); #[cfg(feature = "unstable-command")] - let cli = clap_complete::dynamic::CompleteCommand::augment_subcommands(cli); + let cli = clap_complete::CompleteCommand::augment_subcommands(cli); cli } diff --git a/clap_complete/src/generator/mod.rs b/clap_complete/src/aot/generator/mod.rs similarity index 100% rename from clap_complete/src/generator/mod.rs rename to clap_complete/src/aot/generator/mod.rs diff --git a/clap_complete/src/generator/utils.rs b/clap_complete/src/aot/generator/utils.rs similarity index 100% rename from clap_complete/src/generator/utils.rs rename to clap_complete/src/aot/generator/utils.rs diff --git a/clap_complete/src/aot/mod.rs b/clap_complete/src/aot/mod.rs new file mode 100644 index 00000000000..fa73134d0c0 --- /dev/null +++ b/clap_complete/src/aot/mod.rs @@ -0,0 +1,56 @@ +//! Prebuilt completions +//! +//! ## Quick Start +//! +//! - For generating at compile-time, see [`generate_to`] +//! - For generating at runtime, see [`generate`] +//! +//! [`Shell`] is a convenience `enum` for an argument value type that implements `Generator` +//! for each natively-supported shell type. +//! +//! To customize completions, see +//! - [`ValueHint`] +//! - [`ValueEnum`][clap::ValueEnum] +//! +//! ## Example +//! +//! ```rust,no_run +//! use clap::{Command, Arg, ValueHint, value_parser, ArgAction}; +//! use clap_complete::{generate, Generator, Shell}; +//! use std::io; +//! +//! fn build_cli() -> Command { +//! Command::new("example") +//! .arg(Arg::new("file") +//! .help("some input file") +//! .value_hint(ValueHint::AnyPath), +//! ) +//! .arg( +//! Arg::new("generator") +//! .long("generate") +//! .action(ArgAction::Set) +//! .value_parser(value_parser!(Shell)), +//! ) +//! } +//! +//! fn print_completions(gen: G, cmd: &mut Command) { +//! generate(gen, cmd, cmd.get_name().to_string(), &mut io::stdout()); +//! } +//! +//! fn main() { +//! let matches = build_cli().get_matches(); +//! +//! if let Some(generator) = matches.get_one::("generator").copied() { +//! let mut cmd = build_cli(); +//! eprintln!("Generating completion file for {generator}..."); +//! print_completions(generator, &mut cmd); +//! } +//! } +//! ``` + +mod generator; +mod shells; + +pub use clap::ValueHint; +pub use generator::*; +pub use shells::*; diff --git a/clap_complete/src/shells/bash.rs b/clap_complete/src/aot/shells/bash.rs similarity index 91% rename from clap_complete/src/shells/bash.rs rename to clap_complete/src/aot/shells/bash.rs index a8b8fb1d1a0..5da32d14a63 100644 --- a/clap_complete/src/shells/bash.rs +++ b/clap_complete/src/aot/shells/bash.rs @@ -20,10 +20,9 @@ impl Generator for Bash { let fn_name = bin_name.replace('-', "__"); - w!( + write!( buf, - format!( - "_{name}() {{ + "_{name}() {{ local i cur prev opts cmd COMPREPLY=() cur=\"${{COMP_WORDS[COMP_CWORD]}}\" @@ -66,15 +65,13 @@ else complete -F _{name} -o bashdefault -o default {name} fi ", - name = bin_name, - cmd = fn_name, - name_opts = all_options_for_path(cmd, bin_name), - name_opts_details = option_details_for_path(cmd, bin_name), - subcmds = all_subcommands(cmd, &fn_name), - subcmd_details = subcommand_details(cmd) - ) - .as_bytes() - ); + name = bin_name, + cmd = fn_name, + name_opts = all_options_for_path(cmd, bin_name), + name_opts_details = option_details_for_path(cmd, bin_name), + subcmds = all_subcommands(cmd, &fn_name), + subcmd_details = subcommand_details(cmd) + ).expect("failed to write completion file"); } } @@ -274,22 +271,23 @@ fn all_options_for_path(cmd: &Command, path: &str) -> String { let mut opts = String::new(); for short in utils::shorts_and_visible_aliases(p) { - write!(&mut opts, "-{short} ").unwrap(); + write!(&mut opts, "-{short} ").expect("writing to String is infallible"); } for long in utils::longs_and_visible_aliases(p) { - write!(&mut opts, "--{long} ").unwrap(); + write!(&mut opts, "--{long} ").expect("writing to String is infallible"); } for pos in p.get_positionals() { if let Some(vals) = utils::possible_values(pos) { for value in vals { - write!(&mut opts, "{} ", value.get_name()).unwrap(); + write!(&mut opts, "{} ", value.get_name()) + .expect("writing to String is infallible"); } } else { - write!(&mut opts, "{pos} ").unwrap(); + write!(&mut opts, "{pos} ").expect("writing to String is infallible"); } } for (sc, _) in utils::subcommands(p) { - write!(&mut opts, "{sc} ").unwrap(); + write!(&mut opts, "{sc} ").expect("writing to String is infallible"); } opts.pop(); diff --git a/clap_complete/src/shells/elvish.rs b/clap_complete/src/aot/shells/elvish.rs similarity index 97% rename from clap_complete/src/shells/elvish.rs rename to clap_complete/src/aot/shells/elvish.rs index eb81e78b1c1..b41f035ec75 100644 --- a/clap_complete/src/shells/elvish.rs +++ b/clap_complete/src/aot/shells/elvish.rs @@ -22,7 +22,8 @@ impl Generator for Elvish { let subcommands_cases = generate_inner(cmd, ""); - let result = format!( + write!( + buf, r#" use builtin; use str; @@ -46,9 +47,8 @@ set edit:completion:arg-completer[{bin_name}] = {{|@words| $completions[$command] }} "#, - ); - - w!(buf, result.as_bytes()); + ) + .expect("failed to write completion file"); } } diff --git a/clap_complete/src/shells/fish.rs b/clap_complete/src/aot/shells/fish.rs similarity index 98% rename from clap_complete/src/shells/fish.rs rename to clap_complete/src/aot/shells/fish.rs index 25404c6b925..a87dd1d5f07 100644 --- a/clap_complete/src/shells/fish.rs +++ b/clap_complete/src/aot/shells/fish.rs @@ -42,7 +42,7 @@ impl Generator for Fish { needs_fn_name, using_fn_name, ); - w!(buf, buffer.as_bytes()); + write!(buf, "{buffer}").expect("failed to write completion file"); } } @@ -240,7 +240,9 @@ fn gen_subcommand_helpers( } } let optspecs_fn_name = format!("__fish_{bin_name}_global_optspecs"); - let template = format!("\ + write!( + buf, + "\ # Print an optspec for argparse to handle cmd's options that are independent of any subcommand.\n\ function {optspecs_fn_name}\n\ \tstring join \\n{optspecs}\n\ @@ -264,8 +266,7 @@ fn gen_subcommand_helpers( \tand return 1\n\ \tcontains -- $cmd[1] $argv\n\ end\n\n\ - "); - w!(buf, template.as_bytes()); + ").expect("failed to write completion file"); } fn value_completion(option: &Arg) -> String { diff --git a/clap_complete/src/shells/mod.rs b/clap_complete/src/aot/shells/mod.rs similarity index 100% rename from clap_complete/src/shells/mod.rs rename to clap_complete/src/aot/shells/mod.rs diff --git a/clap_complete/src/shells/powershell.rs b/clap_complete/src/aot/shells/powershell.rs similarity index 97% rename from clap_complete/src/shells/powershell.rs rename to clap_complete/src/aot/shells/powershell.rs index efc78aba08a..0b71865707e 100644 --- a/clap_complete/src/shells/powershell.rs +++ b/clap_complete/src/aot/shells/powershell.rs @@ -22,7 +22,8 @@ impl Generator for PowerShell { let subcommands_cases = generate_inner(cmd, ""); - let result = format!( + write!( + buf, r#" using namespace System.Management.Automation using namespace System.Management.Automation.Language @@ -51,9 +52,8 @@ Register-ArgumentCompleter -Native -CommandName '{bin_name}' -ScriptBlock {{ Sort-Object -Property ListItemText }} "# - ); - - w!(buf, result.as_bytes()); + ) + .expect("failed to write completion file"); } } diff --git a/clap_complete/src/shells/shell.rs b/clap_complete/src/aot/shells/shell.rs similarity index 100% rename from clap_complete/src/shells/shell.rs rename to clap_complete/src/aot/shells/shell.rs diff --git a/clap_complete/src/shells/zsh.rs b/clap_complete/src/aot/shells/zsh.rs similarity index 98% rename from clap_complete/src/shells/zsh.rs rename to clap_complete/src/aot/shells/zsh.rs index 510481303be..364df24f185 100644 --- a/clap_complete/src/shells/zsh.rs +++ b/clap_complete/src/aot/shells/zsh.rs @@ -19,10 +19,9 @@ impl Generator for Zsh { .get_bin_name() .expect("crate::generate should have set the bin_name"); - w!( + write!( buf, - format!( - "#compdef {name} + "#compdef {name} autoload -U is-at-least @@ -49,13 +48,12 @@ else compdef _{name} {name} fi ", - name = bin_name, - initial_args = get_args_of(cmd, None), - subcommands = get_subcommands_of(cmd), - subcommand_details = subcommand_details(cmd) - ) - .as_bytes() - ); + name = bin_name, + initial_args = get_args_of(cmd, None), + subcommands = get_subcommands_of(cmd), + subcommand_details = subcommand_details(cmd) + ) + .expect("failed to write completion file"); } } @@ -673,7 +671,7 @@ fn write_positionals_of(p: &Command) -> String { #[cfg(test)] mod tests { - use crate::shells::zsh::{escape_help, escape_value}; + use super::{escape_help, escape_value}; #[test] fn test_escape_value() { diff --git a/clap_complete/src/dynamic/command/mod.rs b/clap_complete/src/command/mod.rs similarity index 95% rename from clap_complete/src/dynamic/command/mod.rs rename to clap_complete/src/command/mod.rs index ce3c4ffd430..97c9845b933 100644 --- a/clap_complete/src/dynamic/command/mod.rs +++ b/clap_complete/src/command/mod.rs @@ -46,6 +46,11 @@ pub use shells::*; /// A completion subcommand to add to your CLI /// +/// To customize completions, see +/// - [`ValueHint`][crate::ValueHint] +/// - [`ValueEnum`][clap::ValueEnum] +/// - [`ArgValueCompleter`][crate::ArgValueCompleter] +/// /// **Warning:** `stdout` should not be written to before [`CompleteCommand::complete`] has had a /// chance to run. /// @@ -55,7 +60,7 @@ pub use shells::*; /// ```no_run /// // src/main.rs /// use clap::{CommandFactory, FromArgMatches, Parser, Subcommand}; -/// use clap_complete::dynamic::CompleteCommand; +/// use clap_complete::CompleteCommand; /// /// #[derive(Parser, Debug)] /// #[clap(name = "dynamic", about = "A dynamic command line tool")] @@ -113,6 +118,11 @@ impl CompleteCommand { /// A completion subcommand to add to your CLI /// +/// To customize completions, see +/// - [`ValueHint`][crate::ValueHint] +/// - [`ValueEnum`][clap::ValueEnum] +/// - [`ArgValueCompleter`][crate::ArgValueCompleter] +/// /// **Warning:** `stdout` should not be written to before [`CompleteArgs::complete`] has had a /// chance to run. /// @@ -122,7 +132,7 @@ impl CompleteCommand { /// ```no_run /// // src/main.rs /// use clap::{CommandFactory, FromArgMatches, Parser, Subcommand}; -/// use clap_complete::dynamic::CompleteArgs; +/// use clap_complete::CompleteArgs; /// /// #[derive(Parser, Debug)] /// #[clap(name = "dynamic", about = "A dynamic command line tool")] diff --git a/clap_complete/src/dynamic/command/shells.rs b/clap_complete/src/command/shells.rs similarity index 100% rename from clap_complete/src/dynamic/command/shells.rs rename to clap_complete/src/command/shells.rs diff --git a/clap_complete/src/dynamic/mod.rs b/clap_complete/src/dynamic/mod.rs index 118b7a266ac..c10cf57a6de 100644 --- a/clap_complete/src/dynamic/mod.rs +++ b/clap_complete/src/dynamic/mod.rs @@ -1,25 +1,12 @@ -//! Complete commands within shells +//! `clap`-native completion system //! -//! To customize completions, see -//! - [`ValueHint`][crate::ValueHint] -//! - [`ValueEnum`][clap::ValueEnum] -//! - [`ArgValueCompleter`] +//! See [`complete()`] mod candidate; mod complete; mod custom; -#[cfg(feature = "unstable-command")] -pub mod command; -pub mod env; - pub use candidate::CompletionCandidate; pub use complete::complete; pub use custom::ArgValueCompleter; pub use custom::CustomCompleter; - -#[cfg(feature = "unstable-command")] -pub use command::CompleteArgs; -#[cfg(feature = "unstable-command")] -pub use command::CompleteCommand; -pub use env::CompleteEnv; diff --git a/clap_complete/src/dynamic/env/mod.rs b/clap_complete/src/env/mod.rs similarity index 95% rename from clap_complete/src/dynamic/env/mod.rs rename to clap_complete/src/env/mod.rs index f46af829a3c..093ac67f4c9 100644 --- a/clap_complete/src/dynamic/env/mod.rs +++ b/clap_complete/src/env/mod.rs @@ -2,7 +2,7 @@ //! //! See [`CompleteEnv`]: //! ```rust -//! # use clap_complete::dynamic::CompleteEnv; +//! # use clap_complete::CompleteEnv; //! fn cli() -> clap::Command { //! // ... //! # clap::Command::new("empty") @@ -16,6 +16,11 @@ //! } //! ``` //! +//! To customize completions, see +//! - [`ValueHint`][crate::ValueHint] +//! - [`ValueEnum`][clap::ValueEnum] +//! - [`ArgValueCompleter`][crate::ArgValueCompleter] +//! //! To source your completions: //! //! **WARNING:** We recommend re-sourcing your completions on upgrade. @@ -63,8 +68,13 @@ pub use shells::*; /// - Performance: we don't need to general [`clap::Command`] twice or parse arguments /// - Flexibility: there is no concern over it interfering with other CLI logic /// +/// **Warning:** `stdout` should not be written to before [`CompleteEnv::complete`] has had a +/// chance to run. +/// +/// # Examples +/// /// ```rust -/// # use clap_complete::dynamic::CompleteEnv; +/// # use clap_complete::CompleteEnv; /// fn cli() -> clap::Command { /// // ... /// # clap::Command::new("empty") @@ -90,7 +100,7 @@ impl<'s, F: FnOnce() -> clap::Command> CompleteEnv<'s, F> { /// /// Builder: /// ```rust - /// # use clap_complete::dynamic::CompleteEnv; + /// # use clap_complete::CompleteEnv; /// fn cli() -> clap::Command { /// // ... /// # clap::Command::new("empty") @@ -107,7 +117,7 @@ impl<'s, F: FnOnce() -> clap::Command> CompleteEnv<'s, F> { /// Derive: /// ``` /// # use clap::Parser; - /// # use clap_complete::dynamic::CompleteEnv; + /// # use clap_complete::CompleteEnv; /// use clap::CommandFactory as _; /// /// #[derive(Debug, Parser)] diff --git a/clap_complete/src/dynamic/env/shells.rs b/clap_complete/src/env/shells.rs similarity index 100% rename from clap_complete/src/dynamic/env/shells.rs rename to clap_complete/src/env/shells.rs diff --git a/clap_complete/src/lib.rs b/clap_complete/src/lib.rs index c97a9f1a728..247532830d4 100644 --- a/clap_complete/src/lib.rs +++ b/clap_complete/src/lib.rs @@ -17,7 +17,7 @@ //! //! ```rust,no_run //! use clap::{Command, Arg, ValueHint, value_parser, ArgAction}; -//! use clap_complete::{generate, Generator, Shell}; +//! use clap_complete::aot::{generate, Generator, Shell}; //! use std::io; //! //! fn build_cli() -> Command { @@ -65,14 +65,49 @@ const INTERNAL_ERROR_MSG: &str = "Fatal internal error. Please consider filing a #[allow(missing_docs)] mod macros; -pub mod generator; -pub mod shells; +pub mod aot; +#[cfg(feature = "unstable-command")] +pub mod command; +#[cfg(feature = "unstable-dynamic")] +pub mod dynamic; +#[cfg(feature = "unstable-dynamic")] +pub mod env; pub use clap::ValueHint; -pub use generator::generate; -pub use generator::generate_to; -pub use generator::Generator; -pub use shells::Shell; - +#[cfg(feature = "unstable-command")] +pub use command::CompleteArgs; +#[cfg(feature = "unstable-command")] +pub use command::CompleteCommand; +#[doc(inline)] #[cfg(feature = "unstable-dynamic")] -pub mod dynamic; +pub use dynamic::ArgValueCompleter; +#[doc(inline)] +#[cfg(feature = "unstable-dynamic")] +pub use dynamic::CompletionCandidate; +#[cfg(feature = "unstable-dynamic")] +pub use env::CompleteEnv; + +/// Deprecated, see [`aot`] +pub mod generator { + pub use crate::aot::generate; + pub use crate::aot::generate_to; + pub use crate::aot::utils; + pub use crate::aot::Generator; +} +/// Deprecated, see [`aot`] +pub mod shells { + pub use crate::aot::Bash; + pub use crate::aot::Elvish; + pub use crate::aot::Fish; + pub use crate::aot::PowerShell; + pub use crate::aot::Shell; + pub use crate::aot::Zsh; +} +/// Deprecated, see [`aot::generate`] +pub use aot::generate; +/// Deprecated, see [`aot::generate_to`] +pub use aot::generate_to; +/// Deprecated, see [`aot::Generator`] +pub use aot::Generator; +/// Deprecated, see [`aot::Shell`] +pub use aot::Shell; diff --git a/clap_complete/src/macros.rs b/clap_complete/src/macros.rs index bc697946098..f9e529d9566 100644 --- a/clap_complete/src/macros.rs +++ b/clap_complete/src/macros.rs @@ -1,12 +1,3 @@ -macro_rules! w { - ($buf:expr, $to_w:expr) => { - match $buf.write_all($to_w) { - Ok(..) => (), - Err(..) => panic!("Failed to write to generated file"), - } - }; -} - #[cfg(feature = "debug")] macro_rules! debug { ($($arg:tt)*) => { diff --git a/clap_complete/tests/testsuite/bash.rs b/clap_complete/tests/testsuite/bash.rs index 91001a75734..00c57c8fd25 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::command::CommandCompleter as _; + use clap_complete::command::CommandCompleter as _; let name = "my-app"; let bin = name; let completer = name; let mut buf = Vec::new(); - clap_complete::dynamic::command::Bash + clap_complete::command::Bash .write_registration(name, bin, completer, &mut buf) .unwrap(); snapbox::Assert::new()