aboutsummaryrefslogtreecommitdiff
path: root/clap/src/completions
diff options
context:
space:
mode:
authorRobin Krahl <robin.krahl@ireas.org>2020-01-07 11:18:04 +0000
committerDaniel Mueller <deso@posteo.net>2020-01-08 09:20:25 -0800
commit5e20a29b4fdc8a2d442d1093681b396dcb4b816b (patch)
tree55ab083fa8999d2ccbb5e921c1ffe52560dca152 /clap/src/completions
parent203e691f46d591a2cc8acdfd850fa9f5b0fb8a98 (diff)
downloadnitrocli-5e20a29b4fdc8a2d442d1093681b396dcb4b816b.tar.gz
nitrocli-5e20a29b4fdc8a2d442d1093681b396dcb4b816b.tar.bz2
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
Diffstat (limited to 'clap/src/completions')
-rw-r--r--clap/src/completions/bash.rs219
-rw-r--r--clap/src/completions/elvish.rs126
-rw-r--r--clap/src/completions/fish.rs99
-rw-r--r--clap/src/completions/macros.rs28
-rw-r--r--clap/src/completions/mod.rs179
-rw-r--r--clap/src/completions/powershell.rs139
-rw-r--r--clap/src/completions/shell.rs52
-rw-r--r--clap/src/completions/zsh.rs472
8 files changed, 1314 insertions, 0 deletions
diff --git a/clap/src/completions/bash.rs b/clap/src/completions/bash.rs
new file mode 100644
index 0000000..37dfa66
--- /dev/null
+++ b/clap/src/completions/bash.rs
@@ -0,0 +1,219 @@
+// Std
+use std::io::Write;
+
+// Internal
+use app::parser::Parser;
+use args::OptBuilder;
+use completions;
+
+pub struct BashGen<'a, 'b>
+where
+ 'a: 'b,
+{
+ p: &'b Parser<'a, 'b>,
+}
+
+impl<'a, 'b> BashGen<'a, 'b> {
+ pub fn new(p: &'b Parser<'a, 'b>) -> Self { BashGen { p: p } }
+
+ pub fn generate_to<W: Write>(&self, buf: &mut W) {
+ w!(
+ buf,
+ format!(
+ r#"_{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
+ {name})
+ cmd="{name}"
+ ;;
+ {subcmds}
+ *)
+ ;;
+ esac
+ done
+
+ case "${{cmd}}" in
+ {name})
+ 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 = self.p.meta.bin_name.as_ref().unwrap(),
+ name_opts = self.all_options_for_path(self.p.meta.bin_name.as_ref().unwrap()),
+ name_opts_details =
+ self.option_details_for_path(self.p.meta.bin_name.as_ref().unwrap()),
+ subcmds = self.all_subcommands(),
+ subcmd_details = self.subcommand_details()
+ ).as_bytes()
+ );
+ }
+
+ fn all_subcommands(&self) -> String {
+ debugln!("BashGen::all_subcommands;");
+ let mut subcmds = String::new();
+ let scs = completions::all_subcommand_names(self.p);
+
+ for sc in &scs {
+ subcmds = format!(
+ r#"{}
+ {name})
+ cmd+="__{fn_name}"
+ ;;"#,
+ subcmds,
+ name = sc,
+ fn_name = sc.replace("-", "__")
+ );
+ }
+
+ subcmds
+ }
+
+ fn subcommand_details(&self) -> String {
+ debugln!("BashGen::subcommand_details;");
+ let mut subcmd_dets = String::new();
+ let mut scs = completions::get_all_subcommand_paths(self.p, true);
+ scs.sort();
+ scs.dedup();
+
+ for sc in &scs {
+ subcmd_dets = format!(
+ r#"{}
+ {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_dets,
+ subcmd = sc.replace("-", "__"),
+ sc_opts = self.all_options_for_path(&*sc),
+ level = sc.split("__").map(|_| 1).fold(0, |acc, n| acc + n),
+ opts_details = self.option_details_for_path(&*sc)
+ );
+ }
+
+ subcmd_dets
+ }
+
+ fn option_details_for_path(&self, path: &str) -> String {
+ debugln!("BashGen::option_details_for_path: path={}", path);
+ let mut p = self.p;
+ for sc in path.split("__").skip(1) {
+ debugln!("BashGen::option_details_for_path:iter: sc={}", sc);
+ p = &find_subcmd!(p, sc).unwrap().p;
+ }
+ let mut opts = String::new();
+ for o in p.opts() {
+ if let Some(l) = o.s.long {
+ opts = format!(
+ "{}
+ --{})
+ COMPREPLY=({})
+ return 0
+ ;;",
+ opts,
+ l,
+ self.vals_for(o)
+ );
+ }
+ if let Some(s) = o.s.short {
+ opts = format!(
+ "{}
+ -{})
+ COMPREPLY=({})
+ return 0
+ ;;",
+ opts,
+ s,
+ self.vals_for(o)
+ );
+ }
+ }
+ opts
+ }
+
+ fn vals_for(&self, o: &OptBuilder) -> String {
+ debugln!("BashGen::vals_for: o={}", o.b.name);
+ use args::AnyArg;
+ if let Some(vals) = o.possible_vals() {
+ format!(r#"$(compgen -W "{}" -- "${{cur}}")"#, vals.join(" "))
+ } else {
+ String::from(r#"$(compgen -f "${cur}")"#)
+ }
+ }
+
+ fn all_options_for_path(&self, path: &str) -> String {
+ debugln!("BashGen::all_options_for_path: path={}", path);
+ let mut p = self.p;
+ for sc in path.split("__").skip(1) {
+ debugln!("BashGen::all_options_for_path:iter: sc={}", sc);
+ p = &find_subcmd!(p, sc).unwrap().p;
+ }
+ let mut opts = shorts!(p).fold(String::new(), |acc, s| format!("{} -{}", acc, s));
+ opts = format!(
+ "{} {}",
+ opts,
+ longs!(p).fold(String::new(), |acc, l| format!("{} --{}", acc, l))
+ );
+ opts = format!(
+ "{} {}",
+ opts,
+ p.positionals
+ .values()
+ .fold(String::new(), |acc, p| format!("{} {}", acc, p))
+ );
+ opts = format!(
+ "{} {}",
+ opts,
+ p.subcommands
+ .iter()
+ .fold(String::new(), |acc, s| format!("{} {}", acc, s.p.meta.name))
+ );
+ for sc in &p.subcommands {
+ if let Some(ref aliases) = sc.p.meta.aliases {
+ opts = format!(
+ "{} {}",
+ opts,
+ aliases
+ .iter()
+ .map(|&(n, _)| n)
+ .fold(String::new(), |acc, a| format!("{} {}", acc, a))
+ );
+ }
+ }
+ opts
+ }
+}
diff --git a/clap/src/completions/elvish.rs b/clap/src/completions/elvish.rs
new file mode 100644
index 0000000..9a5f21a
--- /dev/null
+++ b/clap/src/completions/elvish.rs
@@ -0,0 +1,126 @@
+// Std
+use std::io::Write;
+
+// Internal
+use app::parser::Parser;
+use INTERNAL_ERROR_MSG;
+
+pub struct ElvishGen<'a, 'b>
+where
+ 'a: 'b,
+{
+ p: &'b Parser<'a, 'b>,
+}
+
+impl<'a, 'b> ElvishGen<'a, 'b> {
+ pub fn new(p: &'b Parser<'a, 'b>) -> Self { ElvishGen { p: p } }
+
+ pub fn generate_to<W: Write>(&self, buf: &mut W) {
+ let bin_name = self.p.meta.bin_name.as_ref().unwrap();
+
+ let mut names = vec![];
+ let subcommands_cases =
+ generate_inner(self.p, "", &mut names);
+
+ let result = format!(r#"
+edit:completion:arg-completer[{bin_name}] = [@words]{{
+ fn spaces [n]{{
+ repeat $n ' ' | joins ''
+ }}
+ fn cand [text desc]{{
+ edit:complex-candidate $text &display-suffix=' '(spaces (- 14 (wcswidth $text)))$desc
+ }}
+ command = '{bin_name}'
+ for word $words[1:-1] {{
+ if (has-prefix $word '-') {{
+ break
+ }}
+ command = $command';'$word
+ }}
+ 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<'a, 'b, 'p>(
+ p: &'p Parser<'a, 'b>,
+ previous_command_name: &str,
+ names: &mut Vec<&'p str>,
+) -> String {
+ debugln!("ElvishGen::generate_inner;");
+ let command_name = if previous_command_name.is_empty() {
+ p.meta.bin_name.as_ref().expect(INTERNAL_ERROR_MSG).clone()
+ } else {
+ format!("{};{}", previous_command_name, &p.meta.name)
+ };
+
+ let mut completions = String::new();
+ let preamble = String::from("\n cand ");
+
+ for option in p.opts() {
+ if let Some(data) = option.s.short {
+ let tooltip = get_tooltip(option.b.help, data);
+ completions.push_str(&preamble);
+ completions.push_str(format!("-{} '{}'", data, tooltip).as_str());
+ }
+ if let Some(data) = option.s.long {
+ let tooltip = get_tooltip(option.b.help, data);
+ completions.push_str(&preamble);
+ completions.push_str(format!("--{} '{}'", data, tooltip).as_str());
+ }
+ }
+
+ for flag in p.flags() {
+ if let Some(data) = flag.s.short {
+ let tooltip = get_tooltip(flag.b.help, data);
+ completions.push_str(&preamble);
+ completions.push_str(format!("-{} '{}'", data, tooltip).as_str());
+ }
+ if let Some(data) = flag.s.long {
+ let tooltip = get_tooltip(flag.b.help, data);
+ completions.push_str(&preamble);
+ completions.push_str(format!("--{} '{}'", data, tooltip).as_str());
+ }
+ }
+
+ for subcommand in &p.subcommands {
+ let data = &subcommand.p.meta.name;
+ let tooltip = get_tooltip(subcommand.p.meta.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.subcommands {
+ let subcommand_subcommands_cases =
+ generate_inner(&subcommand.p, &command_name, names);
+ subcommands_cases.push_str(&subcommand_subcommands_cases);
+ }
+
+ subcommands_cases
+}
diff --git a/clap/src/completions/fish.rs b/clap/src/completions/fish.rs
new file mode 100644
index 0000000..c2c5a5e
--- /dev/null
+++ b/clap/src/completions/fish.rs
@@ -0,0 +1,99 @@
+// Std
+use std::io::Write;
+
+// Internal
+use app::parser::Parser;
+
+pub struct FishGen<'a, 'b>
+where
+ 'a: 'b,
+{
+ p: &'b Parser<'a, 'b>,
+}
+
+impl<'a, 'b> FishGen<'a, 'b> {
+ pub fn new(p: &'b Parser<'a, 'b>) -> Self { FishGen { p: p } }
+
+ pub fn generate_to<W: Write>(&self, buf: &mut W) {
+ let command = self.p.meta.bin_name.as_ref().unwrap();
+ let mut buffer = String::new();
+ gen_fish_inner(command, self, command, &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, comp_gen: &FishGen, subcommand: &str, buffer: &mut String) {
+ debugln!("FishGen::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 {} -n ", root_command);
+ if root_command == subcommand {
+ basic_template.push_str("\"__fish_use_subcommand\"");
+ } else {
+ basic_template.push_str(format!("\"__fish_seen_subcommand_from {}\"", subcommand).as_str());
+ }
+
+ for option in comp_gen.p.opts() {
+ let mut template = basic_template.clone();
+ if let Some(data) = option.s.short {
+ template.push_str(format!(" -s {}", data).as_str());
+ }
+ if let Some(data) = option.s.long {
+ template.push_str(format!(" -l {}", data).as_str());
+ }
+ if let Some(data) = option.b.help {
+ template.push_str(format!(" -d '{}'", escape_string(data)).as_str());
+ }
+ if let Some(ref data) = option.v.possible_vals {
+ template.push_str(format!(" -r -f -a \"{}\"", data.join(" ")).as_str());
+ }
+ buffer.push_str(template.as_str());
+ buffer.push_str("\n");
+ }
+
+ for flag in comp_gen.p.flags() {
+ let mut template = basic_template.clone();
+ if let Some(data) = flag.s.short {
+ template.push_str(format!(" -s {}", data).as_str());
+ }
+ if let Some(data) = flag.s.long {
+ template.push_str(format!(" -l {}", data).as_str());
+ }
+ if let Some(data) = flag.b.help {
+ template.push_str(format!(" -d '{}'", escape_string(data)).as_str());
+ }
+ buffer.push_str(template.as_str());
+ buffer.push_str("\n");
+ }
+
+ for subcommand in &comp_gen.p.subcommands {
+ let mut template = basic_template.clone();
+ template.push_str(" -f");
+ template.push_str(format!(" -a \"{}\"", &subcommand.p.meta.name).as_str());
+ if let Some(data) = subcommand.p.meta.about {
+ template.push_str(format!(" -d '{}'", escape_string(data)).as_str())
+ }
+ buffer.push_str(template.as_str());
+ buffer.push_str("\n");
+ }
+
+ // generate options of subcommands
+ for subcommand in &comp_gen.p.subcommands {
+ let sub_comp_gen = FishGen::new(&subcommand.p);
+ gen_fish_inner(root_command, &sub_comp_gen, &subcommand.to_string(), buffer);
+ }
+}
diff --git a/clap/src/completions/macros.rs b/clap/src/completions/macros.rs
new file mode 100644
index 0000000..653c72c
--- /dev/null
+++ b/clap/src/completions/macros.rs
@@ -0,0 +1,28 @@
+macro_rules! w {
+ ($buf:expr, $to_w:expr) => {
+ match $buf.write_all($to_w) {
+ Ok(..) => (),
+ Err(..) => panic!("Failed to write to completions file"),
+ }
+ };
+}
+
+macro_rules! get_zsh_arg_conflicts {
+ ($p:ident, $arg:ident, $msg:ident) => {
+ if let Some(conf_vec) = $arg.blacklist() {
+ let mut v = vec![];
+ for arg_name in conf_vec {
+ let arg = $p.find_any_arg(arg_name).expect($msg);
+ if let Some(s) = arg.short() {
+ v.push(format!("-{}", s));
+ }
+ if let Some(l) = arg.long() {
+ v.push(format!("--{}", l));
+ }
+ }
+ v.join(" ")
+ } else {
+ String::new()
+ }
+ }
+}
diff --git a/clap/src/completions/mod.rs b/clap/src/completions/mod.rs
new file mode 100644
index 0000000..a3306d7
--- /dev/null
+++ b/clap/src/completions/mod.rs
@@ -0,0 +1,179 @@
+#[macro_use]
+mod macros;
+mod bash;
+mod fish;
+mod zsh;
+mod powershell;
+mod elvish;
+mod shell;
+
+// Std
+use std::io::Write;
+
+// Internal
+use app::parser::Parser;
+use self::bash::BashGen;
+use self::fish::FishGen;
+use self::zsh::ZshGen;
+use self::powershell::PowerShellGen;
+use self::elvish::ElvishGen;
+pub use self::shell::Shell;
+
+pub struct ComplGen<'a, 'b>
+where
+ 'a: 'b,
+{
+ p: &'b Parser<'a, 'b>,
+}
+
+impl<'a, 'b> ComplGen<'a, 'b> {
+ pub fn new(p: &'b Parser<'a, 'b>) -> Self { ComplGen { p: p } }
+
+ pub fn generate<W: Write>(&self, for_shell: Shell, buf: &mut W) {
+ match for_shell {
+ Shell::Bash => BashGen::new(self.p).generate_to(buf),
+ Shell::Fish => FishGen::new(self.p).generate_to(buf),
+ Shell::Zsh => ZshGen::new(self.p).generate_to(buf),
+ Shell::PowerShell => PowerShellGen::new(self.p).generate_to(buf),
+ Shell::Elvish => ElvishGen::new(self.p).generate_to(buf),
+ }
+ }
+}
+
+// Gets all subcommands including child subcommands in the form of 'name' where the name
+// is a single word (i.e. "install") of the path to said subcommand (i.e.
+// "rustup toolchain install")
+//
+// Also note, aliases are treated as their own subcommands but duplicates of whatever they're
+// aliasing.
+pub fn all_subcommand_names(p: &Parser) -> Vec<String> {
+ debugln!("all_subcommand_names;");
+ let mut subcmds: Vec<_> = subcommands_of(p)
+ .iter()
+ .map(|&(ref n, _)| n.clone())
+ .collect();
+ for sc_v in p.subcommands.iter().map(|s| all_subcommand_names(&s.p)) {
+ subcmds.extend(sc_v);
+ }
+ subcmds.sort();
+ subcmds.dedup();
+ subcmds
+}
+
+// Gets all subcommands including child subcommands in the form of ('name', 'bin_name') where the name
+// is a single word (i.e. "install") of the path and full bin_name of said subcommand (i.e.
+// "rustup toolchain install")
+//
+// Also note, aliases are treated as their own subcommands but duplicates of whatever they're
+// aliasing.
+pub fn all_subcommands(p: &Parser) -> Vec<(String, String)> {
+ debugln!("all_subcommands;");
+ let mut subcmds: Vec<_> = subcommands_of(p);
+ for sc_v in p.subcommands.iter().map(|s| all_subcommands(&s.p)) {
+ subcmds.extend(sc_v);
+ }
+ subcmds
+}
+
+// Gets all subcommands excluding child subcommands in the form of (name, bin_name) where the name
+// is a single word (i.e. "install") and the bin_name is a space delineated list of the path to said
+// subcommand (i.e. "rustup toolchain install")
+//
+// Also note, aliases are treated as their own subcommands but duplicates of whatever they're
+// aliasing.
+pub fn subcommands_of(p: &Parser) -> Vec<(String, String)> {
+ debugln!(
+ "subcommands_of: name={}, bin_name={}",
+ p.meta.name,
+ p.meta.bin_name.as_ref().unwrap()
+ );
+ let mut subcmds = vec![];
+
+ debugln!(
+ "subcommands_of: Has subcommands...{:?}",
+ p.has_subcommands()
+ );
+ if !p.has_subcommands() {
+ let mut ret = vec![];
+ debugln!("subcommands_of: Looking for aliases...");
+ if let Some(ref aliases) = p.meta.aliases {
+ for &(n, _) in aliases {
+ debugln!("subcommands_of:iter:iter: Found alias...{}", n);
+ let mut als_bin_name: Vec<_> =
+ p.meta.bin_name.as_ref().unwrap().split(' ').collect();
+ als_bin_name.push(n);
+ let old = als_bin_name.len() - 2;
+ als_bin_name.swap_remove(old);
+ ret.push((n.to_owned(), als_bin_name.join(" ")));
+ }
+ }
+ return ret;
+ }
+ for sc in &p.subcommands {
+ debugln!(
+ "subcommands_of:iter: name={}, bin_name={}",
+ sc.p.meta.name,
+ sc.p.meta.bin_name.as_ref().unwrap()
+ );
+
+ debugln!("subcommands_of:iter: Looking for aliases...");
+ if let Some(ref aliases) = sc.p.meta.aliases {
+ for &(n, _) in aliases {
+ debugln!("subcommands_of:iter:iter: Found alias...{}", n);
+ let mut als_bin_name: Vec<_> =
+ p.meta.bin_name.as_ref().unwrap().split(' ').collect();
+ als_bin_name.push(n);
+ let old = als_bin_name.len() - 2;
+ als_bin_name.swap_remove(old);
+ subcmds.push((n.to_owned(), als_bin_name.join(" ")));
+ }
+ }
+ subcmds.push((
+ sc.p.meta.name.clone(),
+ sc.p.meta.bin_name.as_ref().unwrap().clone(),
+ ));
+ }
+ subcmds
+}
+
+pub fn get_all_subcommand_paths(p: &Parser, first: bool) -> Vec<String> {
+ debugln!("get_all_subcommand_paths;");
+ let mut subcmds = vec![];
+ if !p.has_subcommands() {
+ if !first {
+ let name = &*p.meta.name;
+ let path = p.meta.bin_name.as_ref().unwrap().clone().replace(" ", "__");
+ let mut ret = vec![path.clone()];
+ if let Some(ref aliases) = p.meta.aliases {
+ for &(n, _) in aliases {
+ ret.push(path.replace(name, n));
+ }
+ }
+ return ret;
+ }
+ return vec![];
+ }
+ for sc in &p.subcommands {
+ let name = &*sc.p.meta.name;
+ let path = sc.p
+ .meta
+ .bin_name
+ .as_ref()
+ .unwrap()
+ .clone()
+ .replace(" ", "__");
+ subcmds.push(path.clone());
+ if let Some(ref aliases) = sc.p.meta.aliases {
+ for &(n, _) in aliases {
+ subcmds.push(path.replace(name, n));
+ }
+ }
+ }
+ for sc_v in p.subcommands
+ .iter()
+ .map(|s| get_all_subcommand_paths(&s.p, false))
+ {
+ subcmds.extend(sc_v);
+ }
+ subcmds
+}
diff --git a/clap/src/completions/powershell.rs b/clap/src/completions/powershell.rs
new file mode 100644
index 0000000..9fc77c7
--- /dev/null
+++ b/clap/src/completions/powershell.rs
@@ -0,0 +1,139 @@
+// Std
+use std::io::Write;
+
+// Internal
+use app::parser::Parser;
+use INTERNAL_ERROR_MSG;
+
+pub struct PowerShellGen<'a, 'b>
+where
+ 'a: 'b,
+{
+ p: &'b Parser<'a, 'b>,
+}
+
+impl<'a, 'b> PowerShellGen<'a, 'b> {
+ pub fn new(p: &'b Parser<'a, 'b>) -> Self { PowerShellGen { p: p } }
+
+ pub fn generate_to<W: Write>(&self, buf: &mut W) {
+ let bin_name = self.p.meta.bin_name.as_ref().unwrap();
+
+ let mut names = vec![];
+ let subcommands_cases =
+ generate_inner(self.p, "", &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('-')) {{
+ 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<'a, 'b, 'p>(
+ p: &'p Parser<'a, 'b>,
+ previous_command_name: &str,
+ names: &mut Vec<&'p str>,
+) -> String {
+ debugln!("PowerShellGen::generate_inner;");
+ let command_name = if previous_command_name.is_empty() {
+ p.meta.bin_name.as_ref().expect(INTERNAL_ERROR_MSG).clone()
+ } else {
+ format!("{};{}", previous_command_name, &p.meta.name)
+ };
+
+ let mut completions = String::new();
+ let preamble = String::from("\n [CompletionResult]::new(");
+
+ for option in p.opts() {
+ if let Some(data) = option.s.short {
+ let tooltip = get_tooltip(option.b.help, data);
+ completions.push_str(&preamble);
+ completions.push_str(format!("'-{}', '{}', {}, '{}')",
+ data, data, "[CompletionResultType]::ParameterName", tooltip).as_str());
+ }
+ if let Some(data) = option.s.long {
+ let tooltip = get_tooltip(option.b.help, data);
+ completions.push_str(&preamble);
+ completions.push_str(format!("'--{}', '{}', {}, '{}')",
+ data, data, "[CompletionResultType]::ParameterName", tooltip).as_str());
+ }
+ }
+
+ for flag in p.flags() {
+ if let Some(data) = flag.s.short {
+ let tooltip = get_tooltip(flag.b.help, data);
+ completions.push_str(&preamble);
+ completions.push_str(format!("'-{}', '{}', {}, '{}')",
+ data, data, "[CompletionResultType]::ParameterName", tooltip).as_str());
+ }
+ if let Some(data) = flag.s.long {
+ let tooltip = get_tooltip(flag.b.help, data);
+ completions.push_str(&preamble);
+ completions.push_str(format!("'--{}', '{}', {}, '{}')",
+ data, data, "[CompletionResultType]::ParameterName", tooltip).as_str());
+ }
+ }
+
+ for subcommand in &p.subcommands {
+ let data = &subcommand.p.meta.name;
+ let tooltip = get_tooltip(subcommand.p.meta.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.subcommands {
+ let subcommand_subcommands_cases =
+ generate_inner(&subcommand.p, &command_name, names);
+ subcommands_cases.push_str(&subcommand_subcommands_cases);
+ }
+
+ subcommands_cases
+}
diff --git a/clap/src/completions/shell.rs b/clap/src/completions/shell.rs
new file mode 100644
index 0000000..19aab86
--- /dev/null
+++ b/clap/src/completions/shell.rs
@@ -0,0 +1,52 @@
+#[allow(deprecated, unused_imports)]
+use std::ascii::AsciiExt;
+use std::str::FromStr;
+use std::fmt;
+
+/// Describes which shell to produce a completions file for
+#[cfg_attr(feature = "lints", allow(enum_variant_names))]
+#[derive(Debug, Copy, Clone)]
+pub enum Shell {
+ /// Generates a .bash completion file for the Bourne Again SHell (BASH)
+ Bash,
+ /// Generates a .fish completion file for the Friendly Interactive SHell (fish)
+ Fish,
+ /// Generates a completion file for the Z SHell (ZSH)
+ Zsh,
+ /// Generates a completion file for PowerShell
+ PowerShell,
+ /// Generates a completion file for Elvish
+ Elvish,
+}
+
+impl Shell {
+ /// A list of possible variants in `&'static str` form
+ pub fn variants() -> [&'static str; 5] { ["zsh", "bash", "fish", "powershell", "elvish"] }
+}
+
+impl FromStr for Shell {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "ZSH" | _ if s.eq_ignore_ascii_case("zsh") => Ok(Shell::Zsh),
+ "FISH" | _ if s.eq_ignore_ascii_case("fish") => Ok(Shell::Fish),
+ "BASH" | _ if s.eq_ignore_ascii_case("bash") => Ok(Shell::Bash),
+ "POWERSHELL" | _ if s.eq_ignore_ascii_case("powershell") => Ok(Shell::PowerShell),
+ "ELVISH" | _ if s.eq_ignore_ascii_case("elvish") => Ok(Shell::Elvish),
+ _ => Err(String::from("[valid values: bash, fish, zsh, powershell, elvish]")),
+ }
+ }
+}
+
+impl fmt::Display for Shell {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Shell::Bash => write!(f, "BASH"),
+ Shell::Fish => write!(f, "FISH"),
+ Shell::Zsh => write!(f, "ZSH"),
+ Shell::PowerShell => write!(f, "POWERSHELL"),
+ Shell::Elvish => write!(f, "ELVISH"),
+ }
+ }
+}
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")
+}