diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /vendor/clap_complete/src | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/clap_complete/src')
-rw-r--r-- | vendor/clap_complete/src/generator/mod.rs | 242 | ||||
-rw-r--r-- | vendor/clap_complete/src/generator/utils.rs | 267 | ||||
-rw-r--r-- | vendor/clap_complete/src/lib.rs | 70 | ||||
-rw-r--r-- | vendor/clap_complete/src/macros.rs | 21 | ||||
-rw-r--r-- | vendor/clap_complete/src/shells/bash.rs | 218 | ||||
-rw-r--r-- | vendor/clap_complete/src/shells/elvish.rs | 142 | ||||
-rw-r--r-- | vendor/clap_complete/src/shells/fish.rs | 192 | ||||
-rw-r--r-- | vendor/clap_complete/src/shells/mod.rs | 15 | ||||
-rw-r--r-- | vendor/clap_complete/src/shells/powershell.rs | 178 | ||||
-rw-r--r-- | vendor/clap_complete/src/shells/shell.rs | 99 | ||||
-rw-r--r-- | vendor/clap_complete/src/shells/zsh.rs | 663 |
11 files changed, 2107 insertions, 0 deletions
diff --git a/vendor/clap_complete/src/generator/mod.rs b/vendor/clap_complete/src/generator/mod.rs new file mode 100644 index 000000000..2d00c281d --- /dev/null +++ b/vendor/clap_complete/src/generator/mod.rs @@ -0,0 +1,242 @@ +//! Shell completion machinery + +pub mod utils; + +use std::ffi::OsString; +use std::fs::File; +use std::io::Error; +use std::io::Write; +use std::path::PathBuf; + +use clap::Command; + +/// Generator trait which can be used to write generators +pub trait Generator { + /// Returns the file name that is created when this generator is called during compile time. + /// + /// # Panics + /// + /// May panic when called outside of the context of [`generate`] or [`generate_to`] + /// + /// # Examples + /// + /// ``` + /// # use std::io::Write; + /// # use clap::Command; + /// use clap_complete::Generator; + /// + /// pub struct Fish; + /// + /// impl Generator for Fish { + /// # fn generate(&self, cmd: &Command, buf: &mut dyn Write) {} + /// fn file_name(&self, name: &str) -> String { + /// format!("{}.fish", name) + /// } + /// } + /// ``` + fn file_name(&self, name: &str) -> String; + + /// Generates output out of [`clap::Command`](Command). + /// + /// # Panics + /// + /// May panic when called outside of the context of [`generate`] or [`generate_to`] + /// + /// # Examples + /// + /// The following example generator displays the [`clap::Command`](Command) + /// as if it is printed using [`std::println`]. + /// + /// ``` + /// use std::{io::Write, fmt::write}; + /// use clap::Command; + /// use clap_complete::Generator; + /// + /// pub struct ClapDebug; + /// + /// impl Generator for ClapDebug { + /// fn generate(&self, cmd: &Command, buf: &mut dyn Write) { + /// write!(buf, "{}", cmd).unwrap(); + /// } + /// # fn file_name(&self, name: &str) -> String { + /// # name.into() + /// # } + /// } + /// ``` + fn generate(&self, cmd: &Command, buf: &mut dyn Write); +} + +/// Generate a completions file for a specified shell at compile-time. +/// +/// **NOTE:** to generate the file at compile time you must use a `build.rs` "Build Script" or a +/// [`cargo-xtask`](https://github.com/matklad/cargo-xtask) +/// +/// # Examples +/// +/// The following example generates a bash completion script via a `build.rs` script. In this +/// simple example, we'll demo a very small application with only a single subcommand and two +/// args. Real applications could be many multiple levels deep in subcommands, and have tens or +/// potentially hundreds of arguments. +/// +/// First, it helps if we separate out our `Command` definition into a separate file. Whether you +/// do this as a function, or bare Command definition is a matter of personal preference. +/// +/// ``` +/// // src/cli.rs +/// +/// use clap::{Command, Arg}; +/// +/// pub fn build_cli() -> Command<'static> { +/// Command::new("compl") +/// .about("Tests completions") +/// .arg(Arg::new("file") +/// .help("some input file")) +/// .subcommand(Command::new("test") +/// .about("tests things") +/// .arg(Arg::new("case") +/// .long("case") +/// .takes_value(true) +/// .help("the case to test"))) +/// } +/// ``` +/// +/// In our regular code, we can simply call this `build_cli()` function, then call +/// `get_matches()`, or any of the other normal methods directly after. For example: +/// +/// ```ignore +/// // src/main.rs +/// +/// mod cli; +/// +/// fn main() { +/// let _m = cli::build_cli().get_matches(); +/// +/// // normal logic continues... +/// } +/// ``` +/// +/// Next, we set up our `Cargo.toml` to use a `build.rs` build script. +/// +/// ```toml +/// # Cargo.toml +/// build = "build.rs" +/// +/// [dependencies] +/// clap = "*" +/// +/// [build-dependencies] +/// clap = "*" +/// clap_complete = "*" +/// ``` +/// +/// Next, we place a `build.rs` in our project root. +/// +/// ```ignore +/// use clap_complete::{generate_to, shells::Bash}; +/// use std::env; +/// use std::io::Error; +/// +/// include!("src/cli.rs"); +/// +/// fn main() -> Result<(), Error> { +/// let outdir = match env::var_os("OUT_DIR") { +/// None => return Ok(()), +/// Some(outdir) => outdir, +/// }; +/// +/// let mut cmd = build_cli(); +/// let path = generate_to( +/// Bash, +/// &mut cmd, // We need to specify what generator to use +/// "myapp", // We need to specify the bin name manually +/// outdir, // We need to specify where to write to +/// )?; +/// +/// println!("cargo:warning=completion file is generated: {:?}", path); +/// +/// Ok(()) +/// } +/// ``` +/// +/// Now, once we compile there will be a `{bin_name}.bash` file in the directory. +/// Assuming we compiled with debug mode, it would be somewhere similar to +/// `<project>/target/debug/build/myapp-<hash>/out/myapp.bash`. +/// +/// **NOTE:** Please look at the individual [shells][crate::shells] +/// to see the name of the files generated. +pub fn generate_to<G, S, T>( + gen: G, + cmd: &mut clap::Command, + bin_name: S, + out_dir: T, +) -> Result<PathBuf, Error> +where + G: Generator, + S: Into<String>, + T: Into<OsString>, +{ + cmd.set_bin_name(bin_name); + + let out_dir = PathBuf::from(out_dir.into()); + let file_name = gen.file_name(cmd.get_bin_name().unwrap()); + + let path = out_dir.join(file_name); + let mut file = File::create(&path)?; + + _generate::<G, S>(gen, cmd, &mut file); + Ok(path) +} + +/// Generate a completions file for a specified shell at runtime. +/// +/// Until `cargo install` can install extra files like a completion script, this may be +/// used e.g. in a command that outputs the contents of the completion script, to be +/// redirected into a file by the user. +/// +/// # Examples +/// +/// Assuming a separate `cli.rs` like the [example above](generate_to()), +/// we can let users generate a completion script using a command: +/// +/// ```ignore +/// // src/main.rs +/// +/// mod cli; +/// use std::io; +/// use clap_complete::{generate, shells::Bash}; +/// +/// fn main() { +/// let matches = cli::build_cli().get_matches(); +/// +/// if matches.is_present("generate-bash-completions") { +/// generate(Bash, &mut cli::build_cli(), "myapp", &mut io::stdout()); +/// } +/// +/// // normal logic continues... +/// } +/// +/// ``` +/// +/// Usage: +/// +/// ```shell +/// $ myapp generate-bash-completions > /usr/share/bash-completion/completions/myapp.bash +/// ``` +pub fn generate<G, S>(gen: G, cmd: &mut clap::Command, bin_name: S, buf: &mut dyn Write) +where + G: Generator, + S: Into<String>, +{ + cmd.set_bin_name(bin_name); + _generate::<G, S>(gen, cmd, buf) +} + +fn _generate<G, S>(gen: G, cmd: &mut clap::Command, buf: &mut dyn Write) +where + G: Generator, + S: Into<String>, +{ + cmd._build_all(); + + gen.generate(cmd, buf) +} diff --git a/vendor/clap_complete/src/generator/utils.rs b/vendor/clap_complete/src/generator/utils.rs new file mode 100644 index 000000000..b8aaa4bd5 --- /dev/null +++ b/vendor/clap_complete/src/generator/utils.rs @@ -0,0 +1,267 @@ +//! Helpers for writing generators + +use clap::{Arg, Command}; + +/// Gets all subcommands including child subcommands in the form of `("name", "bin_name")`. +/// +/// Subcommand `rustup toolchain install` would be converted to +/// `("install", "rustup toolchain install")`. +pub fn all_subcommands(cmd: &Command) -> Vec<(String, String)> { + let mut subcmds: Vec<_> = subcommands(cmd); + + for sc_v in cmd.get_subcommands().map(all_subcommands) { + subcmds.extend(sc_v); + } + + subcmds +} + +/// 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> { + let mut cmd = p; + + for sc in path { + cmd = cmd.find_subcommand(sc).unwrap(); + } + + cmd +} + +/// Gets subcommands of [`clap::Command`] in the form of `("name", "bin_name")`. +/// +/// Subcommand `rustup toolchain install` would be converted to +/// `("install", "rustup toolchain install")`. +pub fn subcommands(p: &Command) -> Vec<(String, String)> { + debug!("subcommands: name={}", p.get_name()); + debug!("subcommands: Has subcommands...{:?}", p.has_subcommands()); + + 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(); + + debug!( + "subcommands:iter: name={}, bin_name={}", + sc.get_name(), + sc_bin_name + ); + + subcmds.push((sc.get_name().to_string(), sc_bin_name.to_string())); + } + + subcmds +} + +/// Gets all the short options, their visible aliases and flags of a [`clap::Command`]. +/// Includes `h` and `V` depending on the [`clap::AppSettings`]. +pub fn shorts_and_visible_aliases(p: &Command) -> Vec<char> { + debug!("shorts: name={}", p.get_name()); + + p.get_arguments() + .filter_map(|a| { + if !a.is_positional() { + if a.get_visible_short_aliases().is_some() && a.get_short().is_some() { + let mut shorts_and_visible_aliases = a.get_visible_short_aliases().unwrap(); + shorts_and_visible_aliases.push(a.get_short().unwrap()); + Some(shorts_and_visible_aliases) + } else if a.get_visible_short_aliases().is_none() && a.get_short().is_some() { + Some(vec![a.get_short().unwrap()]) + } else { + None + } + } else { + None + } + }) + .flatten() + .collect() +} + +/// Gets all the long options, their visible aliases and flags of a [`clap::Command`]. +/// Includes `help` and `version` depending on the [`clap::AppSettings`]. +pub fn longs_and_visible_aliases(p: &Command) -> Vec<String> { + debug!("longs: name={}", p.get_name()); + + p.get_arguments() + .filter_map(|a| { + if !a.is_positional() { + if a.get_visible_aliases().is_some() && a.get_long().is_some() { + let mut visible_aliases: Vec<_> = a + .get_visible_aliases() + .unwrap() + .into_iter() + .map(|s| s.to_string()) + .collect(); + visible_aliases.push(a.get_long().unwrap().to_string()); + Some(visible_aliases) + } else if a.get_visible_aliases().is_none() && a.get_long().is_some() { + Some(vec![a.get_long().unwrap().to_string()]) + } else { + None + } + } else { + None + } + }) + .flatten() + .collect() +} + +/// 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<Arg<'help>> { + debug!("flags: name={}", p.get_name()); + p.get_arguments() + .filter(|a| !a.is_takes_value_set() && !a.is_positional()) + .cloned() + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::Arg; + use pretty_assertions::assert_eq; + + fn common_app() -> Command<'static> { + Command::new("myapp") + .subcommand( + Command::new("test").subcommand(Command::new("config")).arg( + Arg::new("file") + .short('f') + .short_alias('c') + .visible_short_alias('p') + .long("file") + .visible_alias("path"), + ), + ) + .subcommand(Command::new("hello")) + .bin_name("my-cmd") + } + + fn built() -> Command<'static> { + let mut cmd = common_app(); + + cmd._build_all(); + cmd + } + + fn built_with_version() -> Command<'static> { + let mut cmd = common_app().version("3.0"); + + cmd._build_all(); + cmd + } + + #[test] + fn test_subcommands() { + let cmd = built_with_version(); + + assert_eq!( + subcommands(&cmd), + vec![ + ("test".to_string(), "my-cmd test".to_string()), + ("hello".to_string(), "my-cmd hello".to_string()), + ("help".to_string(), "my-cmd help".to_string()), + ] + ); + } + + #[test] + fn test_all_subcommands() { + let cmd = built_with_version(); + + assert_eq!( + all_subcommands(&cmd), + vec![ + ("test".to_string(), "my-cmd test".to_string()), + ("hello".to_string(), "my-cmd hello".to_string()), + ("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()), + ] + ); + } + + #[test] + fn test_find_subcommand_with_path() { + let cmd = built_with_version(); + let sc_app = find_subcommand_with_path(&cmd, "test config".split(' ').collect()); + + assert_eq!(sc_app.get_name(), "config"); + } + + #[test] + fn test_flags() { + let cmd = built_with_version(); + let actual_flags = flags(&cmd); + + assert_eq!(actual_flags.len(), 2); + assert_eq!(actual_flags[0].get_long(), Some("help")); + assert_eq!(actual_flags[1].get_long(), Some("version")); + + let sc_flags = flags(find_subcommand_with_path(&cmd, vec!["test"])); + + assert_eq!(sc_flags.len(), 2); + assert_eq!(sc_flags[0].get_long(), Some("file")); + assert_eq!(sc_flags[1].get_long(), Some("help")); + } + + #[test] + fn test_flag_subcommand() { + let cmd = built(); + let actual_flags = flags(&cmd); + + assert_eq!(actual_flags.len(), 1); + assert_eq!(actual_flags[0].get_long(), Some("help")); + + let sc_flags = flags(find_subcommand_with_path(&cmd, vec!["test"])); + + assert_eq!(sc_flags.len(), 2); + assert_eq!(sc_flags[0].get_long(), Some("file")); + assert_eq!(sc_flags[1].get_long(), Some("help")); + } + + #[test] + fn test_shorts() { + let cmd = built_with_version(); + let shorts = shorts_and_visible_aliases(&cmd); + + assert_eq!(shorts.len(), 2); + assert_eq!(shorts[0], 'h'); + assert_eq!(shorts[1], 'V'); + + let sc_shorts = shorts_and_visible_aliases(find_subcommand_with_path(&cmd, vec!["test"])); + + assert_eq!(sc_shorts.len(), 3); + assert_eq!(sc_shorts[0], 'p'); + assert_eq!(sc_shorts[1], 'f'); + assert_eq!(sc_shorts[2], 'h'); + } + + #[test] + fn test_longs() { + let cmd = built_with_version(); + let longs = longs_and_visible_aliases(&cmd); + + assert_eq!(longs.len(), 2); + assert_eq!(longs[0], "help"); + assert_eq!(longs[1], "version"); + + let sc_longs = longs_and_visible_aliases(find_subcommand_with_path(&cmd, vec!["test"])); + + assert_eq!(sc_longs.len(), 3); + assert_eq!(sc_longs[0], "path"); + assert_eq!(sc_longs[1], "file"); + assert_eq!(sc_longs[2], "help"); + } +} diff --git a/vendor/clap_complete/src/lib.rs b/vendor/clap_complete/src/lib.rs new file mode 100644 index 000000000..a22ff9ac3 --- /dev/null +++ b/vendor/clap_complete/src/lib.rs @@ -0,0 +1,70 @@ +// Copyright ⓒ 2015-2018 Kevin B. Knapp +// +// `clap_complete` is distributed under the terms of both the MIT license and the Apache License +// (Version 2.0). +// See the [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) files in this repository +// for more information. + +#![doc(html_logo_url = "https://raw.githubusercontent.com/clap-rs/clap/master/assets/clap.png")] +#![doc = include_str!("../README.md")] +#![warn(missing_docs, trivial_casts, unused_allocation, trivial_numeric_casts)] +#![forbid(unsafe_code)] +#![allow(clippy::needless_doctest_main)] + +//! ## 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. +//! +//! ## Example +//! +//! ```rust,no_run +//! use clap::{Command, AppSettings, Arg, ValueHint}; +//! use clap_complete::{generate, Generator, Shell}; +//! use std::io; +//! +//! fn build_cli() -> Command<'static> { +//! Command::new("example") +//! .arg(Arg::new("file") +//! .help("some input file") +//! .value_hint(ValueHint::AnyPath), +//! ) +//! .arg( +//! Arg::new("generator") +//! .long("generate") +//! .possible_values(Shell::possible_values()), +//! ) +//! } +//! +//! fn print_completions<G: Generator>(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 Ok(generator) = matches.value_of_t::<Shell>("generator") { +//! let mut cmd = build_cli(); +//! eprintln!("Generating completion file for {}...", generator); +//! print_completions(generator, &mut cmd); +//! } +//! } +//! ``` + +const INTERNAL_ERROR_MSG: &str = "Fatal internal error. Please consider filing a bug \ + report at https://github.com/clap-rs/clap/issues"; + +#[macro_use] +#[allow(missing_docs)] +mod macros; + +pub mod generator; +pub mod shells; + +pub use generator::generate; +pub use generator::generate_to; +pub use generator::Generator; +pub use shells::Shell; diff --git a/vendor/clap_complete/src/macros.rs b/vendor/clap_complete/src/macros.rs new file mode 100644 index 000000000..def051434 --- /dev/null +++ b/vendor/clap_complete/src/macros.rs @@ -0,0 +1,21 @@ +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)*) => { + print!("[{:>w$}] \t", module_path!(), w = 28); + println!($($arg)*) + } +} + +#[cfg(not(feature = "debug"))] +macro_rules! debug { + ($($arg:tt)*) => {}; +} diff --git a/vendor/clap_complete/src/shells/bash.rs b/vendor/clap_complete/src/shells/bash.rs new file mode 100644 index 000000000..08bf1190c --- /dev/null +++ b/vendor/clap_complete/src/shells/bash.rs @@ -0,0 +1,218 @@ +use std::{fmt::Write as _, io::Write}; + +use clap::*; + +use crate::generator::{utils, Generator}; + +/// Generate bash completion file +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Bash; + +impl Generator for Bash { + fn file_name(&self, name: &str) -> String { + format!("{}.bash", name) + } + + fn generate(&self, cmd: &Command, buf: &mut dyn Write) { + let bin_name = cmd + .get_bin_name() + .expect("crate::generate should have set the bin_name"); + + w!( + buf, + format!( + "_{name}() {{ + local i cur prev opts cmds + COMPREPLY=() + cur=\"${{COMP_WORDS[COMP_CWORD]}}\" + prev=\"${{COMP_WORDS[COMP_CWORD-1]}}\" + cmd=\"\" + opts=\"\" + + for i in ${{COMP_WORDS[@]}} + do + case \"${{i}}\" in + \"$1\") + cmd=\"{cmd}\" + ;;{subcmds} + *) + ;; + esac + done + + case \"${{cmd}}\" in + {cmd}) + opts=\"{name_opts}\" + if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq 1 ]] ; then + COMPREPLY=( $(compgen -W \"${{opts}}\" -- \"${{cur}}\") ) + return 0 + fi + case \"${{prev}}\" in{name_opts_details} + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W \"${{opts}}\" -- \"${{cur}}\") ) + return 0 + ;;{subcmd_details} + esac +}} + +complete -F _{name} -o bashdefault -o default {name} +", + name = bin_name, + cmd = bin_name.replace('-', "__"), + name_opts = all_options_for_path(cmd, bin_name), + name_opts_details = option_details_for_path(cmd, bin_name), + subcmds = all_subcommands(cmd), + subcmd_details = subcommand_details(cmd) + ) + .as_bytes() + ); + } +} + +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::<Vec<_>>(); + + scs.sort(); + scs.dedup(); + + subcmds.extend(scs.iter().map(|sc| { + format!( + "{name}) + cmd+=\"__{fn_name}\" + ;;", + name = sc, + fn_name = sc.replace('-', "__") + ) + })); + + subcmds.join("\n ") +} + +fn subcommand_details(cmd: &Command) -> String { + debug!("subcommand_details"); + + let mut subcmd_dets = vec![String::new()]; + let mut scs = utils::all_subcommands(cmd) + .iter() + .map(|x| x.1.replace(' ', "__")) + .collect::<Vec<_>>(); + + scs.sort(); + + subcmd_dets.extend(scs.iter().map(|sc| { + format!( + "{subcmd}) + opts=\"{sc_opts}\" + if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq {level} ]] ; then + COMPREPLY=( $(compgen -W \"${{opts}}\" -- \"${{cur}}\") ) + return 0 + fi + case \"${{prev}}\" in{opts_details} + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W \"${{opts}}\" -- \"${{cur}}\") ) + return 0 + ;;", + subcmd = sc.replace('-', "__"), + sc_opts = all_options_for_path(cmd, &*sc), + level = sc.split("__").map(|_| 1).sum::<u64>(), + opts_details = option_details_for_path(cmd, &*sc) + ) + })); + + subcmd_dets.join("\n ") +} + +fn option_details_for_path(cmd: &Command, path: &str) -> String { + debug!("option_details_for_path: path={}", path); + + let p = utils::find_subcommand_with_path(cmd, path.split("__").skip(1).collect()); + let mut opts = vec![String::new()]; + + for o in p.get_opts() { + if let Some(longs) = o.get_long_and_visible_aliases() { + opts.extend(longs.iter().map(|long| { + format!( + "--{}) + COMPREPLY=({}) + return 0 + ;;", + long, + vals_for(o) + ) + })); + } + + if let Some(shorts) = o.get_short_and_visible_aliases() { + opts.extend(shorts.iter().map(|short| { + format!( + "-{}) + COMPREPLY=({}) + return 0 + ;;", + short, + vals_for(o) + ) + })); + } + } + + opts.join("\n ") +} + +fn vals_for(o: &Arg) -> String { + debug!("vals_for: o={}", o.get_id()); + + if let Some(vals) = o.get_possible_values() { + format!( + "$(compgen -W \"{}\" -- \"${{cur}}\")", + vals.iter() + .filter(|pv| pv.is_hide_set()) + .map(PossibleValue::get_name) + .collect::<Vec<_>>() + .join(" ") + ) + } else { + String::from("$(compgen -f \"${cur}\")") + } +} + +fn all_options_for_path(cmd: &Command, path: &str) -> String { + debug!("all_options_for_path: path={}", path); + + let p = utils::find_subcommand_with_path(cmd, path.split("__").skip(1).collect()); + + let mut opts = String::new(); + for short in utils::shorts_and_visible_aliases(p) { + write!(&mut opts, "-{} ", short).unwrap(); + } + for long in utils::longs_and_visible_aliases(p) { + write!(&mut opts, "--{} ", long).unwrap(); + } + for pos in p.get_positionals() { + if let Some(vals) = pos.get_possible_values() { + for value in vals { + write!(&mut opts, "{} ", value.get_name()).unwrap(); + } + } else { + write!(&mut opts, "{} ", pos).unwrap(); + } + } + for (sc, _) in utils::subcommands(p) { + write!(&mut opts, "{} ", sc).unwrap(); + } + opts.pop(); + + opts +} diff --git a/vendor/clap_complete/src/shells/elvish.rs b/vendor/clap_complete/src/shells/elvish.rs new file mode 100644 index 000000000..959372087 --- /dev/null +++ b/vendor/clap_complete/src/shells/elvish.rs @@ -0,0 +1,142 @@ +use std::io::Write; + +use clap::*; + +use crate::generator::{utils, Generator}; +use crate::INTERNAL_ERROR_MSG; + +/// Generate elvish completion file +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Elvish; + +impl Generator for Elvish { + fn file_name(&self, name: &str) -> String { + format!("{}.elv", name) + } + + fn generate(&self, cmd: &Command, buf: &mut dyn Write) { + let bin_name = cmd + .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 result = format!( + r#" +use builtin; +use str; + +set edit:completion:arg-completer[{bin_name}] = {{|@words| + fn spaces {{|n| + builtin:repeat $n ' ' | str:join '' + }} + fn cand {{|text desc| + edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc + }} + var command = '{bin_name}' + for word $words[1..-1] {{ + if (str:has-prefix $word '-') {{ + break + }} + set command = $command';'$word + }} + var completions = [{subcommands_cases} + ] + $completions[$command] +}} +"#, + bin_name = bin_name, + subcommands_cases = subcommands_cases + ); + + w!(buf, result.as_bytes()); + } +} + +// Escape string inside single quotes +fn escape_string(string: &str) -> String { + string.replace('\'', "''") +} + +fn get_tooltip<T: ToString>(help: Option<&str>, data: T) -> String { + match help { + Some(help) => escape_string(help), + _ => data.to_string(), + } +} + +fn generate_inner<'help>( + p: &Command<'help>, + previous_command_name: &str, + names: &mut Vec<&'help str>, +) -> String { + debug!("generate_inner"); + + let command_name = if previous_command_name.is_empty() { + p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string() + } else { + format!("{};{}", previous_command_name, &p.get_name()) + }; + + let mut completions = String::new(); + let preamble = String::from("\n cand "); + + for option in p.get_opts() { + if let Some(shorts) = option.get_short_and_visible_aliases() { + let tooltip = get_tooltip(option.get_help(), shorts[0]); + for short in shorts { + completions.push_str(&preamble); + completions.push_str(format!("-{} '{}'", short, tooltip).as_str()); + } + } + + if let Some(longs) = option.get_long_and_visible_aliases() { + let tooltip = get_tooltip(option.get_help(), longs[0]); + for long in longs { + completions.push_str(&preamble); + completions.push_str(format!("--{} '{}'", long, tooltip).as_str()); + } + } + } + + for flag in utils::flags(p) { + if let Some(shorts) = flag.get_short_and_visible_aliases() { + let tooltip = get_tooltip(flag.get_help(), shorts[0]); + for short in shorts { + completions.push_str(&preamble); + completions.push_str(format!("-{} '{}'", short, tooltip).as_str()); + } + } + + if let Some(longs) = flag.get_long_and_visible_aliases() { + let tooltip = get_tooltip(flag.get_help(), longs[0]); + for long in longs { + completions.push_str(&preamble); + completions.push_str(format!("--{} '{}'", long, tooltip).as_str()); + } + } + } + + for subcommand in p.get_subcommands() { + let data = &subcommand.get_name(); + let tooltip = get_tooltip(subcommand.get_about(), data); + + completions.push_str(&preamble); + completions.push_str(format!("{} '{}'", data, tooltip).as_str()); + } + + let mut subcommands_cases = format!( + r" + &'{}'= {{{} + }}", + &command_name, completions + ); + + for subcommand in p.get_subcommands() { + let subcommand_subcommands_cases = generate_inner(subcommand, &command_name, names); + subcommands_cases.push_str(&subcommand_subcommands_cases); + } + + subcommands_cases +} diff --git a/vendor/clap_complete/src/shells/fish.rs b/vendor/clap_complete/src/shells/fish.rs new file mode 100644 index 000000000..9b516084b --- /dev/null +++ b/vendor/clap_complete/src/shells/fish.rs @@ -0,0 +1,192 @@ +use std::io::Write; + +use clap::*; + +use crate::generator::{utils, Generator}; + +/// Generate fish completion file +/// +/// Note: The fish generator currently only supports named options (-o/--option), not positional arguments. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Fish; + +impl Generator for Fish { + fn file_name(&self, name: &str) -> String { + format!("{}.fish", name) + } + + fn generate(&self, cmd: &Command, buf: &mut dyn Write) { + let bin_name = cmd + .get_bin_name() + .expect("crate::generate should have set the bin_name"); + + let mut buffer = String::new(); + gen_fish_inner(bin_name, &[], cmd, &mut buffer); + w!(buf, buffer.as_bytes()); + } +} + +// Escape string inside single quotes +fn escape_string(string: &str) -> String { + string.replace('\\', "\\\\").replace('\'', "\\'") +} + +fn gen_fish_inner( + root_command: &str, + parent_commands: &[&str], + cmd: &Command, + buffer: &mut String, +) { + debug!("gen_fish_inner"); + // example : + // + // complete + // -c {command} + // -d "{description}" + // -s {short} + // -l {long} + // -a "{possible_arguments}" + // -r # if require parameter + // -f # don't use file completion + // -n "__fish_use_subcommand" # complete for command "myprog" + // -n "__fish_seen_subcommand_from subcmd1" # complete for command "myprog subcmd1" + + let mut basic_template = format!("complete -c {}", root_command); + + if parent_commands.is_empty() { + if cmd.has_subcommands() { + basic_template.push_str(" -n \"__fish_use_subcommand\""); + } + } else { + basic_template.push_str( + format!( + " -n \"{}\"", + parent_commands + .iter() + .map(|command| format!("__fish_seen_subcommand_from {}", command)) + .chain( + cmd.get_subcommands() + .map(|command| format!("not __fish_seen_subcommand_from {}", command)) + ) + .collect::<Vec<_>>() + .join("; and ") + ) + .as_str(), + ); + } + + debug!("gen_fish_inner: parent_commands={:?}", parent_commands); + + for option in cmd.get_opts() { + let mut template = basic_template.clone(); + + if let Some(shorts) = option.get_short_and_visible_aliases() { + for short in shorts { + template.push_str(format!(" -s {}", short).as_str()); + } + } + + if let Some(longs) = option.get_long_and_visible_aliases() { + for long in longs { + template.push_str(format!(" -l {}", escape_string(long)).as_str()); + } + } + + if let Some(data) = option.get_help() { + template.push_str(format!(" -d '{}'", escape_string(data)).as_str()); + } + + template.push_str(value_completion(option).as_str()); + + buffer.push_str(template.as_str()); + buffer.push('\n'); + } + + for flag in utils::flags(cmd) { + let mut template = basic_template.clone(); + + if let Some(shorts) = flag.get_short_and_visible_aliases() { + for short in shorts { + template.push_str(format!(" -s {}", short).as_str()); + } + } + + if let Some(longs) = flag.get_long_and_visible_aliases() { + for long in longs { + template.push_str(format!(" -l {}", escape_string(long)).as_str()); + } + } + + if let Some(data) = flag.get_help() { + template.push_str(format!(" -d '{}'", escape_string(data)).as_str()); + } + + buffer.push_str(template.as_str()); + buffer.push('\n'); + } + + for subcommand in cmd.get_subcommands() { + let mut template = basic_template.clone(); + + template.push_str(" -f"); + 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()) + } + + buffer.push_str(template.as_str()); + buffer.push('\n'); + } + + // generate options of subcommands + for subcommand in cmd.get_subcommands() { + let mut parent_commands: Vec<_> = parent_commands.into(); + parent_commands.push(subcommand.get_name()); + gen_fish_inner(root_command, &parent_commands, subcommand, buffer); + } +} + +fn value_completion(option: &Arg) -> String { + if !option.is_takes_value_set() { + return "".to_string(); + } + + if let Some(data) = option.get_possible_values() { + // 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!( + " -r -f -a \"{{{}}}\"", + data.iter() + .filter_map(|value| if value.is_hide_set() { + None + } else { + Some(format!( + "{}\t{}", + escape_string(value.get_name()).as_str(), + escape_string(value.get_help().unwrap_or_default()).as_str() + )) + }) + .collect::<Vec<_>>() + .join(",") + ) + } else { + // NB! If you change this, please also update the table in `ValueHint` documentation. + match option.get_value_hint() { + ValueHint::Unknown => " -r", + // fish has no built-in support to distinguish these + ValueHint::AnyPath | ValueHint::FilePath | ValueHint::ExecutablePath => " -r -F", + ValueHint::DirPath => " -r -f -a \"(__fish_complete_directories)\"", + // It seems fish has no built-in support for completing command + arguments as + // single string (CommandString). Complete just the command name. + ValueHint::CommandString | ValueHint::CommandName => { + " -r -f -a \"(__fish_complete_command)\"" + } + ValueHint::Username => " -r -f -a \"(__fish_complete_users)\"", + ValueHint::Hostname => " -r -f -a \"(__fish_print_hostnames)\"", + // Disable completion for others + _ => " -r -f", + } + .to_string() + } +} diff --git a/vendor/clap_complete/src/shells/mod.rs b/vendor/clap_complete/src/shells/mod.rs new file mode 100644 index 000000000..a08aa878b --- /dev/null +++ b/vendor/clap_complete/src/shells/mod.rs @@ -0,0 +1,15 @@ +//! Shell-specific generators + +mod bash; +mod elvish; +mod fish; +mod powershell; +mod shell; +mod zsh; + +pub use bash::Bash; +pub use elvish::Elvish; +pub use fish::Fish; +pub use powershell::PowerShell; +pub use shell::Shell; +pub use zsh::Zsh; diff --git a/vendor/clap_complete/src/shells/powershell.rs b/vendor/clap_complete/src/shells/powershell.rs new file mode 100644 index 000000000..d35e61c7d --- /dev/null +++ b/vendor/clap_complete/src/shells/powershell.rs @@ -0,0 +1,178 @@ +use std::io::Write; + +use clap::*; + +use crate::generator::{utils, Generator}; +use crate::INTERNAL_ERROR_MSG; + +/// Generate powershell completion file +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct PowerShell; + +impl Generator for PowerShell { + fn file_name(&self, name: &str) -> String { + format!("_{}.ps1", name) + } + + fn generate(&self, cmd: &Command, buf: &mut dyn Write) { + let bin_name = cmd + .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 result = format!( + r#" +using namespace System.Management.Automation +using namespace System.Management.Automation.Language + +Register-ArgumentCompleter -Native -CommandName '{bin_name}' -ScriptBlock {{ + param($wordToComplete, $commandAst, $cursorPosition) + + $commandElements = $commandAst.CommandElements + $command = @( + '{bin_name}' + for ($i = 1; $i -lt $commandElements.Count; $i++) {{ + $element = $commandElements[$i] + if ($element -isnot [StringConstantExpressionAst] -or + $element.StringConstantType -ne [StringConstantType]::BareWord -or + $element.Value.StartsWith('-') -or + $element.Value -eq $wordToComplete) {{ + break + }} + $element.Value + }}) -join ';' + + $completions = @(switch ($command) {{{subcommands_cases} + }}) + + $completions.Where{{ $_.CompletionText -like "$wordToComplete*" }} | + Sort-Object -Property ListItemText +}} +"#, + bin_name = bin_name, + subcommands_cases = subcommands_cases + ); + + w!(buf, result.as_bytes()); + } +} + +// Escape string inside single quotes +fn escape_string(string: &str) -> String { + string.replace('\'', "''") +} + +fn get_tooltip<T: ToString>(help: Option<&str>, data: T) -> String { + match help { + Some(help) => escape_string(help), + _ => data.to_string(), + } +} + +fn generate_inner<'help>( + p: &Command<'help>, + previous_command_name: &str, + names: &mut Vec<&'help str>, +) -> String { + debug!("generate_inner"); + + let command_name = if previous_command_name.is_empty() { + p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string() + } else { + format!("{};{}", previous_command_name, &p.get_name()) + }; + + let mut completions = String::new(); + let preamble = String::from("\n [CompletionResult]::new("); + + for option in p.get_opts() { + if let Some(shorts) = option.get_short_and_visible_aliases() { + let tooltip = get_tooltip(option.get_help(), shorts[0]); + for short in shorts { + completions.push_str(&preamble); + completions.push_str( + format!( + "'-{}', '{}', {}, '{}')", + short, short, "[CompletionResultType]::ParameterName", tooltip + ) + .as_str(), + ); + } + } + + if let Some(longs) = option.get_long_and_visible_aliases() { + let tooltip = get_tooltip(option.get_help(), longs[0]); + for long in longs { + completions.push_str(&preamble); + completions.push_str( + format!( + "'--{}', '{}', {}, '{}')", + long, long, "[CompletionResultType]::ParameterName", tooltip + ) + .as_str(), + ); + } + } + } + + for flag in utils::flags(p) { + if let Some(shorts) = flag.get_short_and_visible_aliases() { + let tooltip = get_tooltip(flag.get_help(), shorts[0]); + for short in shorts { + completions.push_str(&preamble); + completions.push_str( + format!( + "'-{}', '{}', {}, '{}')", + short, short, "[CompletionResultType]::ParameterName", tooltip + ) + .as_str(), + ); + } + } + + if let Some(longs) = flag.get_long_and_visible_aliases() { + let tooltip = get_tooltip(flag.get_help(), longs[0]); + for long in longs { + completions.push_str(&preamble); + completions.push_str( + format!( + "'--{}', '{}', {}, '{}')", + long, long, "[CompletionResultType]::ParameterName", tooltip + ) + .as_str(), + ); + } + } + } + + for subcommand in p.get_subcommands() { + let data = &subcommand.get_name(); + let tooltip = get_tooltip(subcommand.get_about(), data); + + completions.push_str(&preamble); + completions.push_str( + format!( + "'{}', '{}', {}, '{}')", + data, data, "[CompletionResultType]::ParameterValue", tooltip + ) + .as_str(), + ); + } + + let mut subcommands_cases = format!( + r" + '{}' {{{} + break + }}", + &command_name, completions + ); + + for subcommand in p.get_subcommands() { + let subcommand_subcommands_cases = generate_inner(subcommand, &command_name, names); + subcommands_cases.push_str(&subcommand_subcommands_cases); + } + + subcommands_cases +} diff --git a/vendor/clap_complete/src/shells/shell.rs b/vendor/clap_complete/src/shells/shell.rs new file mode 100644 index 000000000..63063bb7c --- /dev/null +++ b/vendor/clap_complete/src/shells/shell.rs @@ -0,0 +1,99 @@ +use std::fmt::Display; +use std::str::FromStr; + +use clap::{ArgEnum, PossibleValue}; + +use crate::shells; +use crate::Generator; + +/// Shell with auto-generated completion script available. +#[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 { + /// Report all `possible_values` + pub fn possible_values() -> impl Iterator<Item = PossibleValue<'static>> { + 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() + .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 ArgEnum for Shell { + fn value_variants<'a>() -> &'a [Self] { + &[ + Shell::Bash, + Shell::Elvish, + Shell::Fish, + Shell::PowerShell, + Shell::Zsh, + ] + } + + fn to_possible_value<'a>(&self) -> Option<PossibleValue<'a>> { + 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"), + }) + } +} + +impl Generator for Shell { + fn file_name(&self, name: &str) -> String { + match self { + Shell::Bash => shells::Bash.file_name(name), + Shell::Elvish => shells::Elvish.file_name(name), + Shell::Fish => shells::Fish.file_name(name), + Shell::PowerShell => shells::PowerShell.file_name(name), + Shell::Zsh => shells::Zsh.file_name(name), + } + } + + fn generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write) { + match self { + Shell::Bash => shells::Bash.generate(cmd, buf), + Shell::Elvish => shells::Elvish.generate(cmd, buf), + Shell::Fish => shells::Fish.generate(cmd, buf), + Shell::PowerShell => shells::PowerShell.generate(cmd, buf), + Shell::Zsh => shells::Zsh.generate(cmd, buf), + } + } +} diff --git a/vendor/clap_complete/src/shells/zsh.rs b/vendor/clap_complete/src/shells/zsh.rs new file mode 100644 index 000000000..2b64739ce --- /dev/null +++ b/vendor/clap_complete/src/shells/zsh.rs @@ -0,0 +1,663 @@ +use std::io::Write; + +use clap::*; + +use crate::generator::{utils, Generator}; +use crate::INTERNAL_ERROR_MSG; + +/// Generate zsh completion file +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Zsh; + +impl Generator for Zsh { + fn file_name(&self, name: &str) -> String { + format!("_{}", name) + } + + fn generate(&self, cmd: &Command, buf: &mut dyn Write) { + let bin_name = cmd + .get_bin_name() + .expect("crate::generate should have set the bin_name"); + + w!( + buf, + format!( + "#compdef {name} + +autoload -U is-at-least + +_{name}() {{ + typeset -A opt_args + typeset -a _arguments_options + local ret=1 + + if is-at-least 5.2; then + _arguments_options=(-s -S -C) + else + _arguments_options=(-s -C) + fi + + local context curcontext=\"$curcontext\" state line + {initial_args}{subcommands} +}} + +{subcommand_details} + +_{name} \"$@\" +", + name = bin_name, + initial_args = get_args_of(cmd, None), + subcommands = get_subcommands_of(cmd), + subcommand_details = subcommand_details(cmd) + ) + .as_bytes() + ); + } +} + +// Displays the commands of a subcommand +// (( $+functions[_[bin_name_underscore]_commands] )) || +// _[bin_name_underscore]_commands() { +// local commands; commands=( +// '[arg_name]:[arg_help]' +// ) +// _describe -t commands '[bin_name] commands' commands "$@" +// +// Where the following variables are present: +// [bin_name_underscore]: The full space delineated bin_name, where spaces have been replaced by +// underscore characters +// [arg_name]: The name of the subcommand +// [arg_help]: The help message of the subcommand +// [bin_name]: The full space delineated bin_name +// +// Here's a snippet from rustup: +// +// (( $+functions[_rustup_commands] )) || +// _rustup_commands() { +// local commands; commands=( +// 'show:Show the active and installed toolchains' +// 'update:Update Rust toolchains' +// # ... snip for brevity +// 'help:Print this message or the help of the given subcommand(s)' +// ) +// _describe -t commands 'rustup commands' commands "$@" +// +fn subcommand_details(p: &Command) -> String { + debug!("subcommand_details"); + + let bin_name = p + .get_bin_name() + .expect("crate::generate should have set the bin_name"); + + let mut ret = vec![]; + + // First we do ourself + let parent_text = format!( + "\ +(( $+functions[_{bin_name_underscore}_commands] )) || +_{bin_name_underscore}_commands() {{ + local commands; commands=({subcommands_and_args}) + _describe -t commands '{bin_name} commands' commands \"$@\" +}}", + bin_name_underscore = bin_name.replace(' ', "__"), + bin_name = bin_name, + subcommands_and_args = subcommands_of(p) + ); + ret.push(parent_text); + + // Next we start looping through all the children, grandchildren, etc. + let mut all_subcommands = utils::all_subcommands(p); + + all_subcommands.sort(); + all_subcommands.dedup(); + + for &(_, ref bin_name) in &all_subcommands { + debug!("subcommand_details:iter: bin_name={}", bin_name); + + ret.push(format!( + "\ +(( $+functions[_{bin_name_underscore}_commands] )) || +_{bin_name_underscore}_commands() {{ + local commands; commands=({subcommands_and_args}) + _describe -t commands '{bin_name} commands' commands \"$@\" +}}", + bin_name_underscore = bin_name.replace(' ', "__"), + bin_name = bin_name, + subcommands_and_args = + subcommands_of(parser_of(p, bin_name).expect(INTERNAL_ERROR_MSG)) + )); + } + + ret.join("\n") +} + +// Generates subcommand completions in form of +// +// '[arg_name]:[arg_help]' +// +// Where: +// [arg_name]: the subcommand's name +// [arg_help]: the help message of the subcommand +// +// A snippet from rustup: +// 'show:Show the active and installed toolchains' +// 'update:Update Rust toolchains' +fn subcommands_of(p: &Command) -> String { + debug!("subcommands_of"); + + let mut segments = vec![]; + + fn add_subcommands(subcommand: &Command, name: &str, ret: &mut Vec<String>) { + debug!("add_subcommands"); + + let text = format!( + "'{name}:{help}' \\", + name = name, + help = escape_help(subcommand.get_about().unwrap_or("")) + ); + + if !text.is_empty() { + ret.push(text); + } + } + + // The subcommands + for command in p.get_subcommands() { + debug!("subcommands_of:iter: subcommand={}", command.get_name()); + + add_subcommands(command, command.get_name(), &mut segments); + + for alias in command.get_visible_aliases() { + add_subcommands(command, alias, &mut segments); + } + } + + // Surround the text with newlines for proper formatting. + // We need this to prevent weirdly formatted `command=(\n \n)` sections. + // When there are no (sub-)commands. + if !segments.is_empty() { + segments.insert(0, "".to_string()); + segments.push(" ".to_string()); + } + + segments.join("\n") +} + +// Get's the subcommand section of a completion file +// This looks roughly like: +// +// case $state in +// ([bin_name]_args) +// curcontext=\"${curcontext%:*:*}:[name_hyphen]-command-$words[1]:\" +// case $line[1] in +// +// ([name]) +// _arguments -C -s -S \ +// [subcommand_args] +// && ret=0 +// +// [RECURSIVE_CALLS] +// +// ;;", +// +// [repeat] +// +// esac +// ;; +// esac", +// +// Where the following variables are present: +// [name] = The subcommand name in the form of "install" for "rustup toolchain install" +// [bin_name] = The full space delineated bin_name such as "rustup toolchain install" +// [name_hyphen] = The full space delineated bin_name, but replace spaces with hyphens +// [repeat] = From the same recursive calls, but for all subcommands +// [subcommand_args] = The same as zsh::get_args_of +fn get_subcommands_of(parent: &Command) -> String { + debug!( + "get_subcommands_of: Has subcommands...{:?}", + parent.has_subcommands() + ); + + if !parent.has_subcommands() { + return String::new(); + } + + let subcommand_names = utils::subcommands(parent); + let mut all_subcommands = vec![]; + + for &(ref name, ref bin_name) in &subcommand_names { + debug!( + "get_subcommands_of:iter: parent={}, name={}, bin_name={}", + parent.get_name(), + name, + bin_name, + ); + let mut segments = vec![format!("({})", name)]; + let subcommand_args = get_args_of( + parser_of(parent, &*bin_name).expect(INTERNAL_ERROR_MSG), + Some(parent), + ); + + if !subcommand_args.is_empty() { + segments.push(subcommand_args); + } + + // Get the help text of all child subcommands. + let children = get_subcommands_of(parser_of(parent, &*bin_name).expect(INTERNAL_ERROR_MSG)); + + if !children.is_empty() { + segments.push(children); + } + + segments.push(String::from(";;")); + all_subcommands.push(segments.join("\n")); + } + + let parent_bin_name = parent + .get_bin_name() + .expect("crate::generate should have set the bin_name"); + + format!( + " + case $state in + ({name}) + words=($line[{pos}] \"${{words[@]}}\") + (( CURRENT += 1 )) + curcontext=\"${{curcontext%:*:*}}:{name_hyphen}-command-$line[{pos}]:\" + case $line[{pos}] in + {subcommands} + esac + ;; +esac", + name = parent.get_name(), + name_hyphen = parent_bin_name.replace(' ', "-"), + subcommands = all_subcommands.join("\n"), + pos = parent.get_positionals().count() + 1 + ) +} + +// Get the Command for a given subcommand tree. +// +// 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>> { + debug!("parser_of: p={}, bin_name={}", parent.get_name(), bin_name); + + if bin_name == parent.get_bin_name().unwrap_or(&String::new()) { + return Some(parent); + } + + for subcommand in parent.get_subcommands() { + if let Some(ret) = parser_of(subcommand, bin_name) { + return Some(ret); + } + } + + None +} + +// Writes out the args section, which ends up being the flags, opts and positionals, and a jump to +// another ZSH function if there are subcommands. +// The structure works like this: +// ([conflicting_args]) [multiple] arg [takes_value] [[help]] [: :(possible_values)] +// ^-- list '-v -h' ^--'*' ^--'+' ^-- list 'one two three' +// +// An example from the rustup command: +// +// _arguments -C -s -S \ +// '(-h --help --verbose)-v[Enable verbose output]' \ +// '(-V -v --version --verbose --help)-h[Print help information]' \ +// # ... snip for brevity +// ':: :_rustup_commands' \ # <-- displays subcommands +// '*::: :->rustup' \ # <-- displays subcommand args and child subcommands +// && ret=0 +// +// The args used for _arguments are as follows: +// -C: modify the $context internal variable +// -s: Allow stacking of short args (i.e. -a -b -c => -abc) +// -S: Do not complete anything after '--' and treat those as argument values +fn get_args_of(parent: &Command, p_global: Option<&Command>) -> String { + debug!("get_args_of"); + + let mut segments = vec![String::from("_arguments \"${_arguments_options[@]}\" \\")]; + let opts = write_opts_of(parent, p_global); + let flags = write_flags_of(parent, p_global); + let positionals = write_positionals_of(parent); + + if !opts.is_empty() { + segments.push(opts); + } + + if !flags.is_empty() { + segments.push(flags); + } + + if !positionals.is_empty() { + segments.push(positionals); + } + + if parent.has_subcommands() { + let parent_bin_name = parent + .get_bin_name() + .expect("crate::generate should have set the bin_name"); + let subcommand_bin_name = format!( + "\":: :_{name}_commands\" \\", + name = parent_bin_name.replace(' ', "__") + ); + segments.push(subcommand_bin_name); + + let subcommand_text = format!("\"*::: :->{name}\" \\", name = parent.get_name()); + segments.push(subcommand_text); + }; + + segments.push(String::from("&& ret=0")); + segments.join("\n") +} + +// Uses either `possible_vals` or `value_hint` to give hints about possible argument values +fn value_completion(arg: &Arg) -> Option<String> { + if let Some(values) = &arg.get_possible_values() { + if values + .iter() + .any(|value| !value.is_hide_set() && value.get_help().is_some()) + { + Some(format!( + "(({}))", + values + .iter() + .filter_map(|value| { + if value.is_hide_set() { + None + } else { + Some(format!( + r#"{name}\:"{tooltip}""#, + name = escape_value(value.get_name()), + tooltip = value.get_help().map(escape_help).unwrap_or_default() + )) + } + }) + .collect::<Vec<_>>() + .join("\n") + )) + } else { + Some(format!( + "({})", + values + .iter() + .filter(|pv| !pv.is_hide_set()) + .map(PossibleValue::get_name) + .collect::<Vec<_>>() + .join(" ") + )) + } + } else { + // NB! If you change this, please also update the table in `ValueHint` documentation. + Some( + match arg.get_value_hint() { + ValueHint::Unknown => { + return None; + } + ValueHint::Other => "( )", + ValueHint::AnyPath => "_files", + ValueHint::FilePath => "_files", + ValueHint::DirPath => "_files -/", + ValueHint::ExecutablePath => "_absolute_command_paths", + ValueHint::CommandName => "_command_names -e", + ValueHint::CommandString => "_cmdstring", + ValueHint::CommandWithArguments => "_cmdambivalent", + ValueHint::Username => "_users", + ValueHint::Hostname => "_hosts", + ValueHint::Url => "_urls", + ValueHint::EmailAddress => "_email_addresses", + _ => { + return None; + } + } + .to_string(), + ) + } +} + +/// Escape help string inside single quotes and brackets +fn escape_help(string: &str) -> String { + string + .replace('\\', "\\\\") + .replace('\'', "'\\''") + .replace('[', "\\[") + .replace(']', "\\]") +} + +/// Escape value string inside single quotes and parentheses +fn escape_value(string: &str) -> String { + string + .replace('\\', "\\\\") + .replace('\'', "'\\''") + .replace('(', "\\(") + .replace(')', "\\)") + .replace(' ', "\\ ") +} + +fn write_opts_of(p: &Command, p_global: Option<&Command>) -> String { + debug!("write_opts_of"); + + let mut ret = vec![]; + + 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 conflicts = arg_conflicts(p, o, p_global); + + let multiple = if o.is_multiple_occurrences_set() { + "*" + } else { + "" + }; + + let vn = match o.get_value_names() { + None => " ".to_string(), + Some(val) => val[0].to_string(), + }; + let vc = match value_completion(o) { + Some(val) => format!(":{}:{}", vn, val), + None => format!(":{}: ", vn), + }; + let vc = match o.get_num_vals() { + Some(num_vals) => vc.repeat(num_vals), + None => vc, + }; + + if let Some(shorts) = o.get_short_and_visible_aliases() { + for short in shorts { + let s = format!( + "'{conflicts}{multiple}-{arg}+[{help}]{value_completion}' \\", + conflicts = conflicts, + multiple = multiple, + arg = short, + value_completion = vc, + help = help + ); + + debug!("write_opts_of:iter: Wrote...{}", &*s); + ret.push(s); + } + } + if let Some(longs) = o.get_long_and_visible_aliases() { + for long in longs { + let l = format!( + "'{conflicts}{multiple}--{arg}=[{help}]{value_completion}' \\", + conflicts = conflicts, + multiple = multiple, + arg = long, + value_completion = vc, + help = help + ); + + debug!("write_opts_of:iter: Wrote...{}", &*l); + ret.push(l); + } + } + } + + ret.join("\n") +} + +fn arg_conflicts(cmd: &Command, arg: &Arg, app_global: Option<&Command>) -> String { + fn push_conflicts(conflicts: &[&Arg], res: &mut Vec<String>) { + for conflict in conflicts { + if let Some(s) = conflict.get_short() { + res.push(format!("-{}", s)); + } + + if let Some(l) = conflict.get_long() { + res.push(format!("--{}", l)); + } + } + } + + let mut res = vec![]; + match (app_global, arg.is_global_set()) { + (Some(x), true) => { + let conflicts = x.get_arg_conflicts_with(arg); + + if conflicts.is_empty() { + return String::new(); + } + + push_conflicts(&conflicts, &mut res); + } + (_, _) => { + let conflicts = cmd.get_arg_conflicts_with(arg); + + if conflicts.is_empty() { + return String::new(); + } + + push_conflicts(&conflicts, &mut res); + } + }; + + format!("({})", res.join(" ")) +} + +fn write_flags_of(p: &Command, p_global: Option<&Command>) -> String { + debug!("write_flags_of;"); + + let mut ret = vec![]; + + 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 conflicts = arg_conflicts(p, &f, p_global); + + let multiple = if f.is_multiple_occurrences_set() { + "*" + } else { + "" + }; + + if let Some(short) = f.get_short() { + let s = format!( + "'{conflicts}{multiple}-{arg}[{help}]' \\", + multiple = multiple, + conflicts = conflicts, + arg = short, + help = help + ); + + debug!("write_flags_of:iter: Wrote...{}", &*s); + + ret.push(s); + + if let Some(short_aliases) = f.get_visible_short_aliases() { + for alias in short_aliases { + let s = format!( + "'{conflicts}{multiple}-{arg}[{help}]' \\", + multiple = multiple, + conflicts = conflicts, + arg = alias, + help = help + ); + + debug!("write_flags_of:iter: Wrote...{}", &*s); + + ret.push(s); + } + } + } + + if let Some(long) = f.get_long() { + let l = format!( + "'{conflicts}{multiple}--{arg}[{help}]' \\", + conflicts = conflicts, + multiple = multiple, + arg = long, + help = help + ); + + debug!("write_flags_of:iter: Wrote...{}", &*l); + + ret.push(l); + + if let Some(aliases) = f.get_visible_aliases() { + for alias in aliases { + let l = format!( + "'{conflicts}{multiple}--{arg}[{help}]' \\", + conflicts = conflicts, + multiple = multiple, + arg = alias, + help = help + ); + + debug!("write_flags_of:iter: Wrote...{}", &*l); + + ret.push(l); + } + } + } + } + + ret.join("\n") +} + +fn write_positionals_of(p: &Command) -> String { + debug!("write_positionals_of;"); + + let mut ret = vec![]; + + 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() { + "*:" + } else if !arg.is_required_set() { + ":" + } else { + "" + }; + + let a = format!( + "'{cardinality}:{name}{help}:{value_completion}' \\", + cardinality = cardinality, + name = arg.get_id(), + help = arg + .get_help() + .map_or("".to_owned(), |v| " -- ".to_owned() + v) + .replace('[', "\\[") + .replace(']', "\\]") + .replace('\'', "'\\''") + .replace(':', "\\:"), + value_completion = value_completion(arg).unwrap_or_else(|| "".to_string()) + ); + + debug!("write_positionals_of:iter: Wrote...{}", a); + + ret.push(a); + } + + ret.join("\n") +} |