From a4b7ed7a42c716ab9f05e351f003d589124fd55d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:18:58 +0200 Subject: Adding upstream version 1.68.2+dfsg1. Signed-off-by: Daniel Baumann --- vendor/clap_complete/src/dynamic.rs | 551 ++++++++++++++++++++++++++ vendor/clap_complete/src/generator/mod.rs | 12 +- vendor/clap_complete/src/generator/utils.rs | 49 ++- vendor/clap_complete/src/lib.rs | 12 +- vendor/clap_complete/src/macros.rs | 4 +- vendor/clap_complete/src/shells/bash.rs | 76 ++-- vendor/clap_complete/src/shells/elvish.rs | 16 +- vendor/clap_complete/src/shells/fish.rs | 29 +- vendor/clap_complete/src/shells/powershell.rs | 16 +- vendor/clap_complete/src/shells/shell.rs | 80 +++- vendor/clap_complete/src/shells/zsh.rs | 45 +-- 11 files changed, 765 insertions(+), 125 deletions(-) create mode 100644 vendor/clap_complete/src/dynamic.rs (limited to 'vendor/clap_complete/src') diff --git a/vendor/clap_complete/src/dynamic.rs b/vendor/clap_complete/src/dynamic.rs new file mode 100644 index 000000000..929841ec8 --- /dev/null +++ b/vendor/clap_complete/src/dynamic.rs @@ -0,0 +1,551 @@ +//! Complete commands within shells + +/// Complete commands within bash +pub mod bash { + use std::ffi::OsString; + use std::io::Write; + + use unicode_xid::UnicodeXID; + + #[derive(clap::Subcommand)] + #[command(hide = true)] + #[allow(missing_docs)] + #[derive(Clone, Debug)] + pub enum CompleteCommand { + /// Register shell completions for this program + Complete(CompleteArgs), + } + + #[derive(clap::Args)] + #[command(group = clap::ArgGroup::new("complete").multiple(true).conflicts_with("register"))] + #[allow(missing_docs)] + #[derive(Clone, Debug)] + pub struct CompleteArgs { + /// Path to write completion-registration to + #[arg(long, required = true)] + register: Option, + + #[arg( + long, + required = true, + value_name = "COMP_CWORD", + hide_short_help = true, + group = "complete" + )] + index: Option, + + #[arg(long, hide_short_help = true, group = "complete")] + ifs: Option, + + #[arg( + long = "type", + required = true, + hide_short_help = true, + group = "complete" + )] + comp_type: Option, + + #[arg(long, hide_short_help = true, group = "complete")] + space: bool, + + #[arg( + long, + conflicts_with = "space", + hide_short_help = true, + group = "complete" + )] + no_space: bool, + + #[arg(raw = true, hide_short_help = true, group = "complete")] + comp_words: Vec, + } + + impl CompleteCommand { + /// Process the completion request + 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 + pub fn try_complete(&self, cmd: &mut clap::Command) -> clap::error::Result<()> { + debug!("CompleteCommand::try_complete: {:?}", self); + let CompleteCommand::Complete(args) = self; + if let Some(out_path) = args.register.as_deref() { + let mut buf = Vec::new(); + let name = cmd.get_name(); + let bin = cmd.get_bin_name().unwrap_or_else(|| cmd.get_name()); + register(name, [bin], bin, &Behavior::default(), &mut buf)?; + if out_path == std::path::Path::new("-") { + std::io::stdout().write_all(&buf)?; + } else if out_path.is_dir() { + let out_path = out_path.join(file_name(name)); + std::fs::write(out_path, buf)?; + } else { + std::fs::write(out_path, buf)?; + } + } else { + let index = args.index.unwrap_or_default(); + let comp_type = args.comp_type.unwrap_or_default(); + let space = match (args.space, args.no_space) { + (true, false) => Some(true), + (false, true) => Some(false), + (true, true) => { + unreachable!("`--space` and `--no-space` set, clap should prevent this") + } + (false, false) => None, + } + .unwrap(); + let current_dir = std::env::current_dir().ok(); + let completions = complete( + cmd, + args.comp_words.clone(), + index, + comp_type, + space, + current_dir.as_deref(), + )?; + + let mut buf = Vec::new(); + for (i, completion) in completions.iter().enumerate() { + if i != 0 { + write!(&mut buf, "{}", args.ifs.as_deref().unwrap_or("\n"))?; + } + write!(&mut buf, "{}", completion.to_string_lossy())?; + } + std::io::stdout().write_all(&buf)?; + } + + Ok(()) + } + } + + /// The recommended file name for the registration code + pub fn file_name(name: &str) -> String { + format!("{}.bash", name) + } + + /// Define the completion behavior + pub enum Behavior { + /// Bare bones behavior + Minimal, + /// Fallback to readline behavior when no matches are generated + Readline, + /// Customize bash's completion behavior + Custom(String), + } + + impl Default for Behavior { + fn default() -> Self { + Self::Readline + } + } + + /// Generate code to register the dynamic completion + pub fn register( + name: &str, + executables: impl IntoIterator>, + completer: &str, + behavior: &Behavior, + buf: &mut dyn Write, + ) -> Result<(), std::io::Error> { + let escaped_name = name.replace('-', "_"); + debug_assert!( + escaped_name.chars().all(|c| c.is_xid_continue()), + "`name` must be an identifier, got `{}`", + escaped_name + ); + let mut upper_name = escaped_name.clone(); + upper_name.make_ascii_uppercase(); + + let executables = executables + .into_iter() + .map(|s| shlex::quote(s.as_ref()).into_owned()) + .collect::>() + .join(" "); + + let options = match behavior { + Behavior::Minimal => "-o nospace -o bashdefault", + Behavior::Readline => "-o nospace -o default -o bashdefault", + Behavior::Custom(c) => c.as_str(), + }; + + let completer = shlex::quote(completer); + + let script = r#" +_clap_complete_NAME() { + local IFS=$'\013' + local SUPPRESS_SPACE=0 + if compopt +o nospace 2> /dev/null; then + SUPPRESS_SPACE=1 + fi + if [[ ${SUPPRESS_SPACE} == 1 ]]; then + SPACE_ARG="--no-space" + else + SPACE_ARG="--space" + fi + COMPREPLY=( $("COMPLETER" complete --index ${COMP_CWORD} --type ${COMP_TYPE} ${SPACE_ARG} --ifs="$IFS" -- "${COMP_WORDS[@]}") ) + if [[ $? != 0 ]]; then + unset COMPREPLY + elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then + compopt -o nospace + fi +} +complete OPTIONS -F _clap_complete_NAME EXECUTABLES +"# + .replace("NAME", &escaped_name) + .replace("EXECUTABLES", &executables) + .replace("OPTIONS", options) + .replace("COMPLETER", &completer) + .replace("UPPER", &upper_name); + + writeln!(buf, "{}", script)?; + Ok(()) + } + + /// Type of completion attempted that caused a completion function to be called + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + #[non_exhaustive] + pub enum CompType { + /// Normal completion + Normal, + /// List completions after successive tabs + Successive, + /// List alternatives on partial word completion + Alternatives, + /// List completions if the word is not unmodified + Unmodified, + /// Menu completion + Menu, + } + + impl clap::ValueEnum for CompType { + fn value_variants<'a>() -> &'a [Self] { + &[ + Self::Normal, + Self::Successive, + Self::Alternatives, + Self::Unmodified, + Self::Menu, + ] + } + fn to_possible_value(&self) -> ::std::option::Option { + match self { + Self::Normal => { + let value = "9"; + debug_assert_eq!(b'\t'.to_string(), value); + Some( + clap::builder::PossibleValue::new(value) + .alias("normal") + .help("Normal completion"), + ) + } + Self::Successive => { + let value = "63"; + debug_assert_eq!(b'?'.to_string(), value); + Some( + clap::builder::PossibleValue::new(value) + .alias("successive") + .help("List completions after successive tabs"), + ) + } + Self::Alternatives => { + let value = "33"; + debug_assert_eq!(b'!'.to_string(), value); + Some( + clap::builder::PossibleValue::new(value) + .alias("alternatives") + .help("List alternatives on partial word completion"), + ) + } + Self::Unmodified => { + let value = "64"; + debug_assert_eq!(b'@'.to_string(), value); + Some( + clap::builder::PossibleValue::new(value) + .alias("unmodified") + .help("List completions if the word is not unmodified"), + ) + } + Self::Menu => { + let value = "37"; + debug_assert_eq!(b'%'.to_string(), value); + Some( + clap::builder::PossibleValue::new(value) + .alias("menu") + .help("Menu completion"), + ) + } + } + } + } + + impl Default for CompType { + fn default() -> Self { + Self::Normal + } + } + + /// Complete the command specified + pub fn complete( + cmd: &mut clap::Command, + args: Vec, + arg_index: usize, + _comp_type: CompType, + _trailing_space: bool, + current_dir: Option<&std::path::Path>, + ) -> Result, std::io::Error> { + cmd.build(); + + let raw_args = clap_lex::RawArgs::new(args.into_iter()); + let mut cursor = raw_args.cursor(); + let mut target_cursor = raw_args.cursor(); + raw_args.seek( + &mut target_cursor, + clap_lex::SeekFrom::Start(arg_index as u64), + ); + // As we loop, `cursor` will always be pointing to the next item + raw_args.next_os(&mut target_cursor); + + // TODO: Multicall support + if !cmd.is_no_binary_name_set() { + raw_args.next_os(&mut cursor); + } + + let mut current_cmd = &*cmd; + let mut pos_index = 1; + let mut is_escaped = false; + while let Some(arg) = raw_args.next(&mut cursor) { + if cursor == target_cursor { + return complete_arg(&arg, current_cmd, current_dir, pos_index, is_escaped); + } + + debug!( + "complete::next: Begin parsing '{:?}' ({:?})", + arg.to_value_os(), + arg.to_value_os().as_raw_bytes() + ); + + if let Ok(value) = arg.to_value() { + if let Some(next_cmd) = current_cmd.find_subcommand(value) { + current_cmd = next_cmd; + pos_index = 0; + continue; + } + } + + if is_escaped { + pos_index += 1; + } else if arg.is_escape() { + is_escaped = true; + } else if let Some(_long) = arg.to_long() { + } else if let Some(_short) = arg.to_short() { + } else { + pos_index += 1; + } + } + + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "No completion generated", + )) + } + + fn complete_arg( + arg: &clap_lex::ParsedArg<'_>, + cmd: &clap::Command, + current_dir: Option<&std::path::Path>, + pos_index: usize, + is_escaped: bool, + ) -> Result, std::io::Error> { + debug!( + "complete_arg: arg={:?}, cmd={:?}, current_dir={:?}, pos_index={}, is_escaped={}", + arg, + cmd.get_name(), + current_dir, + pos_index, + is_escaped + ); + let mut completions = Vec::new(); + + if !is_escaped { + if let Some((flag, value)) = arg.to_long() { + if let Ok(flag) = flag { + if let Some(value) = value { + if let Some(arg) = cmd.get_arguments().find(|a| a.get_long() == Some(flag)) + { + completions.extend( + complete_arg_value(value.to_str().ok_or(value), arg, current_dir) + .into_iter() + .map(|os| { + // HACK: Need better `OsStr` manipulation + format!("--{}={}", flag, os.to_string_lossy()).into() + }), + ) + } + } else { + completions.extend( + crate::generator::utils::longs_and_visible_aliases(cmd) + .into_iter() + .filter_map(|f| { + f.starts_with(flag).then(|| format!("--{}", f).into()) + }), + ); + } + } + } else if arg.is_escape() || arg.is_stdio() || arg.is_empty() { + // HACK: Assuming knowledge of is_escape / is_stdio + completions.extend( + crate::generator::utils::longs_and_visible_aliases(cmd) + .into_iter() + .map(|f| format!("--{}", f).into()), + ); + } + + if arg.is_empty() || arg.is_stdio() || arg.is_short() { + // HACK: Assuming knowledge of is_stdio + completions.extend( + crate::generator::utils::shorts_and_visible_aliases(cmd) + .into_iter() + // HACK: Need better `OsStr` manipulation + .map(|f| format!("{}{}", arg.to_value_os().to_str_lossy(), f).into()), + ); + } + } + + if let Some(positional) = cmd + .get_positionals() + .find(|p| p.get_index() == Some(pos_index)) + { + completions.extend(complete_arg_value(arg.to_value(), positional, current_dir)); + } + + if let Ok(value) = arg.to_value() { + completions.extend(complete_subcommand(value, cmd)); + } + + Ok(completions) + } + + fn complete_arg_value( + value: Result<&str, &clap_lex::RawOsStr>, + arg: &clap::Arg, + current_dir: Option<&std::path::Path>, + ) -> Vec { + let mut values = Vec::new(); + debug!("complete_arg_value: arg={:?}, value={:?}", arg, value); + + if let Some(possible_values) = crate::generator::utils::possible_values(arg) { + if let Ok(value) = value { + values.extend(possible_values.into_iter().filter_map(|p| { + let name = p.get_name(); + name.starts_with(value).then(|| name.into()) + })); + } + } else { + let value_os = match value { + Ok(value) => clap_lex::RawOsStr::from_str(value), + Err(value_os) => value_os, + }; + match arg.get_value_hint() { + clap::ValueHint::Other => { + // Should not complete + } + clap::ValueHint::Unknown | clap::ValueHint::AnyPath => { + values.extend(complete_path(value_os, current_dir, |_| true)); + } + clap::ValueHint::FilePath => { + values.extend(complete_path(value_os, current_dir, |p| p.is_file())); + } + clap::ValueHint::DirPath => { + values.extend(complete_path(value_os, current_dir, |p| p.is_dir())); + } + clap::ValueHint::ExecutablePath => { + use is_executable::IsExecutable; + values.extend(complete_path(value_os, current_dir, |p| p.is_executable())); + } + clap::ValueHint::CommandName + | clap::ValueHint::CommandString + | clap::ValueHint::CommandWithArguments + | clap::ValueHint::Username + | clap::ValueHint::Hostname + | clap::ValueHint::Url + | clap::ValueHint::EmailAddress => { + // No completion implementation + } + _ => { + // Safe-ish fallback + values.extend(complete_path(value_os, current_dir, |_| true)); + } + } + values.sort(); + } + + values + } + + fn complete_path( + value_os: &clap_lex::RawOsStr, + current_dir: Option<&std::path::Path>, + is_wanted: impl Fn(&std::path::Path) -> bool, + ) -> Vec { + let mut completions = Vec::new(); + + let current_dir = match current_dir { + Some(current_dir) => current_dir, + None => { + // Can't complete without a `current_dir` + return Vec::new(); + } + }; + let (existing, prefix) = value_os + .split_once('\\') + .unwrap_or((clap_lex::RawOsStr::from_str(""), value_os)); + let root = current_dir.join(existing.to_os_str()); + debug!("complete_path: root={:?}, prefix={:?}", root, prefix); + + for entry in std::fs::read_dir(&root) + .ok() + .into_iter() + .flatten() + .filter_map(Result::ok) + { + let raw_file_name = clap_lex::RawOsString::new(entry.file_name()); + if !raw_file_name.starts_with_os(prefix) { + continue; + } + + if entry.metadata().map(|m| m.is_dir()).unwrap_or(false) { + let path = entry.path(); + let mut suggestion = pathdiff::diff_paths(&path, current_dir).unwrap_or(path); + suggestion.push(""); // Ensure trailing `/` + completions.push(suggestion.as_os_str().to_owned()); + } else { + let path = entry.path(); + if is_wanted(&path) { + let suggestion = pathdiff::diff_paths(&path, current_dir).unwrap_or(path); + completions.push(suggestion.as_os_str().to_owned()); + } + } + } + + completions + } + + fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec { + debug!( + "complete_subcommand: cmd={:?}, value={:?}", + cmd.get_name(), + value + ); + + let mut scs = crate::generator::utils::all_subcommands(cmd) + .into_iter() + .filter(|x| x.0.starts_with(value)) + .map(|x| OsString::from(&x.0)) + .collect::>(); + scs.sort(); + scs.dedup(); + scs + } +} diff --git a/vendor/clap_complete/src/generator/mod.rs b/vendor/clap_complete/src/generator/mod.rs index 2d00c281d..c025697ed 100644 --- a/vendor/clap_complete/src/generator/mod.rs +++ b/vendor/clap_complete/src/generator/mod.rs @@ -83,10 +83,8 @@ pub trait Generator { /// /// ``` /// // src/cli.rs -/// -/// use clap::{Command, Arg}; -/// -/// pub fn build_cli() -> Command<'static> { +/// # use clap::{Command, Arg, ArgAction}; +/// pub fn build_cli() -> Command { /// Command::new("compl") /// .about("Tests completions") /// .arg(Arg::new("file") @@ -95,7 +93,7 @@ pub trait Generator { /// .about("tests things") /// .arg(Arg::new("case") /// .long("case") -/// .takes_value(true) +/// .action(ArgAction::Set) /// .help("the case to test"))) /// } /// ``` @@ -195,7 +193,7 @@ where /// /// # Examples /// -/// Assuming a separate `cli.rs` like the [example above](generate_to()), +/// Assuming a separate `cli.rs` like the [`generate_to` example](generate_to()), /// we can let users generate a completion script using a command: /// /// ```ignore @@ -236,7 +234,7 @@ where G: Generator, S: Into, { - cmd._build_all(); + cmd.build(); gen.generate(cmd, buf) } diff --git a/vendor/clap_complete/src/generator/utils.rs b/vendor/clap_complete/src/generator/utils.rs index b8aaa4bd5..ca76d189b 100644 --- a/vendor/clap_complete/src/generator/utils.rs +++ b/vendor/clap_complete/src/generator/utils.rs @@ -19,10 +19,7 @@ pub fn all_subcommands(cmd: &Command) -> Vec<(String, String)> { /// Finds the subcommand [`clap::Command`] from the given [`clap::Command`] with the given path. /// /// **NOTE:** `path` should not contain the root `bin_name`. -pub fn find_subcommand_with_path<'help, 'cmd>( - p: &'cmd Command<'help>, - path: Vec<&str>, -) -> &'cmd Command<'help> { +pub fn find_subcommand_with_path<'cmd>(p: &'cmd Command, path: Vec<&str>) -> &'cmd Command { let mut cmd = p; for sc in path { @@ -42,10 +39,6 @@ pub fn subcommands(p: &Command) -> Vec<(String, String)> { let mut subcmds = vec![]; - if !p.has_subcommands() { - return subcmds; - } - for sc in p.get_subcommands() { let sc_bin_name = sc.get_bin_name().unwrap(); @@ -62,7 +55,7 @@ pub fn subcommands(p: &Command) -> Vec<(String, String)> { } /// Gets all the short options, their visible aliases and flags of a [`clap::Command`]. -/// Includes `h` and `V` depending on the [`clap::AppSettings`]. +/// Includes `h` and `V` depending on the [`clap::Command`] settings. pub fn shorts_and_visible_aliases(p: &Command) -> Vec { debug!("shorts: name={}", p.get_name()); @@ -87,7 +80,7 @@ pub fn shorts_and_visible_aliases(p: &Command) -> Vec { } /// Gets all the long options, their visible aliases and flags of a [`clap::Command`]. -/// Includes `help` and `version` depending on the [`clap::AppSettings`]. +/// Includes `help` and `version` depending on the [`clap::Command`] settings. pub fn longs_and_visible_aliases(p: &Command) -> Vec { debug!("longs: name={}", p.get_name()); @@ -117,22 +110,33 @@ pub fn longs_and_visible_aliases(p: &Command) -> Vec { } /// Gets all the flags of a [`clap::Command`](Command). -/// Includes `help` and `version` depending on the [`clap::AppSettings`]. -pub fn flags<'help>(p: &Command<'help>) -> Vec> { +/// Includes `help` and `version` depending on the [`clap::Command`] settings. +pub fn flags(p: &Command) -> Vec { debug!("flags: name={}", p.get_name()); p.get_arguments() - .filter(|a| !a.is_takes_value_set() && !a.is_positional()) + .filter(|a| !a.get_num_args().expect("built").takes_values() && !a.is_positional()) .cloned() .collect() } +/// Get the possible values for completion +pub fn possible_values(a: &Arg) -> Option> { + if !a.get_num_args().expect("built").takes_values() { + None + } else { + a.get_value_parser() + .possible_values() + .map(|pvs| pvs.collect()) + } +} + #[cfg(test)] mod tests { use super::*; use clap::Arg; - use pretty_assertions::assert_eq; + use clap::ArgAction; - fn common_app() -> Command<'static> { + fn common_app() -> Command { Command::new("myapp") .subcommand( Command::new("test").subcommand(Command::new("config")).arg( @@ -141,6 +145,7 @@ mod tests { .short_alias('c') .visible_short_alias('p') .long("file") + .action(ArgAction::SetTrue) .visible_alias("path"), ), ) @@ -148,17 +153,17 @@ mod tests { .bin_name("my-cmd") } - fn built() -> Command<'static> { + fn built() -> Command { let mut cmd = common_app(); - cmd._build_all(); + cmd.build(); cmd } - fn built_with_version() -> Command<'static> { + fn built_with_version() -> Command { let mut cmd = common_app().version("3.0"); - cmd._build_all(); + cmd.build(); cmd } @@ -188,6 +193,12 @@ mod tests { ("help".to_string(), "my-cmd help".to_string()), ("config".to_string(), "my-cmd test config".to_string()), ("help".to_string(), "my-cmd test help".to_string()), + ("config".to_string(), "my-cmd test help config".to_string()), + ("help".to_string(), "my-cmd test help help".to_string()), + ("test".to_string(), "my-cmd help test".to_string()), + ("hello".to_string(), "my-cmd help hello".to_string()), + ("help".to_string(), "my-cmd help help".to_string()), + ("config".to_string(), "my-cmd help test config".to_string()), ] ); } diff --git a/vendor/clap_complete/src/lib.rs b/vendor/clap_complete/src/lib.rs index a22ff9ac3..80fead4a5 100644 --- a/vendor/clap_complete/src/lib.rs +++ b/vendor/clap_complete/src/lib.rs @@ -22,11 +22,11 @@ //! ## Example //! //! ```rust,no_run -//! use clap::{Command, AppSettings, Arg, ValueHint}; +//! use clap::{Command, Arg, ValueHint, value_parser, ArgAction}; //! use clap_complete::{generate, Generator, Shell}; //! use std::io; //! -//! fn build_cli() -> Command<'static> { +//! fn build_cli() -> Command { //! Command::new("example") //! .arg(Arg::new("file") //! .help("some input file") @@ -35,7 +35,8 @@ //! .arg( //! Arg::new("generator") //! .long("generate") -//! .possible_values(Shell::possible_values()), +//! .action(ArgAction::Set) +//! .value_parser(value_parser!(Shell)), //! ) //! } //! @@ -46,7 +47,7 @@ //! fn main() { //! let matches = build_cli().get_matches(); //! -//! if let Ok(generator) = matches.value_of_t::("generator") { +//! 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); @@ -68,3 +69,6 @@ pub use generator::generate; pub use generator::generate_to; pub use generator::Generator; pub use shells::Shell; + +#[cfg(feature = "unstable-dynamic")] +pub mod dynamic; diff --git a/vendor/clap_complete/src/macros.rs b/vendor/clap_complete/src/macros.rs index def051434..bc6979460 100644 --- a/vendor/clap_complete/src/macros.rs +++ b/vendor/clap_complete/src/macros.rs @@ -10,8 +10,8 @@ macro_rules! w { #[cfg(feature = "debug")] macro_rules! debug { ($($arg:tt)*) => { - print!("[{:>w$}] \t", module_path!(), w = 28); - println!($($arg)*) + eprint!("[{:>w$}] \t", module_path!(), w = 28); + eprintln!($($arg)*) } } diff --git a/vendor/clap_complete/src/shells/bash.rs b/vendor/clap_complete/src/shells/bash.rs index 08bf1190c..e110537e5 100644 --- a/vendor/clap_complete/src/shells/bash.rs +++ b/vendor/clap_complete/src/shells/bash.rs @@ -31,8 +31,8 @@ impl Generator for Bash { for i in ${{COMP_WORDS[@]}} do - case \"${{i}}\" in - \"$1\") + case \"${{cmd}},${{i}}\" in + \",$1\") cmd=\"{cmd}\" ;;{subcmds} *) @@ -75,26 +75,52 @@ complete -F _{name} -o bashdefault -o default {name} fn all_subcommands(cmd: &Command) -> String { debug!("all_subcommands"); - let mut subcmds = vec![String::new()]; - let mut scs = utils::all_subcommands(cmd) - .iter() - .map(|x| x.0.clone()) - .collect::>(); - - scs.sort(); - scs.dedup(); + fn add_command( + parent_fn_name: &str, + cmd: &Command, + subcmds: &mut Vec<(String, String, String)>, + ) { + let fn_name = format!( + "{parent_fn_name}__{cmd_name}", + parent_fn_name = parent_fn_name, + cmd_name = cmd.get_name().to_string().replace('-', "__") + ); + subcmds.push(( + parent_fn_name.to_string(), + cmd.get_name().to_string(), + fn_name.clone(), + )); + for alias in cmd.get_visible_aliases() { + subcmds.push(( + parent_fn_name.to_string(), + alias.to_string(), + fn_name.clone(), + )); + } + for subcmd in cmd.get_subcommands() { + add_command(&fn_name, subcmd, subcmds); + } + } + let mut subcmds = vec![]; + let fn_name = cmd.get_name().replace('-', "__"); + for subcmd in cmd.get_subcommands() { + add_command(&fn_name, subcmd, &mut subcmds); + } + subcmds.sort(); - subcmds.extend(scs.iter().map(|sc| { - format!( - "{name}) - cmd+=\"__{fn_name}\" + let mut cases = vec![String::new()]; + for (parent_fn_name, name, fn_name) in subcmds { + cases.push(format!( + "{parent_fn_name},{name}) + cmd=\"{fn_name}\" ;;", - name = sc, - fn_name = sc.replace('-', "__") - ) - })); + parent_fn_name = parent_fn_name, + name = name, + fn_name = fn_name, + )); + } - subcmds.join("\n ") + cases.join("\n ") } fn subcommand_details(cmd: &Command) -> String { @@ -125,9 +151,9 @@ fn subcommand_details(cmd: &Command) -> String { return 0 ;;", subcmd = sc.replace('-', "__"), - sc_opts = all_options_for_path(cmd, &*sc), + sc_opts = all_options_for_path(cmd, sc), level = sc.split("__").map(|_| 1).sum::(), - opts_details = option_details_for_path(cmd, &*sc) + opts_details = option_details_for_path(cmd, sc) ) })); @@ -174,12 +200,12 @@ fn option_details_for_path(cmd: &Command, path: &str) -> String { fn vals_for(o: &Arg) -> String { debug!("vals_for: o={}", o.get_id()); - if let Some(vals) = o.get_possible_values() { + if let Some(vals) = crate::generator::utils::possible_values(o) { format!( "$(compgen -W \"{}\" -- \"${{cur}}\")", vals.iter() - .filter(|pv| pv.is_hide_set()) - .map(PossibleValue::get_name) + .filter(|pv| !pv.is_hide_set()) + .map(|n| n.get_name()) .collect::>() .join(" ") ) @@ -201,7 +227,7 @@ fn all_options_for_path(cmd: &Command, path: &str) -> String { write!(&mut opts, "--{} ", long).unwrap(); } for pos in p.get_positionals() { - if let Some(vals) = pos.get_possible_values() { + if let Some(vals) = utils::possible_values(pos) { for value in vals { write!(&mut opts, "{} ", value.get_name()).unwrap(); } diff --git a/vendor/clap_complete/src/shells/elvish.rs b/vendor/clap_complete/src/shells/elvish.rs index 959372087..07da28348 100644 --- a/vendor/clap_complete/src/shells/elvish.rs +++ b/vendor/clap_complete/src/shells/elvish.rs @@ -1,5 +1,6 @@ use std::io::Write; +use clap::builder::StyledStr; use clap::*; use crate::generator::{utils, Generator}; @@ -19,8 +20,7 @@ impl Generator for Elvish { .get_bin_name() .expect("crate::generate should have set the bin_name"); - let mut names = vec![]; - let subcommands_cases = generate_inner(cmd, "", &mut names); + let subcommands_cases = generate_inner(cmd, ""); let result = format!( r#" @@ -59,18 +59,14 @@ fn escape_string(string: &str) -> String { string.replace('\'', "''") } -fn get_tooltip(help: Option<&str>, data: T) -> String { +fn get_tooltip(help: Option<&StyledStr>, data: T) -> String { match help { - Some(help) => escape_string(help), + Some(help) => escape_string(&help.to_string()), _ => data.to_string(), } } -fn generate_inner<'help>( - p: &Command<'help>, - previous_command_name: &str, - names: &mut Vec<&'help str>, -) -> String { +fn generate_inner(p: &Command, previous_command_name: &str) -> String { debug!("generate_inner"); let command_name = if previous_command_name.is_empty() { @@ -134,7 +130,7 @@ fn generate_inner<'help>( ); for subcommand in p.get_subcommands() { - let subcommand_subcommands_cases = generate_inner(subcommand, &command_name, names); + let subcommand_subcommands_cases = generate_inner(subcommand, &command_name); subcommands_cases.push_str(&subcommand_subcommands_cases); } diff --git a/vendor/clap_complete/src/shells/fish.rs b/vendor/clap_complete/src/shells/fish.rs index 9b516084b..fd2f3a4f8 100644 --- a/vendor/clap_complete/src/shells/fish.rs +++ b/vendor/clap_complete/src/shells/fish.rs @@ -27,8 +27,13 @@ impl Generator for Fish { } // Escape string inside single quotes -fn escape_string(string: &str) -> String { - string.replace('\\', "\\\\").replace('\'', "\\'") +fn escape_string(string: &str, escape_comma: bool) -> String { + let string = string.replace('\\', "\\\\").replace('\'', "\\'"); + if escape_comma { + string.replace(',', "\\,") + } else { + string + } } fn gen_fish_inner( @@ -88,12 +93,13 @@ fn gen_fish_inner( if let Some(longs) = option.get_long_and_visible_aliases() { for long in longs { - template.push_str(format!(" -l {}", escape_string(long)).as_str()); + template.push_str(format!(" -l {}", escape_string(long, false)).as_str()); } } if let Some(data) = option.get_help() { - template.push_str(format!(" -d '{}'", escape_string(data)).as_str()); + template + .push_str(format!(" -d '{}'", escape_string(&data.to_string(), false)).as_str()); } template.push_str(value_completion(option).as_str()); @@ -113,12 +119,13 @@ fn gen_fish_inner( if let Some(longs) = flag.get_long_and_visible_aliases() { for long in longs { - template.push_str(format!(" -l {}", escape_string(long)).as_str()); + template.push_str(format!(" -l {}", escape_string(long, false)).as_str()); } } if let Some(data) = flag.get_help() { - template.push_str(format!(" -d '{}'", escape_string(data)).as_str()); + template + .push_str(format!(" -d '{}'", escape_string(&data.to_string(), false)).as_str()); } buffer.push_str(template.as_str()); @@ -132,7 +139,7 @@ fn gen_fish_inner( template.push_str(format!(" -a \"{}\"", &subcommand.get_name()).as_str()); if let Some(data) = subcommand.get_about() { - template.push_str(format!(" -d '{}'", escape_string(data)).as_str()) + template.push_str(format!(" -d '{}'", escape_string(&data.to_string(), false)).as_str()) } buffer.push_str(template.as_str()); @@ -148,11 +155,11 @@ fn gen_fish_inner( } fn value_completion(option: &Arg) -> String { - if !option.is_takes_value_set() { + if !option.get_num_args().expect("built").takes_values() { return "".to_string(); } - if let Some(data) = option.get_possible_values() { + if let Some(data) = crate::generator::utils::possible_values(option) { // We return the possible values with their own empty description e.g. {a\t,b\t} // this makes sure that a and b don't get the description of the option or argument format!( @@ -163,8 +170,8 @@ fn value_completion(option: &Arg) -> String { } else { Some(format!( "{}\t{}", - escape_string(value.get_name()).as_str(), - escape_string(value.get_help().unwrap_or_default()).as_str() + escape_string(value.get_name(), true).as_str(), + escape_string(&value.get_help().unwrap_or_default().to_string(), true) )) }) .collect::>() diff --git a/vendor/clap_complete/src/shells/powershell.rs b/vendor/clap_complete/src/shells/powershell.rs index d35e61c7d..0d3a2a55f 100644 --- a/vendor/clap_complete/src/shells/powershell.rs +++ b/vendor/clap_complete/src/shells/powershell.rs @@ -1,5 +1,6 @@ use std::io::Write; +use clap::builder::StyledStr; use clap::*; use crate::generator::{utils, Generator}; @@ -19,8 +20,7 @@ impl Generator for PowerShell { .get_bin_name() .expect("crate::generate should have set the bin_name"); - let mut names = vec![]; - let subcommands_cases = generate_inner(cmd, "", &mut names); + let subcommands_cases = generate_inner(cmd, ""); let result = format!( r#" @@ -64,18 +64,14 @@ fn escape_string(string: &str) -> String { string.replace('\'', "''") } -fn get_tooltip(help: Option<&str>, data: T) -> String { +fn get_tooltip(help: Option<&StyledStr>, data: T) -> String { match help { - Some(help) => escape_string(help), + Some(help) => escape_string(&help.to_string()), _ => data.to_string(), } } -fn generate_inner<'help>( - p: &Command<'help>, - previous_command_name: &str, - names: &mut Vec<&'help str>, -) -> String { +fn generate_inner(p: &Command, previous_command_name: &str) -> String { debug!("generate_inner"); let command_name = if previous_command_name.is_empty() { @@ -170,7 +166,7 @@ fn generate_inner<'help>( ); for subcommand in p.get_subcommands() { - let subcommand_subcommands_cases = generate_inner(subcommand, &command_name, names); + let subcommand_subcommands_cases = generate_inner(subcommand, &command_name); subcommands_cases.push_str(&subcommand_subcommands_cases); } diff --git a/vendor/clap_complete/src/shells/shell.rs b/vendor/clap_complete/src/shells/shell.rs index 63063bb7c..f6e70f575 100644 --- a/vendor/clap_complete/src/shells/shell.rs +++ b/vendor/clap_complete/src/shells/shell.rs @@ -1,7 +1,9 @@ use std::fmt::Display; +use std::path::Path; use std::str::FromStr; -use clap::{ArgEnum, PossibleValue}; +use clap::builder::PossibleValue; +use clap::ValueEnum; use crate::shells; use crate::Generator; @@ -22,15 +24,6 @@ pub enum Shell { Zsh, } -impl Shell { - /// Report all `possible_values` - pub fn possible_values() -> impl Iterator> { - Shell::value_variants() - .iter() - .filter_map(ArgEnum::to_possible_value) - } -} - impl Display for Shell { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.to_possible_value() @@ -54,7 +47,7 @@ impl FromStr for Shell { } // Hand-rolled so it can work even when `derive` feature is disabled -impl ArgEnum for Shell { +impl ValueEnum for Shell { fn value_variants<'a>() -> &'a [Self] { &[ Shell::Bash, @@ -65,7 +58,7 @@ impl ArgEnum for Shell { ] } - fn to_possible_value<'a>(&self) -> Option> { + fn to_possible_value<'a>(&self) -> Option { Some(match self { Shell::Bash => PossibleValue::new("bash"), Shell::Elvish => PossibleValue::new("elvish"), @@ -97,3 +90,66 @@ impl Generator for Shell { } } } + +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: P) -> 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 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 { + 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 { + 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, + } +} diff --git a/vendor/clap_complete/src/shells/zsh.rs b/vendor/clap_complete/src/shells/zsh.rs index 2b64739ce..580de77a2 100644 --- a/vendor/clap_complete/src/shells/zsh.rs +++ b/vendor/clap_complete/src/shells/zsh.rs @@ -153,12 +153,10 @@ fn subcommands_of(p: &Command) -> String { let text = format!( "'{name}:{help}' \\", name = name, - help = escape_help(subcommand.get_about().unwrap_or("")) + help = escape_help(&subcommand.get_about().unwrap_or_default().to_string()) ); - if !text.is_empty() { - ret.push(text); - } + ret.push(text); } // The subcommands @@ -234,7 +232,7 @@ fn get_subcommands_of(parent: &Command) -> String { ); let mut segments = vec![format!("({})", name)]; let subcommand_args = get_args_of( - parser_of(parent, &*bin_name).expect(INTERNAL_ERROR_MSG), + parser_of(parent, bin_name).expect(INTERNAL_ERROR_MSG), Some(parent), ); @@ -243,7 +241,7 @@ fn get_subcommands_of(parent: &Command) -> String { } // Get the help text of all child subcommands. - let children = get_subcommands_of(parser_of(parent, &*bin_name).expect(INTERNAL_ERROR_MSG)); + let children = get_subcommands_of(parser_of(parent, bin_name).expect(INTERNAL_ERROR_MSG)); if !children.is_empty() { segments.push(children); @@ -280,13 +278,10 @@ esac", // // Given the bin_name "a b c" and the Command for "a" this returns the "c" Command. // Given the bin_name "a b c" and the Command for "b" this returns the "c" Command. -fn parser_of<'help, 'cmd>( - parent: &'cmd Command<'help>, - bin_name: &str, -) -> Option<&'cmd Command<'help>> { +fn parser_of<'cmd>(parent: &'cmd Command, bin_name: &str) -> Option<&'cmd Command> { debug!("parser_of: p={}, bin_name={}", parent.get_name(), bin_name); - if bin_name == parent.get_bin_name().unwrap_or(&String::new()) { + if bin_name == parent.get_bin_name().unwrap_or_default() { return Some(parent); } @@ -359,7 +354,7 @@ fn get_args_of(parent: &Command, p_global: Option<&Command>) -> String { // Uses either `possible_vals` or `value_hint` to give hints about possible argument values fn value_completion(arg: &Arg) -> Option { - if let Some(values) = &arg.get_possible_values() { + if let Some(values) = crate::generator::utils::possible_values(arg) { if values .iter() .any(|value| !value.is_hide_set() && value.get_help().is_some()) @@ -375,7 +370,8 @@ fn value_completion(arg: &Arg) -> Option { Some(format!( r#"{name}\:"{tooltip}""#, name = escape_value(value.get_name()), - tooltip = value.get_help().map(escape_help).unwrap_or_default() + tooltip = + escape_help(&value.get_help().unwrap_or_default().to_string()), )) } }) @@ -388,7 +384,7 @@ fn value_completion(arg: &Arg) -> Option { values .iter() .filter(|pv| !pv.is_hide_set()) - .map(PossibleValue::get_name) + .map(|n| n.get_name()) .collect::>() .join(" ") )) @@ -448,10 +444,10 @@ fn write_opts_of(p: &Command, p_global: Option<&Command>) -> String { for o in p.get_opts() { debug!("write_opts_of:iter: o={}", o.get_id()); - let help = o.get_help().map_or(String::new(), escape_help); + let help = escape_help(&o.get_help().unwrap_or_default().to_string()); let conflicts = arg_conflicts(p, o, p_global); - let multiple = if o.is_multiple_occurrences_set() { + let multiple = if let ArgAction::Count | ArgAction::Append = o.get_action() { "*" } else { "" @@ -465,10 +461,7 @@ fn write_opts_of(p: &Command, p_global: Option<&Command>) -> String { Some(val) => format!(":{}:{}", vn, val), None => format!(":{}: ", vn), }; - let vc = match o.get_num_vals() { - Some(num_vals) => vc.repeat(num_vals), - None => vc, - }; + let vc = vc.repeat(o.get_num_args().expect("built").min_values()); if let Some(shorts) = o.get_short_and_visible_aliases() { for short in shorts { @@ -551,10 +544,10 @@ fn write_flags_of(p: &Command, p_global: Option<&Command>) -> String { for f in utils::flags(p) { debug!("write_flags_of:iter: f={}", f.get_id()); - let help = f.get_help().map_or(String::new(), escape_help); + let help = escape_help(&f.get_help().unwrap_or_default().to_string()); let conflicts = arg_conflicts(p, &f, p_global); - let multiple = if f.is_multiple_occurrences_set() { + let multiple = if let ArgAction::Count | ArgAction::Append = f.get_action() { "*" } else { "" @@ -632,7 +625,8 @@ fn write_positionals_of(p: &Command) -> String { for arg in p.get_positionals() { debug!("write_positionals_of:iter: arg={}", arg.get_id()); - let cardinality = if arg.is_multiple_values_set() || arg.is_multiple_occurrences_set() { + let num_args = arg.get_num_args().expect("built"); + let cardinality = if num_args.max_values() > 1 { "*:" } else if !arg.is_required_set() { ":" @@ -646,12 +640,13 @@ fn write_positionals_of(p: &Command) -> String { name = arg.get_id(), help = arg .get_help() - .map_or("".to_owned(), |v| " -- ".to_owned() + v) + .map(|s| s.to_string()) + .map_or("".to_owned(), |v| " -- ".to_owned() + &v) .replace('[', "\\[") .replace(']', "\\]") .replace('\'', "'\\''") .replace(':', "\\:"), - value_completion = value_completion(arg).unwrap_or_else(|| "".to_string()) + value_completion = value_completion(arg).unwrap_or_default() ); debug!("write_positionals_of:iter: Wrote...{}", a); -- cgit v1.2.3