From 5e20a29b4fdc8a2d442d1093681b396dcb4b816b Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 7 Jan 2020 11:18:04 +0000 Subject: Add structopt dependency in version 0.3.7 This patch series replaces argparse with structopt in the argument handling code. As a first step, we need structopt as a dependency. Import subrepo structopt/:structopt at efbdda4753592e27bc430fb01f7b9650b2f3174d Import subrepo bitflags/:bitflags at 30668016aca6bd3b02c766e8347e0b4080d4c296 Import subrepo clap/:clap at 784524f7eb193e35f81082cc69454c8c21b948f7 Import subrepo heck/:heck at 093d56fbf001e1506e56dbfa38631d99b1066df1 Import subrepo proc-macro-error/:proc-macro-error at 6c4cfe79a622c5de8ae68557993542be46eacae2 Import subrepo proc-macro2/:proc-macro2 at d5d48eddca4566e5438e8a2cbed4a74e049544de Import subrepo quote/:quote at 727436c6c137b20f0f34dde5d8fda2679b9747ad Import subrepo rustversion/:rustversion at 0c5663313516263059ce9059ef81fc7a1cf655ca Import subrepo syn-mid/:syn-mid at 5d3d85414a9e6674e1857ec22a87b96e04a6851a Import subrepo syn/:syn at e87c27e87f6f4ef8919d0372bdb056d53ef0d8f3 Import subrepo textwrap/:textwrap at abcd618beae3f74841032aa5b53c1086b0a57ca2 Import subrepo unicode-segmentation/:unicode-segmentation at 637c9874c4fe0c205ff27787faf150a40295c6c3 Import subrepo unicode-width/:unicode-width at 3033826f8bf05e82724140a981d5941e48fce393 Import subrepo unicode-xid/:unicode-xid at 4baae9fffb156ba229665b972a9cd5991787ceb7 --- clap/src/completions/zsh.rs | 472 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 472 insertions(+) create mode 100644 clap/src/completions/zsh.rs (limited to 'clap/src/completions/zsh.rs') diff --git a/clap/src/completions/zsh.rs b/clap/src/completions/zsh.rs new file mode 100644 index 0000000..5d23fd2 --- /dev/null +++ b/clap/src/completions/zsh.rs @@ -0,0 +1,472 @@ +// Std +use std::io::Write; +#[allow(deprecated, unused_imports)] +use std::ascii::AsciiExt; + +// Internal +use app::App; +use app::parser::Parser; +use args::{AnyArg, ArgSettings}; +use completions; +use INTERNAL_ERROR_MSG; + +pub struct ZshGen<'a, 'b> +where + 'a: 'b, +{ + p: &'b Parser<'a, 'b>, +} + +impl<'a, 'b> ZshGen<'a, 'b> { + pub fn new(p: &'b Parser<'a, 'b>) -> Self { + debugln!("ZshGen::new;"); + ZshGen { p: p } + } + + pub fn generate_to(&self, buf: &mut W) { + debugln!("ZshGen::generate_to;"); + 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 = self.p.meta.bin_name.as_ref().unwrap(), + initial_args = get_args_of(self.p), + subcommands = get_subcommands_of(self.p), + subcommand_details = subcommand_details(self.p) + ).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:Prints this message or the help of the given subcommand(s)' +// ) +// _describe -t commands 'rustup commands' commands "$@" +// +fn subcommand_details(p: &Parser) -> String { + debugln!("ZshGen::subcommand_details;"); + // First we do ourself + let mut ret = vec![ + 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 = p.meta.bin_name.as_ref().unwrap().replace(" ", "__"), + bin_name = p.meta.bin_name.as_ref().unwrap(), + subcommands_and_args = subcommands_of(p) + ), + ]; + + // Next we start looping through all the children, grandchildren, etc. + let mut all_subcommands = completions::all_subcommands(p); + all_subcommands.sort(); + all_subcommands.dedup(); + for &(_, ref bin_name) in &all_subcommands { + debugln!("ZshGen::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)) + )); + } + + 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: &Parser) -> String { + debugln!("ZshGen::subcommands_of;"); + let mut ret = vec![]; + fn add_sc(sc: &App, n: &str, ret: &mut Vec) { + debugln!("ZshGen::add_sc;"); + let s = format!( + "\"{name}:{help}\" \\", + name = n, + help = sc.p + .meta + .about + .unwrap_or("") + .replace("[", "\\[") + .replace("]", "\\]") + ); + if !s.is_empty() { + ret.push(s); + } + } + + // The subcommands + for sc in p.subcommands() { + debugln!( + "ZshGen::subcommands_of:iter: subcommand={}", + sc.p.meta.name + ); + add_sc(sc, &sc.p.meta.name, &mut ret); + if let Some(ref v) = sc.p.meta.aliases { + for alias in v.iter().filter(|&&(_, vis)| vis).map(|&(n, _)| n) { + add_sc(sc, alias, &mut ret); + } + } + } + + ret.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(p: &Parser) -> String { + debugln!("get_subcommands_of;"); + + debugln!( + "get_subcommands_of: Has subcommands...{:?}", + p.has_subcommands() + ); + if !p.has_subcommands() { + return String::new(); + } + + let sc_names = completions::subcommands_of(p); + + let mut subcmds = vec![]; + for &(ref name, ref bin_name) in &sc_names { + let mut v = vec![format!("({})", name)]; + let subcommand_args = get_args_of(parser_of(p, &*bin_name)); + if !subcommand_args.is_empty() { + v.push(subcommand_args); + } + let subcommands = get_subcommands_of(parser_of(p, &*bin_name)); + if !subcommands.is_empty() { + v.push(subcommands); + } + v.push(String::from(";;")); + subcmds.push(v.join("\n")); + } + + 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 = p.meta.name, + name_hyphen = p.meta.bin_name.as_ref().unwrap().replace(" ", "-"), + subcommands = subcmds.join("\n"), + pos = p.positionals().len() + 1 + ) +} + +fn parser_of<'a, 'b>(p: &'b Parser<'a, 'b>, sc: &str) -> &'b Parser<'a, 'b> { + debugln!("parser_of: sc={}", sc); + if sc == p.meta.bin_name.as_ref().unwrap_or(&String::new()) { + return p; + } + &p.find_subcommand(sc).expect(INTERNAL_ERROR_MSG).p +} + +// Writes out the args section, which ends up being the flags, opts and postionals, and a jump to +// another ZSH function if there are subcommands. +// The structer 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[Prints 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(p: &Parser) -> String { + debugln!("get_args_of;"); + let mut ret = vec![String::from("_arguments \"${_arguments_options[@]}\" \\")]; + let opts = write_opts_of(p); + let flags = write_flags_of(p); + let positionals = write_positionals_of(p); + let sc_or_a = if p.has_subcommands() { + format!( + "\":: :_{name}_commands\" \\", + name = p.meta.bin_name.as_ref().unwrap().replace(" ", "__") + ) + } else { + String::new() + }; + let sc = if p.has_subcommands() { + format!("\"*::: :->{name}\" \\", name = p.meta.name) + } else { + String::new() + }; + + if !opts.is_empty() { + ret.push(opts); + } + if !flags.is_empty() { + ret.push(flags); + } + if !positionals.is_empty() { + ret.push(positionals); + } + if !sc_or_a.is_empty() { + ret.push(sc_or_a); + } + if !sc.is_empty() { + ret.push(sc); + } + ret.push(String::from("&& ret=0")); + + ret.join("\n") +} + +// 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: &Parser) -> String { + debugln!("write_opts_of;"); + let mut ret = vec![]; + for o in p.opts() { + debugln!("write_opts_of:iter: o={}", o.name()); + let help = o.help().map_or(String::new(), escape_help); + let mut conflicts = get_zsh_arg_conflicts!(p, o, INTERNAL_ERROR_MSG); + conflicts = if conflicts.is_empty() { + String::new() + } else { + format!("({})", conflicts) + }; + + let multiple = if o.is_set(ArgSettings::Multiple) { + "*" + } else { + "" + }; + let pv = if let Some(pv_vec) = o.possible_vals() { + format!(": :({})", pv_vec.iter().map( + |v| escape_value(*v)).collect::>().join(" ")) + } else { + String::new() + }; + if let Some(short) = o.short() { + let s = format!( + "'{conflicts}{multiple}-{arg}+[{help}]{possible_values}' \\", + conflicts = conflicts, + multiple = multiple, + arg = short, + possible_values = pv, + help = help + ); + + debugln!("write_opts_of:iter: Wrote...{}", &*s); + ret.push(s); + } + if let Some(long) = o.long() { + let l = format!( + "'{conflicts}{multiple}--{arg}=[{help}]{possible_values}' \\", + conflicts = conflicts, + multiple = multiple, + arg = long, + possible_values = pv, + help = help + ); + + debugln!("write_opts_of:iter: Wrote...{}", &*l); + ret.push(l); + } + } + + ret.join("\n") +} + +fn write_flags_of(p: &Parser) -> String { + debugln!("write_flags_of;"); + let mut ret = vec![]; + for f in p.flags() { + debugln!("write_flags_of:iter: f={}", f.name()); + let help = f.help().map_or(String::new(), escape_help); + let mut conflicts = get_zsh_arg_conflicts!(p, f, INTERNAL_ERROR_MSG); + conflicts = if conflicts.is_empty() { + String::new() + } else { + format!("({})", conflicts) + }; + + let multiple = if f.is_set(ArgSettings::Multiple) { + "*" + } else { + "" + }; + if let Some(short) = f.short() { + let s = format!( + "'{conflicts}{multiple}-{arg}[{help}]' \\", + multiple = multiple, + conflicts = conflicts, + arg = short, + help = help + ); + + debugln!("write_flags_of:iter: Wrote...{}", &*s); + ret.push(s); + } + + if let Some(long) = f.long() { + let l = format!( + "'{conflicts}{multiple}--{arg}[{help}]' \\", + conflicts = conflicts, + multiple = multiple, + arg = long, + help = help + ); + + debugln!("write_flags_of:iter: Wrote...{}", &*l); + ret.push(l); + } + } + + ret.join("\n") +} + +fn write_positionals_of(p: &Parser) -> String { + debugln!("write_positionals_of;"); + let mut ret = vec![]; + for arg in p.positionals() { + debugln!("write_positionals_of:iter: arg={}", arg.b.name); + let a = format!( + "'{optional}:{name}{help}:{action}' \\", + optional = if !arg.b.is_set(ArgSettings::Required) { ":" } else { "" }, + name = arg.b.name, + help = arg.b + .help + .map_or("".to_owned(), |v| " -- ".to_owned() + v) + .replace("[", "\\[") + .replace("]", "\\]"), + action = arg.possible_vals().map_or("_files".to_owned(), |values| { + format!("({})", + values.iter().map(|v| escape_value(*v)).collect::>().join(" ")) + }) + ); + + debugln!("write_positionals_of:iter: Wrote...{}", a); + ret.push(a); + } + + ret.join("\n") +} -- cgit v1.2.1