Refactor clap_generate

This commit is contained in:
Pavan Kumar Sunkara 2020-02-05 11:04:59 +01:00
parent 7acc9225c6
commit 33f47acc67
19 changed files with 2603 additions and 17 deletions

View file

@ -84,7 +84,10 @@ features = ["doc"]
[workspace]
members = [
"clap_derive",
"clap_generate",
]
default-members = [
".", "clap_derive",
".",
"clap_derive",
"clap_generate",
]

View file

@ -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 = []

View file

@ -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.

50
clap_generate/Cargo.toml Normal file
View file

@ -0,0 +1,50 @@
[package]
name = "clap_generate"
version = "3.0.0-beta.1"
edition = "2018"
authors = [
"Kevin K. <kbknapp@gmail.com>",
"Pavan Kumar Sunkara <pavan.sss1991@gmail.com>",
]
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]
pretty_assertions = "0.6"
version-sync = "0.8"
[features]
default = []
unstable = ["clap/unstable"]
nightly = ["clap/nightly"]
debug = ["clap/debug"]
doc = []
[package.metadata.docs.rs]
features = ["doc"]

1
clap_generate/README.md Normal file
View file

@ -0,0 +1 @@
# clap_generate

View file

@ -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 dyn Write) {}
/// 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, fmt::write};
/// use clap::App;
/// use clap_generate::Generator;
///
/// pub struct ClapDebug;
///
/// impl Generator for ClapDebug {
/// fn generate(app: &App, buf: &mut dyn Write) {
/// write!(buf, "{}", app).unwrap();
/// }
/// # fn file_name(name: &str) -> String {
/// # name.into()
/// # }
/// }
/// ```
fn generate(app: &App, buf: &mut dyn Write);
/// 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<String> {
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<String> {
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
}
}

View file

@ -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 dyn Write) {
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
}

View file

@ -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 dyn Write) {
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<T: ToString>(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
}

View file

@ -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 dyn Write) {
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);
}
}

View file

@ -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;

View file

@ -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 dyn Write) {
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<T: ToString>(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
}

View file

@ -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 dyn Write) {
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<String>) {
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::<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: &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::<Vec<String>>()
.join(" ")
)
})
);
debugln!("Zsh::write_positionals_of:iter: Wrote...{}", a);
ret.push(a);
}
ret.join("\n")
}

188
clap_generate/src/lib.rs Normal file
View file

@ -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<Bash>(&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
/// `<project>/target/debug/build/myapp-<hash>/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<G, S, T>(app: &mut clap::App, bin_name: S, out_dir: T)
where
G: Generator,
S: Into<String>,
T: Into<OsString>,
{
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::<G, S>(app, bin_name, &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<Bash>(&mut cli::build_cli(), "myapp", &mut io::stdout());
/// }
///
/// // normal logic continues...
/// }
///
/// ```
///
/// Usage:
///
/// ```shell
/// $ myapp generate-bash-completions > /usr/share/bash-completion/completions/myapp.bash
/// ```
pub fn generate<G, S>(app: &mut clap::App, bin_name: S, buf: &mut dyn Write)
where
G: Generator,
S: Into<String>,
{
app.bin_name = Some(bin_name.into());
if !app.is_set(clap::AppSettings::Built) {
app._build();
app._build_bin_names();
}
G::generate(app, buf)
}

View file

@ -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
}
})
};
}

View file

@ -0,0 +1,858 @@
use std::fmt;
use clap::{App, Arg};
use clap_generate::{generate, generators::*};
#[derive(PartialEq, Eq)]
pub struct PrettyString<'a>(pub &'a str);
impl<'a> fmt::Debug for PrettyString<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.0)
}
}
macro_rules! assert_eq {
($left:expr, $right:expr) => {
pretty_assertions::assert_eq!(PrettyString($left), PrettyString($right));
}
}
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 <file> 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 <file> 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 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<G: Generator>(app: &mut App, name: &str, fixture: &str) {
let mut buf = vec![];
generate::<G, _>(app, name, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert_eq!(&string, fixture);
}
#[test]
fn bash() {
let mut app = build_app();
common::<Bash>(&mut app, "myapp", BASH);
}
#[test]
fn zsh() {
let mut app = build_app();
common::<Zsh>(&mut app, "myapp", ZSH);
}
#[test]
fn fish() {
let mut app = build_app();
common::<Fish>(&mut app, "myapp", FISH);
}
#[test]
fn powershell() {
let mut app = build_app();
common::<PowerShell>(&mut app, "my_app", POWERSHELL);
}
#[test]
fn elvish() {
let mut app = build_app();
common::<Elvish>(&mut app, "my_app", ELVISH);
}
#[test]
fn elvish_with_special_commands() {
let mut app = build_app_special_commands();
common::<Elvish>(&mut app, "my_app", ELVISH_SPECIAL_CMDS);
}
#[test]
fn powershell_with_special_commands() {
let mut app = build_app_special_commands();
common::<PowerShell>(&mut app, "my_app", POWERSHELL_SPECIAL_CMDS);
}
#[test]
fn bash_with_special_commands() {
let mut app = build_app_special_commands();
common::<Bash>(&mut app, "my_app", BASH_SPECIAL_CMDS);
}
#[test]
fn fish_with_special_commands() {
let mut app = build_app_special_commands();
common::<Fish>(&mut app, "my_app", FISH_SPECIAL_CMDS);
}
#[test]
fn zsh_with_special_commands() {
let mut app = build_app_special_commands();
common::<Zsh>(&mut app, "my_app", ZSH_SPECIAL_CMDS);
}
#[test]
fn fish_with_special_help() {
let mut app = build_app_special_help();
common::<Fish>(&mut app, "my_app", FISH_SPECIAL_HELP);
}
#[test]
fn zsh_with_special_help() {
let mut app = build_app_special_help();
common::<Zsh>(&mut app, "my_app", ZSH_SPECIAL_HELP);
}

View file

@ -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");
}

View file

@ -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

View file

@ -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,6 +1032,8 @@ 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))
@ -1047,7 +1053,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 +1069,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

View file

@ -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),