diff --git a/Cargo.toml b/Cargo.toml index b4b40c352e31..610a5b50092e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,7 +84,10 @@ features = ["doc"] [workspace] members = [ "clap_derive", + "clap_generate", ] default-members = [ - ".", "clap_derive", + ".", + "clap_derive", + "clap_generate", ] diff --git a/clap_derive/Cargo.toml b/clap_derive/Cargo.toml index 489c031dcb55..a4734eeff26f 100644 --- a/clap_derive/Cargo.toml +++ b/clap_derive/Cargo.toml @@ -46,6 +46,7 @@ proc-macro-error = "0.4.3" clap = { path = "../", version = "3.0.0-beta.1" } trybuild = "1.0.5" rustversion = "1" +version-sync = "0.8" [features] default = [] diff --git a/clap_derive/README.md b/clap_derive/README.md index ff89f4776161..68851041ac7c 100644 --- a/clap_derive/README.md +++ b/clap_derive/README.md @@ -1,9 +1,4 @@ -# Work in Progress - -This crate is currently a work in progress and not meant to be used. Please use [`structopt`](https://github.com/TeXitoi/structopt) -while this crate is being built. - -# clap_derive[![Build status](https://travis-ci.org/clap-rs/clap_derive.svg?branch=master)](https://travis-ci.org/clap-rs/clap_derive) [![](https://img.shields.io/crates/v/clap_derive.svg)](https://crates.io/crates/clap_derive) [![](https://docs.rs/clap_derive/badge.svg)](https://docs.rs/clap_derive) +# clap_derive Parse command line argument by defining a struct. It combines [structopt](https://github.com/TeXitoi/structopt) and [clap](https://crates.io/crates/clap) into a single experience. This crate is used by clap, and not meant to be used directly by consumers. diff --git a/clap_generate/Cargo.toml b/clap_generate/Cargo.toml new file mode 100644 index 000000000000..0232ac41f2c3 --- /dev/null +++ b/clap_generate/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "clap_generate" +version = "3.0.0-beta.1" +edition = "2018" +authors = [ + "Kevin K. " +] +include = [ + "src/**/*", + "Cargo.toml", + "README.md" +] +description = "A generator library used with clap for shell completion scripts, manpage, etc." +repository = "https://github.com/clap-rs/clap/tree/master/clap_generate" +documentation = "https://docs.rs/clap_generate" +homepage = "https://clap.rs/" +keywords = [ + "clap", + "cli", + "generate", + "completion", + "manpage", + "parse" +] +categories = ["command-line-interface"] +license = "MIT OR Apache-2.0" +readme = "README.md" + +[badges] +is-it-maintained-issue-resolution = { repository = "clap-rs/clap" } +is-it-maintained-open-issues = { repository = "clap-rs/clap" } +maintenance = {status = "actively-developed"} + +[dependencies] +clap = { path = "../", version = "3.0.0-beta.1" } + +[dev-dependencies] +regex = "1.0" +version-sync = "0.8" + +[features] +default = [] +unstable = ["clap/unstable"] +nightly = ["clap/nightly"] +debug = ["clap/debug"] +doc = [] + +[package.metadata.docs.rs] +features = ["doc"] diff --git a/clap_generate/README.md b/clap_generate/README.md new file mode 100644 index 000000000000..17ce782f8b3c --- /dev/null +++ b/clap_generate/README.md @@ -0,0 +1 @@ +# clap_generate diff --git a/clap_generate/src/generators/mod.rs b/clap_generate/src/generators/mod.rs new file mode 100644 index 000000000000..83ce23bcb1c5 --- /dev/null +++ b/clap_generate/src/generators/mod.rs @@ -0,0 +1,215 @@ +mod shells; + +// Std +use std::io::Write; + +// Internal +use clap::*; + +pub use shells::*; + +/// 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. + /// + /// # Examples + /// + /// ``` + /// # use std::io::Write; + /// # use clap::App; + /// use clap_generate::Generator; + /// + /// pub struct Fish; + /// + /// impl Generator for Fish { + /// # fn generate(app: &app, buf: &mut W) {} + /// fn file_name(name: &str) -> String { + /// format!("{}.fish", name) + /// } + /// } + /// ``` + fn file_name(name: &str) -> String; + + /// Generates output out of [`clap::App`](../clap/struct.App.html). + /// + /// # Examples + /// + /// The following example generator displays the [`clap::App`](../clap/struct.App.html) + /// as if it is printed using [`std::println`](https://doc.rust-lang.org/std/macro.println.html). + /// + /// ``` + /// use std::io::Write; + /// use clap::App; + /// use clap_generate::Generator; + /// + /// pub struct ClapDebug; + /// + /// impl Generator for ClapDebug { + /// fn generate(app: &app, buf: &mut W) { + /// buf.write_all(app).unwrap(); + /// } + /// # fn file_name>(name: S) -> String { + /// # name.into() + /// # } + /// } + /// ``` + fn generate(app: &App, buf: &mut W); + + /// 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. + fn all_subcommand_names(app: &App) -> Vec { + debugln!("all_subcommand_names;"); + + let mut subcmds: Vec<_> = Self::subcommands_of(app) + .iter() + .map(|&(ref n, _)| n.clone()) + .collect(); + + for sc_v in subcommands!(app).map(|s| Self::all_subcommand_names(&s)) { + 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. + fn all_subcommands(app: &App) -> Vec<(String, String)> { + debugln!("all_subcommands;"); + + let mut subcmds: Vec<_> = Self::subcommands_of(app); + + for sc_v in subcommands!(app).map(|s| Self::all_subcommands(&s)) { + subcmds.extend(sc_v); + } + + subcmds + } + + /// Gets all subcommands exlcuding 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 deliniated 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. + fn subcommands_of(p: &App) -> Vec<(String, String)> { + debugln!( + "subcommands_of: name={}, bin_name={}", + p.name, + p.bin_name.as_ref().unwrap() + ); + debugln!( + "subcommands_of: Has subcommands...{:?}", + p.has_subcommands() + ); + + let mut subcmds = vec![]; + + if !p.has_subcommands() { + let mut ret = vec![]; + + debugln!("subcommands_of: Looking for aliases..."); + + if let Some(ref aliases) = p.aliases { + for &(n, _) in aliases { + debugln!("subcommands_of:iter:iter: Found alias...{}", n); + + let mut als_bin_name: Vec<_> = + p.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 subcommands!(p) { + debugln!( + "subcommands_of:iter: name={}, bin_name={}", + sc.name, + sc.bin_name.as_ref().unwrap() + ); + debugln!("subcommands_of:iter: Looking for aliases..."); + + if let Some(ref aliases) = sc.aliases { + for &(n, _) in aliases { + debugln!("subcommands_of:iter:iter: Found alias...{}", n); + + let mut als_bin_name: Vec<_> = + p.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.name.clone(), sc.get_bin_name().unwrap().to_string())); + } + + subcmds + } + + /// TODO + fn get_all_subcommand_paths(p: &App, first: bool) -> Vec { + debugln!("get_all_subcommand_paths;"); + + let mut subcmds = vec![]; + + if !p.has_subcommands() { + if !first { + let name = &*p.name; + let path = p.get_bin_name().unwrap().to_string().replace(" ", "__"); + let mut ret = vec![path.clone()]; + + if let Some(ref aliases) = p.aliases { + for &(n, _) in aliases { + ret.push(path.replace(name, n)); + } + } + + return ret; + } + + return vec![]; + } + + for sc in subcommands!(p) { + let name = &*sc.name; + let path = sc.get_bin_name().unwrap().to_string().replace(" ", "__"); + + subcmds.push(path.clone()); + + if let Some(ref aliases) = sc.aliases { + for &(n, _) in aliases { + subcmds.push(path.replace(name, n)); + } + } + } + + for sc_v in subcommands!(p).map(|s| Self::get_all_subcommand_paths(&s, false)) { + subcmds.extend(sc_v); + } + + subcmds + } +} diff --git a/clap_generate/src/generators/shells/bash.rs b/clap_generate/src/generators/shells/bash.rs new file mode 100644 index 000000000000..0858188a036f --- /dev/null +++ b/clap_generate/src/generators/shells/bash.rs @@ -0,0 +1,212 @@ +// Std +use std::io::Write; + +// Internal +use crate::Generator; +use clap::*; + +/// Generate bash completion file +pub struct Bash; + +impl Generator for Bash { + fn file_name(name: &str) -> String { + format!("{}.bash", name) + } + + fn generate(app: &App, buf: &mut W) { + let bin_name = app.get_bin_name().unwrap(); + + 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 + {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 = bin_name, + name_opts = all_options_for_path(app, bin_name), + name_opts_details = option_details_for_path(app, bin_name), + subcmds = all_subcommands(app), + subcmd_details = subcommand_details(app) + ) + .as_bytes() + ); + } +} + +fn all_subcommands(app: &App) -> String { + debugln!("Bash::all_subcommands;"); + + let mut subcmds = String::new(); + let scs = Bash::all_subcommand_names(app); + + for sc in &scs { + subcmds = format!( + "{} + {name}) + cmd+=\"__{fn_name}\" + ;;", + subcmds, + name = sc, + fn_name = sc.replace("-", "__") + ); + } + + subcmds +} + +fn subcommand_details(app: &App) -> String { + debugln!("Bash::subcommand_details;"); + + let mut subcmd_dets = String::new(); + let mut scs = Bash::get_all_subcommand_paths(app, true); + + scs.sort(); + scs.dedup(); + + for sc in &scs { + subcmd_dets = 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_dets, + subcmd = sc.replace("-", "__"), + sc_opts = all_options_for_path(app, &*sc), + level = sc.split("__").map(|_| 1).fold(0, |acc, n| acc + n), + opts_details = option_details_for_path(app, &*sc) + ); + } + + subcmd_dets +} + +fn option_details_for_path(app: &App, path: &str) -> String { + debugln!("Bash::option_details_for_path: path={}", path); + + let mut p = app; + + for sc in path.split("__").skip(1) { + debugln!("Bash::option_details_for_path:iter: sc={}", sc); + p = &find_subcmd!(p, sc).unwrap(); + } + + let mut opts = String::new(); + + for o in opts!(p) { + if let Some(l) = o.long { + opts = format!( + "{} + --{}) + COMPREPLY=({}) + return 0 + ;;", + opts, + l, + vals_for(o) + ); + } + + if let Some(s) = o.short { + opts = format!( + "{} + -{}) + COMPREPLY=({}) + return 0 + ;;", + opts, + s, + vals_for(o) + ); + } + } + + opts +} + +fn vals_for(o: &Arg) -> String { + debugln!("Bash::vals_for: o={}", o.name); + + if let Some(ref vals) = o.possible_vals { + format!("$(compgen -W \"{}\" -- ${{cur}})", vals.join(" ")) + } else { + String::from("$(compgen -f ${cur})") + } +} + +fn all_options_for_path(app: &App, path: &str) -> String { + debugln!("Bash::all_options_for_path: path={}", path); + + let mut p = app; + + for sc in path.split("__").skip(1) { + debugln!("Bash::all_options_for_path:iter: sc={}", sc); + p = &find_subcmd!(p, sc).unwrap(); + } + + let opts = format!( + "{shorts} {longs} {pos} {subcmds}", + shorts = shorts!(p).fold(String::new(), |acc, s| format!("{} -{}", acc, s)), + // Handles aliases too + longs = longs!(p).fold(String::new(), |acc, l| format!( + "{} --{}", + acc, + l.to_str().unwrap() + )), + pos = positionals!(p).fold(String::new(), |acc, p| format!("{} {}", acc, p)), + // Handles aliases too + subcmds = sc_names!(p).fold(String::new(), |acc, s| format!("{} {}", acc, s)) + ); + + opts +} diff --git a/clap_generate/src/generators/shells/elvish.rs b/clap_generate/src/generators/shells/elvish.rs new file mode 100644 index 000000000000..e4c1b796157f --- /dev/null +++ b/clap_generate/src/generators/shells/elvish.rs @@ -0,0 +1,133 @@ +// Std +use std::io::Write; + +// Internal +use crate::Generator; +use crate::INTERNAL_ERROR_MSG; +use clap::*; + +/// Generate elvish completion file +pub struct Elvish; + +impl Generator for Elvish { + fn file_name(name: &str) -> String { + format!("{}.elv", name) + } + + fn generate(app: &App, buf: &mut W) { + let bin_name = app.get_bin_name().unwrap(); + + let mut names = vec![]; + let subcommands_cases = generate_inner(app, "", &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(help: Option<&str>, data: T) -> String { + match help { + Some(help) => escape_string(help), + _ => data.to_string(), + } +} + +fn generate_inner<'b>( + p: &'b App<'b>, + previous_command_name: &str, + names: &mut Vec<&'b str>, +) -> String { + debugln!("Elvish::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.name) + }; + + let mut completions = String::new(); + let preamble = String::from("\n cand "); + + for option in opts!(p) { + if let Some(data) = option.short { + let tooltip = get_tooltip(option.help, data); + + completions.push_str(&preamble); + completions.push_str(format!("-{} '{}'", data, tooltip).as_str()); + } + + if let Some(data) = option.long { + let tooltip = get_tooltip(option.help, data); + + completions.push_str(&preamble); + completions.push_str(format!("--{} '{}'", data, tooltip).as_str()); + } + } + + for flag in flags!(p) { + if let Some(data) = flag.short { + let tooltip = get_tooltip(flag.help, data); + + completions.push_str(&preamble); + completions.push_str(format!("-{} '{}'", data, tooltip).as_str()); + } + + if let Some(data) = flag.long { + let tooltip = get_tooltip(flag.help, data); + + completions.push_str(&preamble); + completions.push_str(format!("--{} '{}'", data, tooltip).as_str()); + } + } + + for subcommand in &p.subcommands { + let data = &subcommand.name; + let tooltip = get_tooltip(subcommand.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, &command_name, names); + subcommands_cases.push_str(&subcommand_subcommands_cases); + } + + subcommands_cases +} diff --git a/clap_generate/src/generators/shells/fish.rs b/clap_generate/src/generators/shells/fish.rs new file mode 100644 index 000000000000..9168960fff07 --- /dev/null +++ b/clap_generate/src/generators/shells/fish.rs @@ -0,0 +1,113 @@ +// Std +use std::io::Write; + +// Internal +use crate::Generator; +use clap::*; + +/// Generate fish completion file +pub struct Fish; + +impl Generator for Fish { + fn file_name(name: &str) -> String { + format!("{}.fish", name) + } + + fn generate(app: &App, buf: &mut W) { + let command = app.get_bin_name().unwrap(); + let mut buffer = String::new(); + + gen_fish_inner(command, app, 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, app: &App, subcommand: &str, buffer: &mut String) { + debugln!("Fish::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 opts!(app) { + let mut template = basic_template.clone(); + + if let Some(data) = option.short { + template.push_str(format!(" -s {}", data).as_str()); + } + + if let Some(data) = option.long { + template.push_str(format!(" -l {}", data).as_str()); + } + + if let Some(data) = option.help { + template.push_str(format!(" -d '{}'", escape_string(data)).as_str()); + } + + if let Some(ref data) = option.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 flags!(app) { + let mut template = basic_template.clone(); + + if let Some(data) = flag.short { + template.push_str(format!(" -s {}", data).as_str()); + } + + if let Some(data) = flag.long { + template.push_str(format!(" -l {}", data).as_str()); + } + + if let Some(data) = flag.help { + template.push_str(format!(" -d '{}'", escape_string(data)).as_str()); + } + + buffer.push_str(template.as_str()); + buffer.push_str("\n"); + } + + for subcommand in subcommands!(app) { + let mut template = basic_template.clone(); + + template.push_str(" -f"); + template.push_str(format!(" -a \"{}\"", &subcommand.name).as_str()); + + if let Some(data) = subcommand.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 subapp in &app.subcommands { + gen_fish_inner(root_command, subapp, &subapp.name, buffer); + } +} diff --git a/clap_generate/src/generators/shells/mod.rs b/clap_generate/src/generators/shells/mod.rs new file mode 100644 index 000000000000..ef3f4e52b56b --- /dev/null +++ b/clap_generate/src/generators/shells/mod.rs @@ -0,0 +1,11 @@ +mod bash; +mod elvish; +mod fish; +mod powershell; +mod zsh; + +pub use bash::Bash; +pub use elvish::Elvish; +pub use fish::Fish; +pub use powershell::PowerShell; +pub use zsh::Zsh; diff --git a/clap_generate/src/generators/shells/powershell.rs b/clap_generate/src/generators/shells/powershell.rs new file mode 100644 index 000000000000..09db88ea98c8 --- /dev/null +++ b/clap_generate/src/generators/shells/powershell.rs @@ -0,0 +1,171 @@ +// Std +use std::io::Write; + +// Internal +use crate::Generator; +use crate::INTERNAL_ERROR_MSG; +use clap::*; + +/// Generate powershell completion file +pub struct PowerShell; + +impl Generator for PowerShell { + fn file_name(name: &str) -> String { + format!("_{}.ps1", name) + } + + fn generate(app: &App, buf: &mut W) { + let bin_name = app.get_bin_name().unwrap(); + + let mut names = vec![]; + let subcommands_cases = generate_inner(app, "", &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(help: Option<&str>, data: T) -> String { + match help { + Some(help) => escape_string(&help), + _ => data.to_string(), + } +} + +fn generate_inner<'b>( + p: &'b App<'b>, + previous_command_name: &str, + names: &mut Vec<&'b str>, +) -> String { + debugln!("PowerShell::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.name) + }; + + let mut completions = String::new(); + let preamble = String::from("\n [CompletionResult]::new("); + + for option in opts!(p) { + if let Some(data) = option.short { + let tooltip = get_tooltip(option.help, data); + + completions.push_str(&preamble); + completions.push_str( + format!( + "'-{}', '{}', {}, '{}')", + data, data, "[CompletionResultType]::ParameterName", tooltip + ) + .as_str(), + ); + } + + if let Some(data) = option.long { + let tooltip = get_tooltip(option.help, data); + + completions.push_str(&preamble); + completions.push_str( + format!( + "'--{}', '{}', {}, '{}')", + data, data, "[CompletionResultType]::ParameterName", tooltip + ) + .as_str(), + ); + } + } + + for flag in flags!(p) { + if let Some(data) = flag.short { + let tooltip = get_tooltip(flag.help, data); + + completions.push_str(&preamble); + completions.push_str( + format!( + "'-{}', '{}', {}, '{}')", + data, data, "[CompletionResultType]::ParameterName", tooltip + ) + .as_str(), + ); + } + + if let Some(data) = flag.long { + let tooltip = get_tooltip(flag.help, data); + + completions.push_str(&preamble); + completions.push_str( + format!( + "'--{}', '{}', {}, '{}')", + data, data, "[CompletionResultType]::ParameterName", tooltip + ) + .as_str(), + ); + } + } + + for subcommand in subcommands!(p) { + let data = &subcommand.name; + let tooltip = get_tooltip(subcommand.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, &command_name, names); + subcommands_cases.push_str(&subcommand_subcommands_cases); + } + + subcommands_cases +} diff --git a/clap_generate/src/generators/shells/zsh.rs b/clap_generate/src/generators/shells/zsh.rs new file mode 100644 index 000000000000..54fa14044c3a --- /dev/null +++ b/clap_generate/src/generators/shells/zsh.rs @@ -0,0 +1,526 @@ +// Std +use std::io::Write; + +// Internal +use crate::Generator; +use crate::INTERNAL_ERROR_MSG; +use clap::*; + +/// Generate zsh completion file +pub struct Zsh; + +impl Generator for Zsh { + fn file_name(name: &str) -> String { + format!("_{}", name) + } + + fn generate(app: &App, buf: &mut W) { + 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 = app.get_bin_name().unwrap(), + initial_args = get_args_of(app), + subcommands = get_subcommands_of(app), + subcommand_details = subcommand_details(app) + ) + .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 deliniated 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 deliniated 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: &App) -> String { + debugln!("ZshGen::subcommand_details;"); + + let name = p.get_bin_name().unwrap(); + + // 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 = name.replace(" ", "__"), + bin_name = name, + subcommands_and_args = subcommands_of(p) + )]; + + // Next we start looping through all the children, grandchildren, etc. + let mut all_subcommands = Zsh::all_subcommands(p); + + all_subcommands.sort(); + all_subcommands.dedup(); + + for &(_, ref bin_name) in &all_subcommands { + debugln!("Zsh::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: &App) -> String { + debugln!("Zsh::subcommands_of;"); + + let mut ret = vec![]; + + fn add_sc(sc: &App, n: &str, ret: &mut Vec) { + debugln!("Zsh::add_sc;"); + + let s = format!( + "\"{name}:{help}\" \\", + name = n, + help = sc + .about + .unwrap_or("") + .replace("[", "\\[") + .replace("]", "\\]") + ); + + if !s.is_empty() { + ret.push(s); + } + } + + // The subcommands + for sc in subcommands!(p) { + debugln!("Zsh::subcommands_of:iter: subcommand={}", sc.name); + + add_sc(sc, &sc.name, &mut ret); + + if let Some(ref v) = sc.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 deliniated bin_name such as "rustup toolchain install" +// [name_hyphen] = The full space deliniated 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: &App) -> String { + debugln!("Zsh::get_subcommands_of;"); + debugln!( + "Zsh::get_subcommands_of: Has subcommands...{:?}", + p.has_subcommands() + ); + + if !p.has_subcommands() { + return String::new(); + } + + let sc_names = Zsh::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.name, + name_hyphen = p.get_bin_name().unwrap().replace(" ", "-"), + subcommands = subcmds.join("\n"), + pos = positionals!(p).count() + 1 + ) +} + +fn parser_of<'b>(p: &'b App<'b>, mut sc: &str) -> &'b App<'b> { + debugln!("Zsh::parser_of: sc={}", sc); + + if sc == p.get_bin_name().unwrap_or(&String::new()) { + return p; + } + + sc = sc.split(" ").last().unwrap(); + find_subcmd!(p, sc).expect(INTERNAL_ERROR_MSG) +} + +// 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: &App) -> String { + debugln!("Zsh::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.bin_name.as_ref().unwrap().replace(" ", "__") + ) + } else { + String::new() + }; + + let sc = if p.has_subcommands() { + format!("\"*::: :->{name}\" \\", name = p.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: &App) -> String { + debugln!("Zsh::write_opts_of;"); + + let mut ret = vec![]; + + for o in opts!(p) { + debugln!("Zsh::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) + }; + + // @TODO @soundness should probably be either multiple occurrences or multiple values and + // not both + let multiple = if o.is_set(ArgSettings::MultipleOccurrences) + || o.is_set(ArgSettings::MultipleValues) + { + "*" + } else { + "" + }; + + let pv = if let Some(ref 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: &App) -> String { + debugln!("Zsh::write_flags_of;"); + + let mut ret = vec![]; + + for f in flags!(p) { + debugln!("Zsh::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::MultipleOccurrences) { + "*" + } else { + "" + }; + + if let Some(short) = f.short { + let s = format!( + "'{conflicts}{multiple}-{arg}[{help}]' \\", + multiple = multiple, + conflicts = conflicts, + arg = short, + help = help + ); + + debugln!("Zsh::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!("Zsh::write_flags_of:iter: Wrote...{}", &*l); + + ret.push(l); + } + } + + ret.join("\n") +} + +fn write_positionals_of(p: &App) -> String { + debugln!("Zsh::write_positionals_of;"); + + let mut ret = vec![]; + + for arg in positionals!(p) { + debugln!("Zsh::write_positionals_of:iter: arg={}", arg.name); + + let optional = if !arg.is_set(ArgSettings::Required) { + ":" + } else { + "" + }; + + let a = format!( + "'{optional}:{name}{help}:{action}' \\", + optional = optional, + name = arg.name, + help = arg + .help + .map_or("".to_owned(), |v| " -- ".to_owned() + v) + .replace("[", "\\[") + .replace("]", "\\]"), + action = arg + .possible_vals + .as_ref() + .map_or("_files".to_owned(), |values| { + format!( + "({})", + values + .iter() + .map(|v| escape_value(*v)) + .collect::>() + .join(" ") + ) + }) + ); + + debugln!("Zsh::write_positionals_of:iter: Wrote...{}", a); + + ret.push(a); + } + + ret.join("\n") +} diff --git a/clap_generate/src/lib.rs b/clap_generate/src/lib.rs new file mode 100644 index 000000000000..1433d3ad85b0 --- /dev/null +++ b/clap_generate/src/lib.rs @@ -0,0 +1,188 @@ +// Copyright ⓒ 2015-2018 Kevin B. Knapp +// +// `clap_generate` 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. + +//! Generates stuff for [`clap`](https://github.com/clap-rs/clap) based CLIs + +#![doc(html_root_url = "https://docs.rs/clap_generate/3.0.0-beta.1")] +#![deny( + missing_docs, + trivial_casts, + unused_import_braces, + unused_allocation, + trivial_numeric_casts +)] + +const INTERNAL_ERROR_MSG: &'static 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; + +/// Contains some popular generators +pub mod generators; + +use std::ffi::OsString; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +#[doc(inline)] +pub use generators::Generator; + +/// Generate a file for a specified generator at compile time. +/// +/// **NOTE:** to generate the file at compile time you must use a `build.rs` "Build Script" +/// +/// # 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 `App` definition into a separate file. Whether you +/// do this as a function, or bare App definition is a matter of personal preference. +/// +/// ``` +/// // src/cli.rs +/// +/// use clap::{App, Arg}; +/// +/// pub fn build_cli() -> App<'static> { +/// App::new("compl") +/// .about("Tests completions") +/// .arg(Arg::with_name("file") +/// .help("some input file")) +/// .subcommand(App::new("test") +/// .about("tests things") +/// .arg(Arg::with_name("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" +/// +/// [build-dependencies] +/// clap = "*" +/// ``` +/// +/// Next, we place a `build.rs` in our project root. +/// +/// ```ignore +/// use clap_generate::{generate_to, generators::Bash}; +/// +/// include!("src/cli.rs"); +/// +/// fn main() { +/// let outdir = match env::var_os("OUT_DIR") { +/// None => return, +/// Some(outdir) => outdir, +/// }; +/// +/// let mut app = build_cli(); +/// generate_to(&mut app, // 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 +/// } +/// ``` +/// +/// 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 +/// `/target/debug/build/myapp-/out/myapp.bash`. +/// +/// **NOTE:** Please look at the individual [generators](./generators/index.html) +/// to see the name of the files generated. +pub fn generate_to(app: &mut clap::App, bin_name: S, out_dir: T) +where + G: Generator, + T: Into, + S: Into, +{ + use std::error::Error; + + let out_dir = PathBuf::from(out_dir.into()); + let file_name = G::file_name(app.get_bin_name().unwrap()); + + let mut file = match File::create(out_dir.join(file_name)) { + Err(why) => panic!("couldn't create completion file: {}", why.description()), + Ok(file) => file, + }; + + generate::(app, &bin_name.into(), &mut file) +} + +/// 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](./fn.generate_to.html), +/// we can let users generate a completion script using a command: +/// +/// ```ignore +/// // src/main.rs +/// +/// mod cli; +/// use std::io; +/// use clap_generate::{generate_to, generators::Bash}; +/// +/// fn main() { +/// let matches = cli::build_cli().get_matches(); +/// +/// if matches.is_present("generate-bash-completions") { +/// generate(&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(app: &mut clap::App, bin_name: &str, buf: &mut W) +where + G: Generator, + W: Write, +{ + app.bin_name = Some(bin_name.into()); + + if !app.is_set(clap::AppSettings::Built) { + app._build(); + app._build_bin_names(); + } + + G::generate(app, buf) +} diff --git a/clap_generate/src/macros.rs b/clap_generate/src/macros.rs new file mode 100644 index 000000000000..748b258ed7ab --- /dev/null +++ b/clap_generate/src/macros.rs @@ -0,0 +1,87 @@ +macro_rules! w { + ($buf:expr, $to_w:expr) => { + match $buf.write_all($to_w) { + Ok(..) => (), + Err(..) => panic!("Failed to write to generated file"), + } + }; +} + +macro_rules! get_zsh_arg_conflicts { + ($app:expr, $arg:ident, $msg:ident) => { + if let Some(ref conf_vec) = $arg.blacklist { + let mut v = vec![]; + + for arg_name in conf_vec { + let arg = find!($app, 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() + } + }; +} + +#[cfg(feature = "debug")] +#[cfg_attr(feature = "debug", macro_use)] +#[cfg_attr(feature = "debug", allow(unused_macros))] +mod debug_macros { + macro_rules! debugln { + ($fmt:expr) => (println!(concat!("DEBUG:clap_generate:", $fmt))); + ($fmt:expr, $($arg:tt)*) => (println!(concat!("DEBUG:clap_generate:",$fmt), $($arg)*)); + } + macro_rules! sdebugln { + ($fmt:expr) => (println!($fmt)); + ($fmt:expr, $($arg:tt)*) => (println!($fmt, $($arg)*)); + } + macro_rules! debug { + ($fmt:expr) => (print!(concat!("DEBUG:clap_generate:", $fmt))); + ($fmt:expr, $($arg:tt)*) => (print!(concat!("DEBUG:clap_generate:",$fmt), $($arg)*)); + } + macro_rules! sdebug { + ($fmt:expr) => (print!($fmt)); + ($fmt:expr, $($arg:tt)*) => (print!($fmt, $($arg)*)); + } +} + +#[cfg(not(feature = "debug"))] +#[cfg_attr(not(feature = "debug"), macro_use)] +#[cfg_attr(not(feature = "debug"), allow(unused_macros))] +mod debug_macros { + macro_rules! debugln { + ($fmt:expr) => {}; + ($fmt:expr, $($arg:tt)*) => {}; + } + macro_rules! sdebugln { + ($fmt:expr) => {}; + ($fmt:expr, $($arg:tt)*) => {}; + } + macro_rules! debug { + ($fmt:expr) => {}; + ($fmt:expr, $($arg:tt)*) => {}; + } +} + +macro_rules! find { + ($app:expr, $name:expr, $what:ident) => { + $what!($app).find(|a| &a.name == $name) + }; + ($app:expr, $name:expr) => { + $app.args.args.iter().find(|a| { + if let Some(v) = a.index { + &v == $name + } else { + false + } + }) + }; +} diff --git a/clap_generate/tests/completions.rs b/clap_generate/tests/completions.rs new file mode 100644 index 000000000000..f1f22bbc1f71 --- /dev/null +++ b/clap_generate/tests/completions.rs @@ -0,0 +1,857 @@ +use clap::{App, Arg}; +use clap_generate::{generate, generators::*}; +use regex::Regex; + +static BASH: &'static str = r#"_myapp() { + 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 + myapp) + cmd="myapp" + ;; + + help) + cmd+="__help" + ;; + test) + cmd+="__test" + ;; + *) + ;; + esac + done + + case "${cmd}" in + myapp) + opts=" -h -V --help --version test help" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + + myapp__help) + opts=" -h -V --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + myapp__test) + opts=" -h -V --case --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + --case) + COMPREPLY=($(compgen -f ${cur})) + return 0 + ;; + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + esac +} + +complete -F _myapp -o bashdefault -o default myapp +"#; + +static ZSH: &'static str = r#"#compdef myapp + +autoload -U is-at-least + +_myapp() { + 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 + _arguments "${_arguments_options[@]}" \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +'::file -- some input file:_files' \ +":: :_myapp_commands" \ +"*::: :->myapp" \ +&& ret=0 + case $state in + (myapp) + words=($line[2] "${words[@]}") + (( CURRENT += 1 )) + curcontext="${curcontext%:*:*}:myapp-command-$line[2]:" + case $line[2] in + (test) +_arguments "${_arguments_options[@]}" \ +'--case=[the case to test]' \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +&& ret=0 +;; +(help) +_arguments "${_arguments_options[@]}" \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +&& ret=0 +;; + esac + ;; +esac +} + +(( $+functions[_myapp_commands] )) || +_myapp_commands() { + local commands; commands=( + "test:tests things" \ +"help:Prints this message or the help of the given subcommand(s)" \ + ) + _describe -t commands 'myapp commands' commands "$@" +} +(( $+functions[_myapp__help_commands] )) || +_myapp__help_commands() { + local commands; commands=( + + ) + _describe -t commands 'myapp help commands' commands "$@" +} +(( $+functions[_myapp__test_commands] )) || +_myapp__test_commands() { + local commands; commands=( + + ) + _describe -t commands 'myapp test commands' commands "$@" +} + +_myapp "$@""#; + +static FISH: &'static str = r#"complete -c myapp -n "__fish_use_subcommand" -s h -l help -d 'Prints help information' +complete -c myapp -n "__fish_use_subcommand" -s V -l version -d 'Prints version information' +complete -c myapp -n "__fish_use_subcommand" -f -a "test" -d 'tests things' +complete -c myapp -n "__fish_use_subcommand" -f -a "help" -d 'Prints this message or the help of the given subcommand(s)' +complete -c myapp -n "__fish_seen_subcommand_from test" -l case -d 'the case to test' +complete -c myapp -n "__fish_seen_subcommand_from test" -s h -l help -d 'Prints help information' +complete -c myapp -n "__fish_seen_subcommand_from test" -s V -l version -d 'Prints version information' +complete -c myapp -n "__fish_seen_subcommand_from help" -s h -l help -d 'Prints help information' +complete -c myapp -n "__fish_seen_subcommand_from help" -s V -l version -d 'Prints version information' +"#; + +static POWERSHELL: &'static str = r#" +using namespace System.Management.Automation +using namespace System.Management.Automation.Language + +Register-ArgumentCompleter -Native -CommandName 'my_app' -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + + $commandElements = $commandAst.CommandElements + $command = @( + 'my_app' + 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) { + 'my_app' { + [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information') + [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information') + [CompletionResult]::new('test', 'test', [CompletionResultType]::ParameterValue, 'tests things') + [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Prints this message or the help of the given subcommand(s)') + break + } + 'my_app;test' { + [CompletionResult]::new('--case', 'case', [CompletionResultType]::ParameterName, 'the case to test') + [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information') + [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information') + break + } + 'my_app;help' { + [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information') + [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information') + break + } + }) + + $completions.Where{ $_.CompletionText -like "$wordToComplete*" } | + Sort-Object -Property ListItemText +} +"#; + +static ELVISH: &'static str = r#" +edit:completion:arg-completer[my_app] = [@words]{ + fn spaces [n]{ + repeat $n ' ' | joins '' + } + fn cand [text desc]{ + edit:complex-candidate $text &display-suffix=' '(spaces (- 14 (wcswidth $text)))$desc + } + command = 'my_app' + for word $words[1:-1] { + if (has-prefix $word '-') { + break + } + command = $command';'$word + } + completions = [ + &'my_app'= { + cand -h 'Prints help information' + cand --help 'Prints help information' + cand -V 'Prints version information' + cand --version 'Prints version information' + cand test 'tests things' + cand help 'Prints this message or the help of the given subcommand(s)' + } + &'my_app;test'= { + cand --case 'the case to test' + cand -h 'Prints help information' + cand --help 'Prints help information' + cand -V 'Prints version information' + cand --version 'Prints version information' + } + &'my_app;help'= { + cand -h 'Prints help information' + cand --help 'Prints help information' + cand -V 'Prints version information' + cand --version 'Prints version information' + } + ] + $completions[$command] +} +"#; + +static ELVISH_SPECIAL_CMDS: &'static str = r#" +edit:completion:arg-completer[my_app] = [@words]{ + fn spaces [n]{ + repeat $n ' ' | joins '' + } + fn cand [text desc]{ + edit:complex-candidate $text &display-suffix=' '(spaces (- 14 (wcswidth $text)))$desc + } + command = 'my_app' + for word $words[1:-1] { + if (has-prefix $word '-') { + break + } + command = $command';'$word + } + completions = [ + &'my_app'= { + cand -h 'Prints help information' + cand --help 'Prints help information' + cand -V 'Prints version information' + cand --version 'Prints version information' + cand test 'tests things' + cand some_cmd 'tests other things' + cand some-cmd-with-hypens 'some-cmd-with-hypens' + cand help 'Prints this message or the help of the given subcommand(s)' + } + &'my_app;test'= { + cand --case 'the case to test' + cand -h 'Prints help information' + cand --help 'Prints help information' + cand -V 'Prints version information' + cand --version 'Prints version information' + } + &'my_app;some_cmd'= { + cand --config 'the other case to test' + cand -h 'Prints help information' + cand --help 'Prints help information' + cand -V 'Prints version information' + cand --version 'Prints version information' + } + &'my_app;some-cmd-with-hypens'= { + cand -h 'Prints help information' + cand --help 'Prints help information' + cand -V 'Prints version information' + cand --version 'Prints version information' + } + &'my_app;help'= { + cand -h 'Prints help information' + cand --help 'Prints help information' + cand -V 'Prints version information' + cand --version 'Prints version information' + } + ] + $completions[$command] +} +"#; + +static POWERSHELL_SPECIAL_CMDS: &'static str = r#" +using namespace System.Management.Automation +using namespace System.Management.Automation.Language + +Register-ArgumentCompleter -Native -CommandName 'my_app' -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + + $commandElements = $commandAst.CommandElements + $command = @( + 'my_app' + 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) { + 'my_app' { + [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information') + [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information') + [CompletionResult]::new('test', 'test', [CompletionResultType]::ParameterValue, 'tests things') + [CompletionResult]::new('some_cmd', 'some_cmd', [CompletionResultType]::ParameterValue, 'tests other things') + [CompletionResult]::new('some-cmd-with-hypens', 'some-cmd-with-hypens', [CompletionResultType]::ParameterValue, 'some-cmd-with-hypens') + [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Prints this message or the help of the given subcommand(s)') + break + } + 'my_app;test' { + [CompletionResult]::new('--case', 'case', [CompletionResultType]::ParameterName, 'the case to test') + [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information') + [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information') + break + } + 'my_app;some_cmd' { + [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'the other case to test') + [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information') + [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information') + break + } + 'my_app;some-cmd-with-hypens' { + [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information') + [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information') + break + } + 'my_app;help' { + [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') + [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information') + [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information') + break + } + }) + + $completions.Where{ $_.CompletionText -like "$wordToComplete*" } | + Sort-Object -Property ListItemText +} +"#; + +static ZSH_SPECIAL_CMDS: &'static str = r#"#compdef my_app + +autoload -U is-at-least + +_my_app() { + 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 + _arguments "${_arguments_options[@]}" \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +'::file -- some input file:_files' \ +":: :_my_app_commands" \ +"*::: :->my_app" \ +&& ret=0 + case $state in + (my_app) + words=($line[2] "${words[@]}") + (( CURRENT += 1 )) + curcontext="${curcontext%:*:*}:my_app-command-$line[2]:" + case $line[2] in + (test) +_arguments "${_arguments_options[@]}" \ +'--case=[the case to test]' \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +&& ret=0 +;; +(some_cmd) +_arguments "${_arguments_options[@]}" \ +'--config=[the other case to test]' \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +&& ret=0 +;; +(some-cmd-with-hypens) +_arguments "${_arguments_options[@]}" \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +&& ret=0 +;; +(help) +_arguments "${_arguments_options[@]}" \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +&& ret=0 +;; + esac + ;; +esac +} + +(( $+functions[_my_app_commands] )) || +_my_app_commands() { + local commands; commands=( + "test:tests things" \ +"some_cmd:tests other things" \ +"some-cmd-with-hypens:" \ +"help:Prints this message or the help of the given subcommand(s)" \ + ) + _describe -t commands 'my_app commands' commands "$@" +} +(( $+functions[_my_app__help_commands] )) || +_my_app__help_commands() { + local commands; commands=( + + ) + _describe -t commands 'my_app help commands' commands "$@" +} +(( $+functions[_my_app__some-cmd-with-hypens_commands] )) || +_my_app__some-cmd-with-hypens_commands() { + local commands; commands=( + + ) + _describe -t commands 'my_app some-cmd-with-hypens commands' commands "$@" +} +(( $+functions[_my_app__some_cmd_commands] )) || +_my_app__some_cmd_commands() { + local commands; commands=( + + ) + _describe -t commands 'my_app some_cmd commands' commands "$@" +} +(( $+functions[_my_app__test_commands] )) || +_my_app__test_commands() { + local commands; commands=( + + ) + _describe -t commands 'my_app test commands' commands "$@" +} + +_my_app "$@""#; + +static FISH_SPECIAL_CMDS: &'static str = r#"complete -c my_app -n "__fish_use_subcommand" -s h -l help -d 'Prints help information' +complete -c my_app -n "__fish_use_subcommand" -s V -l version -d 'Prints version information' +complete -c my_app -n "__fish_use_subcommand" -f -a "test" -d 'tests things' +complete -c my_app -n "__fish_use_subcommand" -f -a "some_cmd" -d 'tests other things' +complete -c my_app -n "__fish_use_subcommand" -f -a "some-cmd-with-hypens" +complete -c my_app -n "__fish_use_subcommand" -f -a "help" -d 'Prints this message or the help of the given subcommand(s)' +complete -c my_app -n "__fish_seen_subcommand_from test" -l case -d 'the case to test' +complete -c my_app -n "__fish_seen_subcommand_from test" -s h -l help -d 'Prints help information' +complete -c my_app -n "__fish_seen_subcommand_from test" -s V -l version -d 'Prints version information' +complete -c my_app -n "__fish_seen_subcommand_from some_cmd" -l config -d 'the other case to test' +complete -c my_app -n "__fish_seen_subcommand_from some_cmd" -s h -l help -d 'Prints help information' +complete -c my_app -n "__fish_seen_subcommand_from some_cmd" -s V -l version -d 'Prints version information' +complete -c my_app -n "__fish_seen_subcommand_from some-cmd-with-hypens" -s h -l help -d 'Prints help information' +complete -c my_app -n "__fish_seen_subcommand_from some-cmd-with-hypens" -s V -l version -d 'Prints version information' +complete -c my_app -n "__fish_seen_subcommand_from help" -s h -l help -d 'Prints help information' +complete -c my_app -n "__fish_seen_subcommand_from help" -s V -l version -d 'Prints version information' +"#; + +static BASH_SPECIAL_CMDS: &'static str = r#"_my_app() { + 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 + my_app) + cmd="my_app" + ;; + + help) + cmd+="__help" + ;; + some-cmd-with-hypens) + cmd+="__some__cmd__with__hypens" + ;; + some_cmd) + cmd+="__some_cmd" + ;; + test) + cmd+="__test" + ;; + *) + ;; + esac + done + + case "${cmd}" in + my_app) + opts=" -h -V --help --version test some_cmd some-cmd-with-hypens help" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + + my_app__help) + opts=" -h -V --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + my_app__some__cmd__with__hypens) + opts=" -h -V --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + my_app__some_cmd) + opts=" -h -V --config --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + --config) + COMPREPLY=($(compgen -f ${cur})) + return 0 + ;; + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + my_app__test) + opts=" -h -V --case --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + case "${prev}" in + + --case) + COMPREPLY=($(compgen -f ${cur})) + return 0 + ;; + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + esac +} + +complete -F _my_app -o bashdefault -o default my_app +"#; + +static FISH_SPECIAL_HELP: &'static str = r#"complete -c my_app -n "__fish_use_subcommand" -l single-quotes -d 'Can be \'always\', \'auto\', or \'never\'' +complete -c my_app -n "__fish_use_subcommand" -l double-quotes -d 'Can be "always", "auto", or "never"' +complete -c my_app -n "__fish_use_subcommand" -l backticks -d 'For more information see `echo test`' +complete -c my_app -n "__fish_use_subcommand" -l backslash -d 'Avoid \'\\n\'' +complete -c my_app -n "__fish_use_subcommand" -l brackets -d 'List packages [filter]' +complete -c my_app -n "__fish_use_subcommand" -l expansions -d 'Execute the shell command with $SHELL' +complete -c my_app -n "__fish_use_subcommand" -s h -l help -d 'Prints help information' +complete -c my_app -n "__fish_use_subcommand" -s V -l version -d 'Prints version information' +"#; + +static ZSH_SPECIAL_HELP: &'static str = r#"#compdef my_app + +autoload -U is-at-least + +_my_app() { + 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 + _arguments "${_arguments_options[@]}" \ +'--single-quotes[Can be '\''always'\'', '\''auto'\'', or '\''never'\'']' \ +'--double-quotes[Can be "always", "auto", or "never"]' \ +'--backticks[For more information see `echo test`]' \ +'--backslash[Avoid '\''\\n'\'']' \ +'--brackets[List packages \[filter\]]' \ +'--expansions[Execute the shell command with $SHELL]' \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +&& ret=0 + +} + +(( $+functions[_my_app_commands] )) || +_my_app_commands() { + local commands; commands=( + + ) + _describe -t commands 'my_app commands' commands "$@" +} + +_my_app "$@""#; + +fn compare(left: &str, right: &str) -> bool { + let b = left == right; + if !b { + let re = Regex::new(" ").unwrap(); + println!(""); + println!("--> left"); + println!("{}", re.replace_all(left, "\u{2022}")); + println!("--> right"); + println!("{}", re.replace_all(right, "\u{2022}")); + println!("--") + } + b +} + +fn build_app() -> App<'static> { + build_app_with_name("myapp") +} + +fn build_app_with_name(s: &'static str) -> App<'static> { + App::new(s) + .about("Tests completions") + .arg(Arg::with_name("file").help("some input file")) + .subcommand( + App::new("test").about("tests things").arg( + Arg::with_name("case") + .long("case") + .takes_value(true) + .help("the case to test"), + ), + ) +} + +fn build_app_special_commands() -> App<'static> { + build_app_with_name("my_app") + .subcommand( + App::new("some_cmd").about("tests other things").arg( + Arg::with_name("config") + .long("--config") + .takes_value(true) + .help("the other case to test"), + ), + ) + .subcommand(App::new("some-cmd-with-hypens")) +} + +fn build_app_special_help() -> App<'static> { + App::new("my_app") + .arg( + Arg::with_name("single-quotes") + .long("single-quotes") + .help("Can be 'always', 'auto', or 'never'"), + ) + .arg( + Arg::with_name("double-quotes") + .long("double-quotes") + .help("Can be \"always\", \"auto\", or \"never\""), + ) + .arg( + Arg::with_name("backticks") + .long("backticks") + .help("For more information see `echo test`"), + ) + .arg( + Arg::with_name("backslash") + .long("backslash") + .help("Avoid '\\n'"), + ) + .arg( + Arg::with_name("brackets") + .long("brackets") + .help("List packages [filter]"), + ) + .arg( + Arg::with_name("expansions") + .long("expansions") + .help("Execute the shell command with $SHELL"), + ) +} + +fn common(app: &mut App, fixture: &str) { + let mut buf = vec![]; + generate::(app, "myapp", &mut buf); + let string = String::from_utf8(buf).unwrap(); + + assert!(compare(&*string, fixture)); +} + +#[test] +fn bash() { + let mut app = build_app(); + common::(&mut app, BASH); +} + +#[test] +fn zsh() { + let mut app = build_app(); + common::(&mut app, ZSH); +} + +#[test] +fn fish() { + let mut app = build_app(); + common::(&mut app, FISH); +} + +#[test] +fn powershell() { + let mut app = build_app(); + common::(&mut app, POWERSHELL); +} + +#[test] +fn elvish() { + let mut app = build_app(); + common::(&mut app, ELVISH); +} + +#[test] +fn elvish_with_special_commands() { + let mut app = build_app_special_commands(); + common::(&mut app, ELVISH_SPECIAL_CMDS); +} + +#[test] +fn powershell_with_special_commands() { + let mut app = build_app_special_commands(); + common::(&mut app, POWERSHELL_SPECIAL_CMDS); +} + +#[test] +fn bash_with_special_commands() { + let mut app = build_app_special_commands(); + common::(&mut app, BASH_SPECIAL_CMDS); +} + +#[test] +fn fish_with_special_commands() { + let mut app = build_app_special_commands(); + common::(&mut app, FISH_SPECIAL_CMDS); +} + +#[test] +fn zsh_with_special_commands() { + let mut app = build_app_special_commands(); + common::(&mut app, ZSH_SPECIAL_CMDS); +} + +#[test] +fn fish_with_special_help() { + let mut app = build_app_special_help(); + common::(&mut app, FISH_SPECIAL_HELP); +} + +#[test] +fn zsh_with_special_help() { + let mut app = build_app_special_help(); + common::(&mut app, ZSH_SPECIAL_HELP); +} diff --git a/clap_generate/tests/version-numbers.rs b/clap_generate/tests/version-numbers.rs new file mode 100644 index 000000000000..1b8189b7e2af --- /dev/null +++ b/clap_generate/tests/version-numbers.rs @@ -0,0 +1,6 @@ +use version_sync::assert_html_root_url_updated; + +#[test] +fn test_html_root_url() { + assert_html_root_url_updated!("src/lib.rs"); +} diff --git a/src/lib.rs b/src/lib.rs index 300c292e1770..2286a22d60e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -475,8 +475,11 @@ mod output; mod parse; mod util; +#[doc(hidden)] +pub use mkeymap::KeyType; + const INTERNAL_ERROR_MSG: &str = "Fatal internal error. Please consider filing a bug \ - report at https://github.com/kbknapp/clap-rs/issues"; + report at https://github.com/clap-rs/clap/issues"; const INVALID_UTF8: &str = "unexpected invalid UTF-8 code point"; /// @TODO @release @docs diff --git a/src/macros.rs b/src/macros.rs index 7a1c735b6720..847f496c34ff 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -919,18 +919,18 @@ macro_rules! write_nspaces { }}; } +#[macro_export] +#[doc(hidden)] macro_rules! flags { ($app:expr, $how:ident) => {{ $app.args .args .$how() - .filter(|a| { - !a.settings.is_set(crate::build::ArgSettings::TakesValue) && a.index.is_none() - }) + .filter(|a| !a.settings.is_set($crate::ArgSettings::TakesValue) && a.index.is_none()) .filter(|a| !a.help_heading.is_some()) }}; ($app:expr) => { - flags!($app, iter) + $crate::flags!($app, iter) }; } @@ -941,14 +941,14 @@ macro_rules! flags_mut { }; } +#[macro_export] +#[doc(hidden)] macro_rules! opts { ($app:expr, $how:ident) => {{ $app.args .args .$how() - .filter(|a| { - a.settings.is_set(crate::build::ArgSettings::TakesValue) && a.index.is_none() - }) + .filter(|a| a.settings.is_set($crate::ArgSettings::TakesValue) && a.index.is_none()) .filter(|a| !a.help_heading.is_some()) }}; ($app:expr) => { @@ -963,6 +963,8 @@ macro_rules! opts_mut { }; } +#[macro_export] +#[doc(hidden)] macro_rules! positionals { ($app:expr) => { $app.args @@ -997,6 +999,8 @@ macro_rules! subcommands_cloned { }; } +#[macro_export] +#[doc(hidden)] macro_rules! subcommands { ($app:expr, $how:ident) => { $app.subcommands.$how() @@ -1028,15 +1032,34 @@ macro_rules! find_subcmd_cloned { }}; } +#[macro_export] +#[doc(hidden)] macro_rules! find_subcmd { ($app:expr, $sc:expr) => {{ subcommands!($app).find(|a| match_alias!(a, $sc, &*a.name)) }}; } +#[macro_export] +#[doc(hidden)] +macro_rules! shorts { + ($app:expr) => {{ + use $crate::KeyType; + $app.args.keys.iter().map(|x| &x.key).filter_map(|a| { + if let KeyType::Short(v) = a { + Some(v) + } else { + None + } + }) + }}; +} + +#[macro_export] +#[doc(hidden)] macro_rules! longs { ($app:expr) => {{ - use crate::mkeymap::KeyType; + use $crate::KeyType; $app.args.keys.iter().map(|x| &x.key).filter_map(|a| { if let KeyType::Long(v) = a { Some(v) @@ -1047,7 +1070,9 @@ macro_rules! longs { }}; } -macro_rules! _names { +#[macro_export] +#[doc(hidden)] +macro_rules! names { (@args $app:expr) => {{ $app.args.args.iter().map(|a| &*a.name) }}; @@ -1061,12 +1086,16 @@ macro_rules! _names { }}; } +#[macro_export] +#[doc(hidden)] macro_rules! sc_names { ($app:expr) => {{ - _names!(@sc $app) + names!(@sc $app) }}; } +#[macro_export] +#[doc(hidden)] macro_rules! match_alias { ($a:expr, $to:expr, $what:expr) => {{ $what == $to diff --git a/src/mkeymap.rs b/src/mkeymap.rs index f17cd2256a1e..d1e4097b8172 100644 --- a/src/mkeymap.rs +++ b/src/mkeymap.rs @@ -16,6 +16,7 @@ pub struct MKeyMap<'b> { built: bool, // mutation isn't possible after being built } +#[doc(hidden)] #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum KeyType { Short(char),