aboutsummaryrefslogtreecommitdiff
path: root/clap/src/completions/zsh.rs
diff options
context:
space:
mode:
Diffstat (limited to 'clap/src/completions/zsh.rs')
-rw-r--r--clap/src/completions/zsh.rs472
1 files changed, 472 insertions, 0 deletions
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<W: Write>(&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<String>) {
+ 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::<Vec<String>>().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::<Vec<String>>().join(" "))
+ })
+ );
+
+ debugln!("write_positionals_of:iter: Wrote...{}", a);
+ ret.push(a);
+ }
+
+ ret.join("\n")
+}