perf: refactor to remove unneeded vectors and allocations and checks for significant performance increases

Building an `App` struct with a fair number of args/flags/switches, etc. (used ripgrep as test case)
went from taking ~21,000 ns to ~13,000ns.
This commit is contained in:
Kevin K 2017-02-20 23:51:20 -05:00
parent b55cae5fdb
commit 0efa411963
No known key found for this signature in database
GPG key ID: 17218E4B3692F01A
14 changed files with 971 additions and 872 deletions

View file

@ -9,11 +9,12 @@ mod test {
fn compare<S, S2>(l: S, r: S2) -> bool
where S: AsRef<str>,
S2: AsRef<str> {
S2: AsRef<str>
{
let re = Regex::new("\x1b[^m]*m").unwrap();
// Strip out any mismatching \r character on windows that might sneak in on either side
let left = re.replace_all(&l.as_ref().trim().replace("\r", "")[..], "").into_owned();
let right = re.replace_all(&r.as_ref().trim().replace("\r", "")[..], "").into_owned();
let left = re.replace_all(&l.as_ref().trim().replace("\r", "")[..], "");
let right = re.replace_all(&r.as_ref().trim().replace("\r", "")[..], "");
let b = left == right;
if !b {
println!("");

View file

@ -19,11 +19,14 @@ macro_rules! remove_overriden {
};
($_self:ident, $name:expr) => {
debugln!("remove_overriden!;");
if let Some(ref o) = $_self.opts.iter().filter(|o| o.b.name == *$name).next() {
if let Some(o) = $_self.opts.iter() .find(|o| o.b.name == *$name) {
remove_overriden!(@arg $_self, o);
} else if let Some(ref f) = $_self.flags.iter().filter(|f| f.b.name == *$name).next() {
} else if let Some(f) = $_self.flags.iter() .find(|f| f.b.name == *$name) {
remove_overriden!(@arg $_self, f);
} else if let Some(p) = $_self.positionals.values().filter(|p| p.b.name == *$name).next() {
} else {
let p = $_self.positionals.values()
.find(|p| p.b.name == *$name)
.expect(INTERNAL_ERROR_MSG);
remove_overriden!(@arg $_self, p);
}
};
@ -69,21 +72,20 @@ macro_rules! arg_post_processing {
}
}
$me.blacklist.extend(bl);
$me.blacklist.extend_from_slice(bl);
vec_remove_all!($me.overrides, bl.iter());
vec_remove_all!($me.required, bl.iter());
// vec_remove_all!($me.required, bl.iter());
} else { sdebugln!("No"); }
// Add all required args which aren't already found in matcher to the master
// list
debug!("arg_post_processing!: Does '{}' have requirements...", $arg.to_string());
if let Some(reqs) = $arg.requires() {
for n in reqs.iter().filter(|&&(val, _)| val.is_none()).map(|&(_, name)| name) {
if $matcher.contains(&n) {
sdebugln!("\tYes '{}' but it's already met", n);
continue;
} else { sdebugln!("\tYes '{}'", n); }
for n in reqs.iter()
.filter(|&&(val, _)| val.is_none())
.filter(|&&(_, req)| !$matcher.contains(&req))
.map(|&(_, name)| name) {
$me.required.push(n);
}
} else { sdebugln!("No"); }
@ -96,9 +98,9 @@ macro_rules! _handle_group_reqs{
($me:ident, $arg:ident) => ({
use args::AnyArg;
debugln!("_handle_group_reqs!;");
for grp in $me.groups.values() {
for grp in $me.groups.iter() {
let found = if grp.args.contains(&$arg.name()) {
vec_remove!($me.required, &$arg.name());
// vec_remove!($me.required, &$arg.name());
if let Some(ref reqs) = grp.requires {
debugln!("_handle_group_reqs!: Adding {:?} to the required list", reqs);
$me.required.extend(reqs);
@ -126,18 +128,6 @@ macro_rules! _handle_group_reqs{
})
}
macro_rules! validate_multiples {
($_self:ident, $a:ident, $m:ident) => {
debugln!("validate_multiples!;");
if $m.contains(&$a.b.name) && !$a.b.is_set(ArgSettings::Multiple) {
// Not the first time, and we don't allow multiples
return Err(Error::unexpected_multiple_usage($a,
&*$_self.create_current_usage($m, None),
$_self.color()))
}
};
}
macro_rules! parse_positional {
(
$_self:ident,
@ -147,12 +137,11 @@ macro_rules! parse_positional {
$matcher:ident
) => {
debugln!("parse_positional!;");
validate_multiples!($_self, $p, $matcher);
if !$_self.is_set(TrailingValues) &&
($_self.is_set(TrailingVarArg) &&
if !$_self.is_set(AS::TrailingValues) &&
($_self.is_set(AS::TrailingVarArg) &&
$pos_counter == $_self.positionals.len()) {
$_self.settings.set(TrailingValues);
$_self.settings.set(AS::TrailingValues);
}
let _ = try!($_self.add_val_to_arg($p, &$arg_os, $matcher));
@ -166,89 +155,3 @@ macro_rules! parse_positional {
}
};
}
macro_rules! find_from {
($_self:ident, $arg_name:expr, $from:ident, $matcher:expr) => {{
let mut ret = None;
for k in $matcher.arg_names() {
if let Some(f) = find_by_name!($_self, &k, flags, iter) {
if let Some(ref v) = f.$from() {
if v.contains($arg_name) {
ret = Some(f.to_string());
}
}
}
if let Some(o) = find_by_name!($_self, &k, opts, iter) {
if let Some(ref v) = o.$from() {
if v.contains(&$arg_name) {
ret = Some(o.to_string());
}
}
}
if let Some(pos) = find_by_name!($_self, &k, positionals, values) {
if let Some(ref v) = pos.$from() {
if v.contains($arg_name) {
ret = Some(pos.b.name.to_owned());
}
}
}
}
ret
}};
}
macro_rules! find_name_from {
($_self:ident, $arg_name:expr, $from:ident, $matcher:expr) => {{
let mut ret = None;
for k in $matcher.arg_names() {
if let Some(f) = find_by_name!($_self, &k, flags, iter) {
if let Some(ref v) = f.$from() {
if v.contains($arg_name) {
ret = Some(f.b.name);
}
}
}
if let Some(o) = find_by_name!($_self, &k, opts, iter) {
if let Some(ref v) = o.$from() {
if v.contains(&$arg_name) {
ret = Some(o.b.name);
}
}
}
if let Some(pos) = find_by_name!($_self, &k, positionals, values) {
if let Some(ref v) = pos.$from() {
if v.contains($arg_name) {
ret = Some(pos.b.name);
}
}
}
}
ret
}};
}
// Finds an arg by name
macro_rules! find_by_name {
($_self:ident, $name:expr, $what:ident, $how:ident) => {
$_self.$what.$how().find(|o| &o.b.name == $name)
}
}
// Finds an option including if it's aliasesed
macro_rules! find_by_long {
($_self:ident, $long:expr, $what:ident) => {
$_self.$what
.iter()
.filter(|o| o.s.long.is_some())
.find(|o| {
&&o.s.long.unwrap() == &$long ||
(o.s.aliases.is_some() &&
o.s
.aliases
.as_ref()
.unwrap()
.iter()
.any(|&(alias, _)| &&alias == &$long))
})
}
}

File diff suppressed because it is too large Load diff

View file

@ -1335,7 +1335,7 @@ impl<'a, 'b> Arg<'a, 'b> {
/// .long("other")
/// .takes_value(true))
/// .get_matches_from_safe(vec![
/// "prog", "--other", "not-special"
/// "prog", "--other", "not-special"
/// ]);
///
/// assert!(res.is_ok()); // We didn't use --other=special, so "cfg" wasn't required
@ -1355,7 +1355,7 @@ impl<'a, 'b> Arg<'a, 'b> {
/// .long("other")
/// .takes_value(true))
/// .get_matches_from_safe(vec![
/// "prog", "--other", "special"
/// "prog", "--other", "special"
/// ]);
///
/// assert!(res.is_err());
@ -1721,7 +1721,7 @@ impl<'a, 'b> Arg<'a, 'b> {
/// .short("v"))
/// .get_matches_from(vec![
/// "prog", "-v", "-v", "-v" // note, -vvv would have same result
/// ]);
/// ]);
///
/// assert!(m.is_present("verbose"));
/// assert_eq!(m.occurrences_of("verbose"), 3);
@ -1852,7 +1852,7 @@ impl<'a, 'b> Arg<'a, 'b> {
/// [`number_of_values`]).
///
/// **NOTE:** This setting only applies to [options] and [positional arguments]
///
///
/// **NOTE:** When the terminator is passed in on the command line, it is **not** stored as one
/// of the vaues
///
@ -2965,12 +2965,10 @@ impl<'a, 'b> Arg<'a, 'b> {
/// ```
/// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value
/// [`Arg::default_value`]: ./struct.Arg.html#method.default_value
pub fn default_value_if(self,
arg: &'a str,
val: Option<&'b str>,
default: &'b str)
-> Self {
self.default_value_if_os(arg, val.map(str::as_bytes).map(OsStr::from_bytes), OsStr::from_bytes(default.as_bytes()))
pub fn default_value_if(self, arg: &'a str, val: Option<&'b str>, default: &'b str) -> Self {
self.default_value_if_os(arg,
val.map(str::as_bytes).map(OsStr::from_bytes),
OsStr::from_bytes(default.as_bytes()))
}
/// Provides a conditional default value in the exact same manner as [`Arg::default_value_if`]
@ -2978,10 +2976,10 @@ impl<'a, 'b> Arg<'a, 'b> {
/// [`Arg::default_value_if`]: ./struct.Arg.html#method.default_value_if
/// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html
pub fn default_value_if_os(mut self,
arg: &'a str,
val: Option<&'b OsStr>,
default: &'b OsStr)
-> Self {
arg: &'a str,
val: Option<&'b OsStr>,
default: &'b OsStr)
-> Self {
self.setb(ArgSettings::TakesValue);
if let Some(ref mut vm) = self.v.default_vals_ifs {
let l = vm.len();
@ -3080,12 +3078,14 @@ impl<'a, 'b> Arg<'a, 'b> {
/// [`Arg::default_value`]: ./struct.Arg.html#method.default_value
pub fn default_value_ifs(mut self, ifs: &[(&'a str, Option<&'b str>, &'b str)]) -> Self {
for &(arg, val, default) in ifs {
self = self.default_value_if_os(arg, val.map(str::as_bytes).map(OsStr::from_bytes), OsStr::from_bytes(default.as_bytes()));
self = self.default_value_if_os(arg,
val.map(str::as_bytes).map(OsStr::from_bytes),
OsStr::from_bytes(default.as_bytes()));
}
self
}
}
/// Provides multiple conditional default values in the exact same manner as
/// Provides multiple conditional default values in the exact same manner as
/// [`Arg::default_value_ifs`] only using [`OsStr`]s instead.
/// [`Arg::default_value_ifs`]: ./struct.Arg.html#method.default_value_ifs
/// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html

View file

@ -22,9 +22,7 @@ impl<'e> Default for Switched<'e> {
}
impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Switched<'e> {
fn from(a: &'z Arg<'n, 'e>) -> Self {
a.s.clone()
}
fn from(a: &'z Arg<'n, 'e>) -> Self { a.s.clone() }
}
impl<'e> Clone for Switched<'e> {

View file

@ -40,7 +40,7 @@ impl<'a> ArgMatcher<'a> {
gma.vals.insert(i, v.clone());
}
gma
});
});
if sma.vals.is_empty() {
for (i, v) in &vals {
sma.vals.insert(i, v.clone());

View file

@ -125,21 +125,7 @@ complete -F _{name} -o bashdefault -o default {name}
let mut p = self.p;
for sc in path.split("__").skip(1) {
debugln!("BashGen::option_details_for_path:iter: sc={}", sc);
p = &p.subcommands
.iter()
.find(|s| {
s.p.meta.name == sc ||
(s.p.meta.aliases.is_some() &&
s.p
.meta
.aliases
.as_ref()
.unwrap()
.iter()
.any(|&(n, _)| n == sc))
})
.unwrap()
.p;
p = &find_subcmd!(p, sc).unwrap().p;
}
let mut opts = String::new();
for o in p.opts() {
@ -214,28 +200,12 @@ complete -F _{name} -o bashdefault -o default {name}
let mut p = self.p;
for sc in path.split("__").skip(1) {
debugln!("BashGen::all_options_for_path:iter: sc={}", sc);
p = &p.subcommands
.iter()
.find(|s| {
s.p.meta.name == sc ||
(s.p.meta.aliases.is_some() &&
s.p
.meta
.aliases
.as_ref()
.unwrap()
.iter()
.any(|&(n, _)| n == sc))
})
.unwrap()
.p;
p = &find_subcmd!(p, sc).unwrap().p;
}
let mut opts = p.short_list.iter().fold(String::new(), |acc, s| format!("{} -{}", acc, s));
let mut opts = shorts!(p).fold(String::new(), |acc, s| format!("{} -{}", acc, s));
opts = format!("{} {}",
opts,
p.long_list
.iter()
.fold(String::new(), |acc, l| format!("{} --{}", acc, l)));
longs!(p).fold(String::new(), |acc, l| format!("{} --{}", acc, l)));
opts = format!("{} {}",
opts,
p.positionals

View file

@ -19,10 +19,7 @@ impl<'a, 'b> PowerShellGen<'a, 'b> {
let (subcommands_detection_cases, subcommands_cases) = generate_inner(self.p, "");
let mut bin_names = vec![
bin_name.to_string(),
format!("./{0}", bin_name),
];
let mut bin_names = vec![bin_name.to_string(), format!("./{0}", bin_name)];
if cfg!(windows) {
bin_names.push(format!("{0}.exe", bin_name));
bin_names.push(format!(r".\{0}", bin_name));
@ -92,10 +89,10 @@ fn generate_inner<'a, 'b>(p: &Parser<'a, 'b>, previous_command_name: &str) -> (S
for subcommand in &p.subcommands {
completions.push_str(&format!("'{}', ", &subcommand.p.meta.name));
}
for short in &p.short_list {
for short in shorts!(p) {
completions.push_str(&format!("'-{}', ", short));
}
for long in &p.long_list {
for long in longs!(p) {
completions.push_str(&format!("'--{}', ", long));
}

View file

@ -498,11 +498,11 @@ macro_rules! crate_name {
/// Provided separator is for the [`crate_authors!`](macro.crate_authors.html) macro,
/// refer to the documentation therefor.
///
/// **NOTE:** Changing the values in your `Cargo.toml` does not trigger a re-build automatically,
/// **NOTE:** Changing the values in your `Cargo.toml` does not trigger a re-build automatically,
/// and therefore won't change the generated output until you recompile.
///
/// **Pro Tip:** In some cases you can "trick" the compiler into triggering a rebuild when your
/// `Cargo.toml` is changed by including this in your `src/main.rs` file
/// `Cargo.toml` is changed by including this in your `src/main.rs` file
/// `include_str!("../Cargo.toml");`
///
/// # Examples
@ -542,47 +542,47 @@ macro_rules! app_from_crate {
/// # #[macro_use]
/// # extern crate clap;
/// # fn main() {
/// let matches = clap_app!(myapp =>
/// (version: "1.0")
/// (author: "Kevin K. <kbknapp@gmail.com>")
/// (about: "Does awesome things")
/// let matches = clap_app!(myapp =>
/// (version: "1.0")
/// (author: "Kevin K. <kbknapp@gmail.com>")
/// (about: "Does awesome things")
/// (@arg CONFIG: -c --config +takes_value "Sets a custom config file")
/// (@arg INPUT: +required "Sets the input file to use")
/// (@arg debug: -d ... "Sets the level of debugging information")
/// (@subcommand test =>
/// (about: "controls testing features")
/// (version: "1.3")
/// (author: "Someone E. <someone_else@other.com>")
/// (@arg INPUT: +required "Sets the input file to use")
/// (@arg debug: -d ... "Sets the level of debugging information")
/// (@subcommand test =>
/// (about: "controls testing features")
/// (version: "1.3")
/// (author: "Someone E. <someone_else@other.com>")
/// (@arg verbose: -v --verbose "Print test information verbosely")
/// )
/// )
/// );
/// # }
/// # }
/// ```
/// # Shorthand Syntax for Args
///
///
/// * A single hyphen followed by a character (such as `-c`) sets the [`Arg::short`]
/// * A double hyphen followed by a character or word (such as `--config`) sets [`Arg::long`]
/// * Three dots (`...`) sets [`Arg::multiple(true)`]
/// * Angled brackets after either a short or long will set [`Arg::value_name`] and
/// `Arg::required(true)` such as `--config <FILE>` = `Arg::value_name("FILE")` and
/// * Angled brackets after either a short or long will set [`Arg::value_name`] and
/// `Arg::required(true)` such as `--config <FILE>` = `Arg::value_name("FILE")` and
/// `Arg::required(true)
/// * Square brackets after either a short or long will set [`Arg::value_name`] and
/// `Arg::required(false)` such as `--config [FILE]` = `Arg::value_name("FILE")` and
/// * Square brackets after either a short or long will set [`Arg::value_name`] and
/// `Arg::required(false)` such as `--config [FILE]` = `Arg::value_name("FILE")` and
/// `Arg::required(false)
/// * There are short hand syntaxes for Arg methods that accept booleans
/// * There are short hand syntaxes for Arg methods that accept booleans
/// * A plus sign will set that method to `true` such as `+required` = `Arg::required(true)`
/// * An exclamation will set that method to `false` such as `!required` = `Arg::required(false)`
/// * A `#{min, max}` will set [`Arg::min_values(min)`] and [`Arg::max_values(max)`]
/// * An asterisk (`*`) will set `Arg::required(true)`
/// * Curly brackets around a `fn` will set [`Arg::validator`] as in `{fn}` = `Arg::validator(fn)`
/// * An Arg method that accepts a string followed by square brackets will set that method such as
/// `conflicts_with[FOO]` will set `Arg::conflicts_with("FOO")` (note the lack of quotes around
/// `FOO` in the macro)
/// * An Arg method that takes a string and can be set multiple times (such as
/// [`Arg::conflicts_with`]) followed by square brackets and a list of values separated by spaces
/// will set that method such as `conflicts_with[FOO BAR BAZ]` will set
/// * An Arg method that accepts a string followed by square brackets will set that method such as
/// `conflicts_with[FOO]` will set `Arg::conflicts_with("FOO")` (note the lack of quotes around
/// `FOO` in the macro)
/// * An Arg method that takes a string and can be set multiple times (such as
/// [`Arg::conflicts_with`]) followed by square brackets and a list of values separated by spaces
/// will set that method such as `conflicts_with[FOO BAR BAZ]` will set
/// `Arg::conflicts_with("FOO")`, `Arg::conflicts_with("BAR")`, and `Arg::conflicts_with("BAZ")`
/// (note the lack of quotes around the values in the macro)
/// (note the lack of quotes around the values in the macro)
///
/// [`Arg::short`]: ./struct.Arg.html#method.short
/// [`Arg::long`]: ./struct.Arg.html#method.long
@ -846,3 +846,201 @@ macro_rules! vec_remove_all {
}
};
}
macro_rules! find_from {
($_self:ident, $arg_name:expr, $from:ident, $matcher:expr) => {{
let mut ret = None;
for k in $matcher.arg_names() {
if let Some(f) = find_by_name!($_self, &k, flags, iter) {
if let Some(ref v) = f.$from() {
if v.contains($arg_name) {
ret = Some(f.to_string());
}
}
}
if let Some(o) = find_by_name!($_self, &k, opts, iter) {
if let Some(ref v) = o.$from() {
if v.contains(&$arg_name) {
ret = Some(o.to_string());
}
}
}
if let Some(pos) = find_by_name!($_self, &k, positionals, values) {
if let Some(ref v) = pos.$from() {
if v.contains($arg_name) {
ret = Some(pos.b.name.to_owned());
}
}
}
}
ret
}};
}
macro_rules! find_name_from {
($_self:ident, $arg_name:expr, $from:ident, $matcher:expr) => {{
let mut ret = None;
for k in $matcher.arg_names() {
if let Some(f) = find_by_name!($_self, &k, flags, iter) {
if let Some(ref v) = f.$from() {
if v.contains($arg_name) {
ret = Some(f.b.name);
}
}
}
if let Some(o) = find_by_name!($_self, &k, opts, iter) {
if let Some(ref v) = o.$from() {
if v.contains(&$arg_name) {
ret = Some(o.b.name);
}
}
}
if let Some(pos) = find_by_name!($_self, &k, positionals, values) {
if let Some(ref v) = pos.$from() {
if v.contains($arg_name) {
ret = Some(pos.b.name);
}
}
}
}
ret
}};
}
// Finds an arg by name
macro_rules! find_by_name {
($_self:ident, $name:expr, $what:ident, $how:ident) => {
$_self.$what.$how().find(|o| &o.b.name == $name)
}
}
// Finds an option including if it's aliasesed
macro_rules! find_opt_by_long {
(@os $_self:ident, $long:expr) => {{
_find_by_long!($_self, $long, opts)
}};
($_self:ident, $long:expr) => {{
_find_by_long!($_self, $long, opts)
}};
}
macro_rules! find_flag_by_long {
(@os $_self:ident, $long:expr) => {{
_find_by_long!($_self, $long, flags)
}};
($_self:ident, $long:expr) => {{
_find_by_long!($_self, $long, flags)
}};
}
macro_rules! find_any_by_long {
($_self:ident, $long:expr, $what:ident) => {
_find_flag_by_long!($_self, $long).or(_find_opt_by_long!($_self, $long))
}
}
macro_rules! _find_by_long {
($_self:ident, $long:expr, $what:ident) => {{
$_self.$what
.iter()
.filter(|a| a.s.long.is_some())
.find(|a| {
&&a.s.long.unwrap() == &$long ||
(a.s.aliases.is_some() &&
a.s
.aliases
.as_ref()
.unwrap()
.iter()
.any(|&(alias, _)| &&alias == &$long))
})
}}
}
// Finds an option
macro_rules! find_opt_by_short {
($_self:ident, $short:expr) => {{
_find_by_short!($_self, $short, opts)
}}
}
macro_rules! find_flag_by_short {
($_self:ident, $short:expr) => {{
_find_by_short!($_self, $short, flags)
}}
}
macro_rules! find_any_by_short {
($_self:ident, $short:expr, $what:ident) => {
_find_flag_by_short!($_self, $short).or(_find_opt_by_short!($_self, $short))
}
}
macro_rules! _find_by_short {
($_self:ident, $short:expr, $what:ident) => {{
$_self.$what
.iter()
.filter(|a| a.s.short.is_some())
.find(|a| a.s.short.unwrap() == $short)
}}
}
macro_rules! find_subcmd {
($_self:expr, $sc:expr) => {{
$_self.subcommands
.iter()
.find(|s| {
s.p.meta.name == $sc ||
(s.p.meta.aliases.is_some() &&
s.p
.meta
.aliases
.as_ref()
.unwrap()
.iter()
.any(|&(n, _)| n == $sc))
})
}};
}
macro_rules! shorts {
($_self:ident) => {{
_shorts_longs!($_self, short)
}};
}
macro_rules! longs {
($_self:ident) => {{
_shorts_longs!($_self, long)
}};
}
macro_rules! _shorts_longs {
($_self:ident, $what:ident) => {{
$_self.flags
.iter()
.filter(|f| f.s.$what.is_some())
.map(|f| f.s.$what.as_ref().unwrap())
.chain($_self.opts.iter()
.filter(|o| o.s.$what.is_some())
.map(|o| o.s.$what.as_ref().unwrap()))
}};
}
macro_rules! arg_names {
($_self:ident) => {{
_names!($_self)
}};
}
macro_rules! _names {
($_self:ident) => {{
$_self.flags
.iter()
.map(|f| &*f.b.name)
.chain($_self.opts.iter()
.map(|o| &*o.b.name)
.chain($_self.positionals.values()
.map(|p| &*p.b.name)))
}};
}

View file

@ -1,9 +1,9 @@
extern crate regex;
extern crate clap;
use clap::{App, Arg, SubCommand, Shell};
use regex::Regex;
extern crate regex;
extern crate clap;
use clap::{App, Arg, SubCommand, Shell};
use regex::Regex;
static BASH: &'static str = r#"_myapp() {
local i cur prev opts cmds
COMPREPLY=()
@ -63,7 +63,7 @@ static BASH: &'static str = r#"_myapp() {
return 0
;;
myapp__test)
opts=" -h -V --case --help --version "
opts=" -h -V --help --version --case "
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
@ -85,8 +85,8 @@ static BASH: &'static str = r#"_myapp() {
}
complete -F _myapp -o bashdefault -o default myapp
"#;
"#;
static ZSH: &'static str = r#"#compdef myapp
_myapp() {
@ -152,8 +152,8 @@ _myapp__test_commands() {
_describe -t commands 'myapp test commands' commands "$@"
}
_myapp "$@""#;
_myapp "$@""#;
static FISH: &'static str = r#"function __fish_using_command
set cmd (commandline -opc)
if [ (count $cmd) -eq (count $argv) ]
@ -176,9 +176,9 @@ complete -c myapp -n "__fish_using_command myapp test" -s h -l help -d "Prints h
complete -c myapp -n "__fish_using_command myapp test" -s V -l version -d "Prints version information"
complete -c myapp -n "__fish_using_command myapp help" -s h -l help -d "Prints help information"
complete -c myapp -n "__fish_using_command myapp help" -s V -l version -d "Prints version information"
"#;
#[cfg(not(target_os="windows"))]
"#;
#[cfg(not(target_os="windows"))]
static POWERSHELL: &'static str = r#"
@('myapp', './myapp') | %{
Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock {
@ -227,9 +227,9 @@ static POWERSHELL: &'static str = r#"
%{ New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ }
}
}
"#;
#[cfg(target_os="windows")]
"#;
#[cfg(target_os="windows")]
static POWERSHELL: &'static str = r#"
@('myapp', './myapp', 'myapp.exe', '.\myapp', '.\myapp.exe', './myapp.exe') | %{
Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock {
@ -267,9 +267,9 @@ static POWERSHELL: &'static str = r#"
%{ New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ }
}
}
"#;
#[cfg(not(target_os="windows"))]
"#;
#[cfg(not(target_os="windows"))]
static POWERSHELL_WUS: &'static str = r#"
@('my_app', './my_app') | %{
Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock {
@ -327,9 +327,9 @@ static POWERSHELL_WUS: &'static str = r#"
%{ New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ }
}
}
"#;
#[cfg(target_os="windows")]
"#;
#[cfg(target_os="windows")]
static POWERSHELL_WUS: &'static str = r#"
@('my_app', './my_app', 'my_app.exe', '.\my_app', '.\my_app.exe', './my_app.exe') | %{
Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock {
@ -374,8 +374,8 @@ static POWERSHELL_WUS: &'static str = r#"
%{ New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ }
}
}
"#;
"#;
static ZSH_WUS: &'static str = r#"#compdef my_app
_my_app() {
@ -458,8 +458,8 @@ _my_app__test_commands() {
_describe -t commands 'my_app test commands' commands "$@"
}
_my_app "$@""#;
_my_app "$@""#;
static FISH_WUS: &'static str = r#"function __fish_using_command
set cmd (commandline -opc)
if [ (count $cmd) -eq (count $argv) ]
@ -486,8 +486,8 @@ complete -c my_app -n "__fish_using_command my_app some_cmd" -s h -l help -d "Pr
complete -c my_app -n "__fish_using_command my_app some_cmd" -s V -l version -d "Prints version information"
complete -c my_app -n "__fish_using_command my_app help" -s h -l help -d "Prints help information"
complete -c my_app -n "__fish_using_command my_app help" -s V -l version -d "Prints version information"
"#;
"#;
static BASH_WUS: &'static str = r#"_my_app() {
local i cur prev opts cmds
COMPREPLY=()
@ -550,7 +550,7 @@ static BASH_WUS: &'static str = r#"_my_app() {
return 0
;;
my_app__some_cmd)
opts=" -h -V --config --help --version "
opts=" -h -V --help --version --config "
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
@ -569,7 +569,7 @@ static BASH_WUS: &'static str = r#"_my_app() {
return 0
;;
my_app__test)
opts=" -h -V --case --help --version "
opts=" -h -V --help --version --case "
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
@ -591,129 +591,125 @@ static BASH_WUS: &'static str = r#"_my_app() {
}
complete -F _my_app -o bashdefault -o default my_app
"#;
fn compare(left: &str, right: &str) -> bool {
let b = left == right;
if !b {
let re = Regex::new(" ").unwrap();
println!("");
println!("--> left");
// println!("{}", left);
println!("{}", re.replace_all(left, "\u{2022}"));
println!("--> right");
println!("{}", re.replace_all(right, "\u{2022}"));
// println!("{}", right);
println!("--")
}
b
}
fn build_app() -> App<'static, 'static> {
build_app_with_name("myapp")
}
fn build_app_with_name(s: &'static str) -> App<'static, 'static> {
App::new(s)
.about("Tests completions")
.arg(Arg::with_name("file")
.help("some input file"))
.subcommand(SubCommand::with_name("test")
.about("tests things")
.arg(Arg::with_name("case")
.long("case")
.takes_value(true)
.help("the case to test")))
}
fn build_app_with_underscore() -> App<'static, 'static> {
build_app_with_name("my_app")
.subcommand(SubCommand::with_name("some_cmd")
.about("tests other things")
.arg(Arg::with_name("config")
.long("--config")
.takes_value(true)
.help("the other case to test")))
}
#[test]
fn bash() {
let mut app = build_app();
let mut buf = vec![];
app.gen_completions_to("myapp", Shell::Bash, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, BASH));
}
#[test]
fn zsh() {
let mut app = build_app();
let mut buf = vec![];
app.gen_completions_to("myapp", Shell::Zsh, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, ZSH));
}
#[test]
fn fish() {
let mut app = build_app();
let mut buf = vec![];
app.gen_completions_to("myapp", Shell::Fish, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, FISH));
}
// Disabled until I figure out this windows line ending and AppVeyor issues
//#[test]
fn powershell() {
let mut app = build_app();
let mut buf = vec![];
app.gen_completions_to("myapp", Shell::PowerShell, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, POWERSHELL));
}
// Disabled until I figure out this windows line ending and AppVeyor issues
//#[test]
fn powershell_with_underscore() {
let mut app = build_app_with_underscore();
let mut buf = vec![];
app.gen_completions_to("my_app", Shell::PowerShell, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, POWERSHELL_WUS));
}
#[test]
fn bash_with_underscore() {
let mut app = build_app_with_underscore();
let mut buf = vec![];
app.gen_completions_to("my_app", Shell::Bash, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, BASH_WUS));
}
#[test]
fn fish_with_underscore() {
let mut app = build_app_with_underscore();
let mut buf = vec![];
app.gen_completions_to("my_app", Shell::Fish, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, FISH_WUS));
}
#[test]
fn zsh_with_underscore() {
let mut app = build_app_with_underscore();
let mut buf = vec![];
app.gen_completions_to("my_app", Shell::Zsh, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, ZSH_WUS));
}
"#;
fn compare(left: &str, right: &str) -> bool {
let b = left == right;
if !b {
let re = Regex::new(" ").unwrap();
println!("");
println!("--> left");
// println!("{}", left);
println!("{}", re.replace_all(left, "\u{2022}"));
println!("--> right");
println!("{}", re.replace_all(right, "\u{2022}"));
// println!("{}", right);
println!("--")
}
b
}
fn build_app() -> App<'static, 'static> { build_app_with_name("myapp") }
fn build_app_with_name(s: &'static str) -> App<'static, 'static> {
App::new(s)
.about("Tests completions")
.arg(Arg::with_name("file").help("some input file"))
.subcommand(SubCommand::with_name("test")
.about("tests things")
.arg(Arg::with_name("case")
.long("case")
.takes_value(true)
.help("the case to test")))
}
fn build_app_with_underscore() -> App<'static, 'static> {
build_app_with_name("my_app").subcommand(SubCommand::with_name("some_cmd")
.about("tests other things")
.arg(Arg::with_name("config")
.long("--config")
.takes_value(true)
.help("the other case to test")))
}
#[test]
fn bash() {
let mut app = build_app();
let mut buf = vec![];
app.gen_completions_to("myapp", Shell::Bash, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, BASH));
}
#[test]
fn zsh() {
let mut app = build_app();
let mut buf = vec![];
app.gen_completions_to("myapp", Shell::Zsh, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, ZSH));
}
#[test]
fn fish() {
let mut app = build_app();
let mut buf = vec![];
app.gen_completions_to("myapp", Shell::Fish, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, FISH));
}
// Disabled until I figure out this windows line ending and AppVeyor issues
//#[test]
// fn powershell() {
// let mut app = build_app();
// let mut buf = vec![];
// app.gen_completions_to("myapp", Shell::PowerShell, &mut buf);
// let string = String::from_utf8(buf).unwrap();
//
// assert!(compare(&*string, POWERSHELL));
// }
// Disabled until I figure out this windows line ending and AppVeyor issues
//#[test]
// fn powershell_with_underscore() {
// let mut app = build_app_with_underscore();
// let mut buf = vec![];
// app.gen_completions_to("my_app", Shell::PowerShell, &mut buf);
// let string = String::from_utf8(buf).unwrap();
//
// assert!(compare(&*string, POWERSHELL_WUS));
// }
#[test]
fn bash_with_underscore() {
let mut app = build_app_with_underscore();
let mut buf = vec![];
app.gen_completions_to("my_app", Shell::Bash, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, BASH_WUS));
}
#[test]
fn fish_with_underscore() {
let mut app = build_app_with_underscore();
let mut buf = vec![];
app.gen_completions_to("my_app", Shell::Fish, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, FISH_WUS));
}
#[test]
fn zsh_with_underscore() {
let mut app = build_app_with_underscore();
let mut buf = vec![];
app.gen_completions_to("my_app", Shell::Zsh, &mut buf);
let string = String::from_utf8(buf).unwrap();
assert!(compare(&*string, ZSH_WUS));
}

View file

@ -278,8 +278,7 @@ fn help_subcommand() {
#[test]
fn subcommand_short_help() {
let m = test::complex_app()
.get_matches_from_safe(vec!["clap-test", "subcmd", "-h"]);
let m = test::complex_app().get_matches_from_safe(vec!["clap-test", "subcmd", "-h"]);
assert!(m.is_err());
assert_eq!(m.unwrap_err().kind, ErrorKind::HelpDisplayed);
@ -287,8 +286,7 @@ fn subcommand_short_help() {
#[test]
fn subcommand_long_help() {
let m = test::complex_app()
.get_matches_from_safe(vec!["clap-test", "subcmd", "--help"]);
let m = test::complex_app().get_matches_from_safe(vec!["clap-test", "subcmd", "--help"]);
assert!(m.is_err());
assert_eq!(m.unwrap_err().kind, ErrorKind::HelpDisplayed);
@ -296,8 +294,7 @@ fn subcommand_long_help() {
#[test]
fn subcommand_help_rev() {
let m = test::complex_app()
.get_matches_from_safe(vec!["clap-test", "help", "subcmd"]);
let m = test::complex_app().get_matches_from_safe(vec!["clap-test", "help", "subcmd"]);
assert!(m.is_err());
assert_eq!(m.unwrap_err().kind, ErrorKind::HelpDisplayed);
@ -321,12 +318,11 @@ fn after_and_before_help_output() {
#[test]
fn multi_level_sc_help() {
let app = App::new("ctest")
.subcommand(SubCommand::with_name("subcmd")
.subcommand(SubCommand::with_name("multi")
.about("tests subcommands")
.author("Kevin K. <kbknapp@gmail.com>")
.version("0.1")
.args_from_usage("
.subcommand(SubCommand::with_name("subcmd").subcommand(SubCommand::with_name("multi")
.about("tests subcommands")
.author("Kevin K. <kbknapp@gmail.com>")
.version("0.1")
.args_from_usage("
-f, --flag 'tests flags'
-o, --option [scoption]... 'tests options'
")));
@ -354,16 +350,16 @@ fn issue_626_unicode_cutoff() {
.version("0.1")
.set_term_width(70)
.arg(Arg::with_name("cafe")
.short("c")
.long("cafe")
.value_name("FILE")
.help("A coffeehouse, coffee shop, or café is an establishment \
.short("c")
.long("cafe")
.value_name("FILE")
.help("A coffeehouse, coffee shop, or café is an establishment \
which primarily serves hot coffee, related coffee beverages \
(e.g., café latte, cappuccino, espresso), tea, and other hot \
beverages. Some coffeehouses also serve cold beverages such as \
iced coffee and iced tea. Many cafés also serve some type of \
food, such as light snacks, muffins, or pastries.")
.takes_value(true));
.takes_value(true));
assert!(test::compare_output(app, "ctest --help", ISSUE_626_CUTOFF, false));
}
@ -372,20 +368,20 @@ fn hide_possible_vals() {
let app = App::new("ctest")
.version("0.1")
.arg(Arg::with_name("pos")
.short("p")
.long("pos")
.value_name("VAL")
.possible_values(&["fast", "slow"])
.help("Some vals")
.takes_value(true))
.short("p")
.long("pos")
.value_name("VAL")
.possible_values(&["fast", "slow"])
.help("Some vals")
.takes_value(true))
.arg(Arg::with_name("cafe")
.short("c")
.long("cafe")
.value_name("FILE")
.hide_possible_values(true)
.possible_values(&["fast", "slow"])
.help("A coffeehouse, coffee shop, or café.")
.takes_value(true));
.short("c")
.long("cafe")
.value_name("FILE")
.hide_possible_values(true)
.possible_values(&["fast", "slow"])
.help("A coffeehouse, coffee shop, or café.")
.takes_value(true));
assert!(test::compare_output(app, "ctest --help", HIDE_POS_VALS, false));
}
@ -441,9 +437,9 @@ fn old_newline_chars() {
#[test]
fn issue_688_hidden_pos_vals() {
let filter_values = ["Nearest", "Linear", "Cubic", "Gaussian", "Lanczos3"];
let filter_values = ["Nearest", "Linear", "Cubic", "Gaussian", "Lanczos3"];
let app1 = App::new("ctest")
let app1 = App::new("ctest")
.version("0.1")
.set_term_width(120)
.setting(AppSettings::HidePossibleValuesInHelp)
@ -455,7 +451,7 @@ fn issue_688_hidden_pos_vals() {
.takes_value(true));
assert!(test::compare_output(app1, "ctest --help", ISSUE_688, false));
let app2 = App::new("ctest")
let app2 = App::new("ctest")
.version("0.1")
.set_term_width(120)
.arg(Arg::with_name("filter")
@ -466,7 +462,7 @@ fn issue_688_hidden_pos_vals() {
.takes_value(true));
assert!(test::compare_output(app2, "ctest --help", ISSUE_688, false));
let app3 = App::new("ctest")
let app3 = App::new("ctest")
.version("0.1")
.set_term_width(120)
.arg(Arg::with_name("filter")
@ -483,27 +479,26 @@ fn issue_702_multiple_values() {
.version("1.0")
.author("foo")
.about("bar")
.arg(Arg::with_name("arg1")
.help("some option"))
.arg(Arg::with_name("arg1").help("some option"))
.arg(Arg::with_name("arg2")
.multiple(true)
.help("some option"))
.multiple(true)
.help("some option"))
.arg(Arg::with_name("some")
.help("some option")
.short("s")
.long("some")
.takes_value(true))
.help("some option")
.short("s")
.long("some")
.takes_value(true))
.arg(Arg::with_name("other")
.help("some other option")
.short("o")
.long("other")
.takes_value(true))
.help("some other option")
.short("o")
.long("other")
.takes_value(true))
.arg(Arg::with_name("label")
.help("a label")
.short("l")
.long("label")
.multiple(true)
.takes_value(true));
.help("a label")
.short("l")
.long("label")
.multiple(true)
.takes_value(true));
assert!(test::compare_output(app, "myapp --help", ISSUE_702, false));
}
@ -512,17 +507,17 @@ fn issue_760() {
let app = App::new("ctest")
.version("0.1")
.arg(Arg::with_name("option")
.help("tests options")
.short("o")
.long("option")
.takes_value(true)
.multiple(true)
.number_of_values(1))
.help("tests options")
.short("o")
.long("option")
.takes_value(true)
.multiple(true)
.number_of_values(1))
.arg(Arg::with_name("opt")
.help("tests options")
.short("O")
.long("opt")
.takes_value(true));
.help("tests options")
.short("O")
.long("opt")
.takes_value(true));
assert!(test::compare_output(app, "ctest --help", ISSUE_760, false));
}
#[test]

View file

@ -106,8 +106,7 @@ fn quoted_arg_long_name() {
(@arg scpositional: index(1) "tests positionals"))
);
assert_eq!(app.p.long_list[2], "long-option-2");
let matches = app.get_matches_from_safe(vec!["bin_name", "value1", "value2", "--long-option-2"]).expect("Expected to successfully match the given args.");
let matches = app.get_matches_from_safe(vec!["bin_name", "value1", "value2", "--long-option-2"])
.expect("Expected to successfully match the given args.");
assert!(matches.is_present("option2"));
}

View file

@ -18,7 +18,7 @@ static COND_REQ_IN_USAGE: &'static str = "error: The following required argument
--output <output>
USAGE:
test --target <target> --input <input> --output <output>
test --input <input> --output <output> --target <target>
For more information try --help";
@ -46,7 +46,7 @@ fn flag_required_2() {
#[test]
fn option_required() {
let result = App::new("option_required")
.arg(Arg::from_usage("-f [flag] 'some flag'").requires("color"))
.arg(Arg::from_usage("-f [flag] 'some flag'").requires("c"))
.arg(Arg::from_usage("-c [color] 'third flag'"))
.get_matches_from_safe(vec!["", "-f", "val"]);
assert!(result.is_err());
@ -560,4 +560,4 @@ fn required_ifs_wrong_val_mult_fail() {
assert!(res.is_err());
assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument);
}
}

View file

@ -2,30 +2,21 @@ extern crate clap;
use clap::{App, Arg};
#[test]
#[should_panic]
fn unique_arg_names() {
App::new("some").args(&[
Arg::with_name("arg").short("a"),
Arg::with_name("arg").short("b")
]);
App::new("some").args(&[Arg::with_name("arg").short("a"), Arg::with_name("arg").short("b")]);
}
#[test]
#[should_panic]
fn unique_arg_shorts() {
App::new("some").args(&[
Arg::with_name("arg1").short("a"),
Arg::with_name("arg2").short("a")
]);
App::new("some").args(&[Arg::with_name("arg1").short("a"), Arg::with_name("arg2").short("a")]);
}
#[test]
#[should_panic]
fn unique_arg_longs() {
App::new("some").args(&[
Arg::with_name("arg1").long("long"),
Arg::with_name("arg2").long("long")
]);
App::new("some")
.args(&[Arg::with_name("arg1").long("long"), Arg::with_name("arg2").long("long")]);
}