From b55cae5fdb0ebd2c850b4a4832bfdc83b012bf7f Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 20 Feb 2017 22:42:10 -0500 Subject: [PATCH 1/9] refactor: removes unused fields and moves some bools to bitfields --- src/app/macros.rs | 10 +- src/app/mod.rs | 1 - src/app/parser.rs | 230 ++++++++++------------------- src/app/settings.rs | 102 ++++++++----- src/args/any_arg.rs | 1 - src/args/arg_builder/base.rs | 5 +- src/args/arg_builder/flag.rs | 11 +- src/args/arg_builder/option.rs | 9 +- src/args/arg_builder/positional.rs | 11 +- 9 files changed, 168 insertions(+), 212 deletions(-) diff --git a/src/app/macros.rs b/src/app/macros.rs index f589216f396..12af2e1973a 100644 --- a/src/app/macros.rs +++ b/src/app/macros.rs @@ -56,7 +56,7 @@ macro_rules! arg_post_processing { debug!("arg_post_processing!: Does '{}' have conflicts...", $arg.to_string()); if let Some(bl) = $arg.blacklist() { sdebugln!("Yes"); - + for c in bl { // Inject two-way conflicts debug!("arg_post_processing!: Has '{}' already been matched...", c); @@ -129,7 +129,7 @@ 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.settings.is_set(ArgSettings::Multiple) { + 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), @@ -149,10 +149,10 @@ macro_rules! parse_positional { debugln!("parse_positional!;"); validate_multiples!($_self, $p, $matcher); - if !$_self.trailing_vals && - ($_self.settings.is_set(AppSettings::TrailingVarArg) && + if !$_self.is_set(TrailingValues) && + ($_self.is_set(TrailingVarArg) && $pos_counter == $_self.positionals.len()) { - $_self.trailing_vals = true; + $_self.settings.set(TrailingValues); } let _ = try!($_self.add_val_to_arg($p, &$arg_os, $matcher)); diff --git a/src/app/mod.rs b/src/app/mod.rs index 7135e9f7c61..f53da523b6b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1531,7 +1531,6 @@ impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> { fn name(&self) -> &'n str { unreachable!("App struct does not support AnyArg::name, this is a bug!") } - fn id(&self) -> usize { self.p.id } fn overrides(&self) -> Option<&[&'e str]> { None } fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { None } fn blacklist(&self) -> Option<&[&'e str]> { None } diff --git a/src/app/parser.rs b/src/app/parser.rs index 4b5ec83cc9a..297ea44ffdc 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -31,69 +31,31 @@ use fmt::{Colorizer, ColorWhen}; use osstringext::OsStrExt2; use completions::Shell; use suggestions; +use app::settings::AppSettings::*; #[allow(missing_debug_implementations)] #[doc(hidden)] +#[derive(Clone, Default)] pub struct Parser<'a, 'b> where 'a: 'b { - propogated: bool, - required: Vec<&'a str>, - r_ifs: Vec<(&'a str, &'b str, &'a str)>, - pub short_list: Vec, - pub long_list: Vec<&'b str>, - blacklist: Vec<&'b str>, - // A list of possible flags + pub meta: AppMeta<'b>, + settings: AppFlags, + pub g_settings: AppFlags, pub flags: Vec>, - // A list of possible options pub opts: Vec>, - // A list of positional arguments pub positionals: VecMap>, - // A list of subcommands - #[doc(hidden)] pub subcommands: Vec>, groups: HashMap<&'a str, ArgGroup<'a>>, pub global_args: Vec>, + required: Vec<&'a str>, + r_ifs: Vec<(&'a str, &'b str, &'a str)>, + blacklist: Vec<&'b str>, overrides: Vec<&'b str>, + pub short_list: Vec, + pub long_list: Vec<&'b str>, help_short: Option, version_short: Option, - settings: AppFlags, - pub g_settings: AppFlags, - pub meta: AppMeta<'b>, - pub id: usize, - trailing_vals: bool, - valid_neg_num: bool, - // have we found a valid arg yet - valid_arg: bool, -} - -impl<'a, 'b> Default for Parser<'a, 'b> { - fn default() -> Self { - Parser { - propogated: false, - flags: vec![], - opts: vec![], - positionals: VecMap::new(), - subcommands: vec![], - help_short: None, - version_short: None, - required: vec![], - r_ifs: vec![], - short_list: vec![], - long_list: vec![], - blacklist: vec![], - groups: HashMap::new(), - global_args: vec![], - overrides: vec![], - g_settings: AppFlags::new(), - settings: AppFlags::new(), - meta: AppMeta::new(), - trailing_vals: false, - id: 0, - valid_neg_num: false, - valid_arg: false, - } - } } impl<'a, 'b> Parser<'a, 'b> @@ -116,12 +78,12 @@ impl<'a, 'b> Parser<'a, 'b> } pub fn gen_completions_to(&mut self, for_shell: Shell, buf: &mut W) { - if !self.propogated { + if !self.is_set(Propogated) { self.propogate_help_version(); self.build_bin_names(); self.propogate_globals(); self.propogate_settings(); - self.propogated = true; + self.set(Propogated); } ComplGen::new(self).generate(for_shell, buf) @@ -175,9 +137,9 @@ impl<'a, 'b> Parser<'a, 'b> l)); self.long_list.push(l); if l == "help" { - self.unset(AppSettings::NeedsLongHelp); + self.unset(NeedsLongHelp); } else if l == "version" { - self.unset(AppSettings::NeedsLongVersion); + self.unset(NeedsLongVersion); } } if a.is_set(ArgSettings::Required) { @@ -199,13 +161,11 @@ impl<'a, 'b> Parser<'a, 'b> } else if a.is_set(ArgSettings::TakesValue) { let mut ob = OptBuilder::from_arg(a, &mut self.required); let id = self.opts.len(); - ob.b.id = id; ob.s.unified_ord = self.flags.len() + self.opts.len(); self.opts.insert(id, ob); } else { let mut fb = FlagBuilder::from(a); let id = self.flags.len(); - fb.b.id = id; fb.s.unified_ord = self.flags.len() + self.opts.len(); self.flags.insert(id, fb); } @@ -247,7 +207,7 @@ impl<'a, 'b> Parser<'a, 'b> subcmd.p.meta.name); subcmd.p.meta.term_w = self.meta.term_w; if subcmd.p.meta.name == "help" { - self.settings.unset(AppSettings::NeedsSubcommandHelp); + self.unset(NeedsSubcommandHelp); } self.subcommands.push(subcmd); @@ -265,14 +225,14 @@ impl<'a, 'b> Parser<'a, 'b> // We have to create a new scope in order to tell rustc the borrow of `sc` is // done and to recursively call this method { - let vsc = self.settings.is_set(AppSettings::VersionlessSubcommands); - let gv = self.settings.is_set(AppSettings::GlobalVersion); + let vsc = self.settings.is_set(VersionlessSubcommands); + let gv = self.settings.is_set(GlobalVersion); if vsc { - sc.p.settings.set(AppSettings::DisableVersion); + sc.p.set(DisableVersion); } if gv && sc.p.meta.version.is_none() && self.meta.version.is_some() { - sc.p.set(AppSettings::GlobalVersion); + sc.p.set(GlobalVersion); sc.p.meta.version = Some(self.meta.version.unwrap()); } sc.p.settings = sc.p.settings | self.g_settings; @@ -284,8 +244,8 @@ impl<'a, 'b> Parser<'a, 'b> #[cfg_attr(feature = "lints", allow(needless_borrow))] pub fn derive_display_order(&mut self) { - if self.settings.is_set(AppSettings::DeriveDisplayOrder) { - let unified = self.settings.is_set(AppSettings::UnifiedHelpMessage); + if self.is_set(DeriveDisplayOrder) { + let unified = self.is_set(UnifiedHelpMessage); for (i, o) in self.opts .iter_mut() .enumerate() @@ -464,8 +424,7 @@ impl<'a, 'b> Parser<'a, 'b> count += 1; debugln!("Parser::get_args_tag:iter: {} Args not required", count); } - if !self.is_set(AppSettings::DontCollapseArgsInUsage) && - (count > 1 || self.positionals.len() > 1) { + if !self.is_set(DontCollapseArgsInUsage) && (count > 1 || self.positionals.len() > 1) { return None; // [ARGS] } else if count == 1 { let p = self.positionals @@ -473,8 +432,7 @@ impl<'a, 'b> Parser<'a, 'b> .find(|p| !p.is_set(ArgSettings::Required)) .expect(INTERNAL_ERROR_MSG); return Some(format!(" [{}]{}", p.name_no_brackets(), p.multiple_str())); - } else if self.is_set(AppSettings::DontCollapseArgsInUsage) && - !self.positionals.is_empty() { + } else if self.is_set(DontCollapseArgsInUsage) && !self.positionals.is_empty() { return Some(self.positionals .values() .filter(|p| !p.is_set(ArgSettings::Required)) @@ -579,7 +537,7 @@ impl<'a, 'b> Parser<'a, 'b> }, "Only the last positional argument, or second to last positional argument may be set to .multiple(true)"); - self.set(AppSettings::LowIndexMultiplePositional); + self.set(LowIndexMultiplePositional); } debug_assert!(self.positionals.values() @@ -591,7 +549,7 @@ impl<'a, 'b> Parser<'a, 'b> // If it's required we also need to ensure all previous positionals are // required too - if self.is_set(AppSettings::AllowMissingPositional) { + if self.is_set(AllowMissingPositional) { let mut found = false; let mut foundx2 = false; for p in self.positionals.values().rev() { @@ -648,7 +606,7 @@ impl<'a, 'b> Parser<'a, 'b> #[inline] fn possible_subcommand(&self, arg_os: &OsStr) -> bool { debugln!("Parser::possible_subcommand: arg={:?}", arg_os); - if self.is_set(AppSettings::ArgsNegateSubcommands) && self.valid_arg { + if self.is_set(ArgsNegateSubcommands) && self.is_set(ValidArgFound) { return false; } self.subcommands @@ -742,12 +700,12 @@ impl<'a, 'b> Parser<'a, 'b> #[inline] fn is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: Option<&'a str>) -> bool { debugln!("Parser::is_new_arg: arg={:?}, Needs Val of={:?}", arg_os, needs_val_of); - let app_wide_settings = if self.is_set(AppSettings::AllowLeadingHyphen) { + let app_wide_settings = if self.is_set(AllowLeadingHyphen) { true - } else if self.is_set(AppSettings::AllowNegativeNumbers) { + } else if self.is_set(AllowNegativeNumbers) { let a = arg_os.to_string_lossy(); if a.parse::().is_ok() || a.parse::().is_ok() { - self.valid_neg_num = true; + self.set(ValidNegNumFound); true } else { false @@ -815,15 +773,15 @@ impl<'a, 'b> Parser<'a, 'b> let arg_os = arg.into(); debugln!("Parser::get_matches_with: Begin parsing '{:?}' ({:?})", arg_os, &*arg_os.as_bytes()); - self.valid_neg_num = false; + self.unset(ValidNegNumFound); // Is this a new argument, or values from a previous option? let starts_new_arg = self.is_new_arg(&arg_os, needs_val_of); // Has the user already passed '--'? Meaning only positional args follow - if !self.trailing_vals { + if !self.is_set(TrailingValues) { // Does the arg match a subcommand name, or any of it's aliases (if defined) if self.possible_subcommand(&arg_os) { - if &*arg_os == "help" && self.is_set(AppSettings::NeedsSubcommandHelp) { + if &*arg_os == "help" && self.is_set(NeedsSubcommandHelp) { try!(self.parse_help_subcommand(it)); } subcmd_name = Some(arg_os.to_str().expect(INVALID_UTF8).to_owned()); @@ -845,13 +803,12 @@ impl<'a, 'b> Parser<'a, 'b> if arg_os.len_() == 2 { // The user has passed '--' which means only positional args follow no // matter what they start with - self.trailing_vals = true; + self.set(TrailingValues); continue; } needs_val_of = try!(self.parse_long_arg(matcher, &arg_os)); - if !(needs_val_of.is_none() && - self.is_set(AppSettings::AllowLeadingHyphen)) { + if !(needs_val_of.is_none() && self.is_set(AllowLeadingHyphen)) { continue; } } else if arg_os.starts_with(b"-") && arg_os.len_() != 1 { @@ -861,7 +818,7 @@ impl<'a, 'b> Parser<'a, 'b> needs_val_of = try!(self.parse_short_arg(matcher, &arg_os)); // If it's None, we then check if one of those two AppSettings was set if needs_val_of.is_none() { - if self.is_set(AppSettings::AllowNegativeNumbers) { + if self.is_set(AllowNegativeNumbers) { if !(arg_os.to_string_lossy().parse::().is_ok() || arg_os.to_string_lossy().parse::().is_ok()) { return Err(Error::unknown_argument(&*arg_os.to_string_lossy(), @@ -869,7 +826,7 @@ impl<'a, 'b> Parser<'a, 'b> &*self.create_current_usage(matcher, None), self.color())); } - } else if !self.is_set(AppSettings::AllowLeadingHyphen) { + } else if !self.is_set(AllowLeadingHyphen) { continue; } } else { @@ -878,7 +835,7 @@ impl<'a, 'b> Parser<'a, 'b> } } - if !self.is_set(AppSettings::ArgsNegateSubcommands) { + if !self.is_set(ArgsNegateSubcommands) { if let Some(cdate) = suggestions::did_you_mean(&*arg_os.to_string_lossy(), self.subcommands .iter() @@ -897,11 +854,10 @@ impl<'a, 'b> Parser<'a, 'b> } } - let low_index_mults = self.is_set(AppSettings::LowIndexMultiplePositional) && + let low_index_mults = self.is_set(LowIndexMultiplePositional) && !self.positionals.is_empty() && pos_counter == (self.positionals.len() - 1); - let missing_pos = self.is_set(AppSettings::AllowMissingPositional) && - !self.positionals.is_empty() && + let missing_pos = self.is_set(AllowMissingPositional) && !self.positionals.is_empty() && pos_counter == (self.positionals.len() - 1); debugln!("Parser::get_matches_with: Low index multiples...{:?}", low_index_mults); debugln!("Parser::get_matches_with: Positional counter...{}", pos_counter); @@ -933,13 +889,13 @@ impl<'a, 'b> Parser<'a, 'b> } if let Some(p) = self.positionals.get(pos_counter) { parse_positional!(self, p, arg_os, pos_counter, matcher); - self.valid_arg = true; - } else if self.settings.is_set(AppSettings::AllowExternalSubcommands) { + self.settings.set(ValidArgFound); + } else if self.is_set(AllowExternalSubcommands) { // Get external subcommand name let sc_name = match arg_os.to_str() { Some(s) => s.to_string(), None => { - if !self.settings.is_set(AppSettings::StrictUtf8) { + if !self.is_set(StrictUtf8) { return Err(Error::invalid_utf8(&*self.create_current_usage(matcher, None), self.color())); @@ -952,7 +908,7 @@ impl<'a, 'b> Parser<'a, 'b> let mut sc_m = ArgMatcher::new(); while let Some(v) = it.next() { let a = v.into(); - if a.to_str().is_none() && !self.settings.is_set(AppSettings::StrictUtf8) { + if a.to_str().is_none() && !self.is_set(StrictUtf8) { return Err(Error::invalid_utf8(&*self.create_current_usage(matcher, None), self.color())); } @@ -963,8 +919,7 @@ impl<'a, 'b> Parser<'a, 'b> name: sc_name, matches: sc_m.into(), }); - } else if !(self.is_set(AppSettings::AllowLeadingHyphen) || - self.is_set(AppSettings::AllowNegativeNumbers)) { + } else if !(self.is_set(AllowLeadingHyphen) || self.is_set(AllowNegativeNumbers)) { return Err(Error::unknown_argument(&*arg_os.to_string_lossy(), "", &*self.create_current_usage(matcher, None), @@ -994,12 +949,12 @@ impl<'a, 'b> Parser<'a, 'b> .expect(INTERNAL_ERROR_MSG); try!(self.parse_subcommand(sc_name, matcher, it)); } - } else if self.is_set(AppSettings::SubcommandRequired) { + } else if self.is_set(SubcommandRequired) { let bn = self.meta.bin_name.as_ref().unwrap_or(&self.meta.name); return Err(Error::missing_subcommand(bn, &self.create_current_usage(matcher, None), self.color())); - } else if self.is_set(AppSettings::SubcommandRequiredElseHelp) { + } else if self.is_set(SubcommandRequiredElseHelp) { debugln!("parser::get_matches_with: SubcommandRequiredElseHelp=true"); let mut out = vec![]; try!(self.write_help_err(&mut out)); @@ -1040,15 +995,14 @@ impl<'a, 'b> Parser<'a, 'b> } try!(self.validate_blacklist(matcher)); - if !(self.settings.is_set(AppSettings::SubcommandsNegateReqs) && - subcmd_name.is_some()) && !reqs_validated { + if !(self.is_set(SubcommandsNegateReqs) && subcmd_name.is_some()) && !reqs_validated { try!(self.validate_required(matcher)); } try!(self.validate_matched_args(matcher)); matcher.usage(self.create_usage(&[])); if matcher.is_empty() && matcher.subcommand_name().is_none() && - self.is_set(AppSettings::ArgRequiredElseHelp) { + self.is_set(ArgRequiredElseHelp) { let mut out = vec![]; try!(self.write_help_err(&mut out)); return Err(Error { @@ -1106,7 +1060,7 @@ impl<'a, 'b> Parser<'a, 'b> use std::fmt::Write; debugln!("Parser::parse_subcommand;"); let mut mid_string = String::new(); - if !self.settings.is_set(AppSettings::SubcommandsNegateReqs) { + if !self.is_set(SubcommandsNegateReqs) { let mut hs: Vec<&str> = self.required.iter().map(|n| &**n).collect(); for k in matcher.arg_names() { hs.push(k); @@ -1226,7 +1180,7 @@ impl<'a, 'b> Parser<'a, 'b> pub fn create_help_and_version(&mut self) { debugln!("Parser::create_help_and_version;"); // name is "hclap_help" because flags are sorted by name - if self.is_set(AppSettings::NeedsLongHelp) { + if self.is_set(NeedsLongHelp) { debugln!("Parser::create_help_and_version: Building --help"); if self.help_short.is_none() && !self.short_list.contains(&'h') { self.help_short = Some('h'); @@ -1236,7 +1190,6 @@ impl<'a, 'b> Parser<'a, 'b> b: Base { name: "hclap_help", help: Some("Prints help information"), - id: id, ..Default::default() }, s: Switched { @@ -1251,8 +1204,7 @@ impl<'a, 'b> Parser<'a, 'b> self.long_list.push("help"); self.flags.insert(id, arg); } - if !self.settings.is_set(AppSettings::DisableVersion) && - self.is_set(AppSettings::NeedsLongVersion) { + if !self.is_set(DisableVersion) && self.is_set(NeedsLongVersion) { debugln!("Parser::create_help_and_version: Building --version"); if self.version_short.is_none() && !self.short_list.contains(&'V') { self.version_short = Some('V'); @@ -1263,7 +1215,6 @@ impl<'a, 'b> Parser<'a, 'b> b: Base { name: "vclap_version", help: Some("Prints version information"), - id: id, ..Default::default() }, s: Switched { @@ -1278,8 +1229,8 @@ impl<'a, 'b> Parser<'a, 'b> self.long_list.push("version"); self.flags.insert(id, arg); } - if !self.subcommands.is_empty() && !self.is_set(AppSettings::DisableHelpSubcommand) && - self.is_set(AppSettings::NeedsSubcommandHelp) { + if !self.subcommands.is_empty() && !self.is_set(DisableHelpSubcommand) && + self.is_set(NeedsSubcommandHelp) { debugln!("Parser::create_help_and_version: Building help"); self.subcommands .push(App::new("help") @@ -1313,11 +1264,11 @@ impl<'a, 'b> Parser<'a, 'b> debugln!("Parser::check_for_help_and_version_str;"); debug!("Parser::check_for_help_and_version_str: Checking if --{} is help or version...", arg.to_str().unwrap()); - if arg == "help" && self.settings.is_set(AppSettings::NeedsLongHelp) { + if arg == "help" && self.is_set(NeedsLongHelp) { sdebugln!("Help"); try!(self._help()); } - if arg == "version" && self.settings.is_set(AppSettings::NeedsLongVersion) { + if arg == "version" && self.is_set(NeedsLongVersion) { sdebugln!("Version"); try!(self._version()); } @@ -1330,13 +1281,13 @@ impl<'a, 'b> Parser<'a, 'b> debugln!("Parser::check_for_help_and_version_char;"); debug!("Parser::check_for_help_and_version_char: Checking if -{} is help or version...", arg); if let Some(h) = self.help_short { - if arg == h && self.settings.is_set(AppSettings::NeedsLongHelp) { + if arg == h && self.is_set(NeedsLongHelp) { sdebugln!("Help"); try!(self._help()); } } if let Some(v) = self.version_short { - if arg == v && self.settings.is_set(AppSettings::NeedsLongVersion) { + if arg == v && self.is_set(NeedsLongVersion) { sdebugln!("Version"); try!(self._version()); } @@ -1386,14 +1337,14 @@ impl<'a, 'b> Parser<'a, 'b> if let Some(opt) = find_by_long!(self, &arg, opts) { debugln!("Parser::parse_long_arg: Found valid opt '{}'", opt.to_string()); - self.valid_arg = true; + self.settings.set(ValidArgFound); let ret = try!(self.parse_opt(val, opt, val.is_some(), matcher)); arg_post_processing!(self, opt, matcher); return Ok(ret); } else if let Some(flag) = find_by_long!(self, &arg, flags) { debugln!("Parser::parse_long_arg: Found valid flag '{}'", flag.to_string()); - self.valid_arg = true; + self.settings.set(ValidArgFound); // Only flags could be help or version, and we need to check the raw long // so this is the first point to check try!(self.check_for_help_and_version_str(arg)); @@ -1404,9 +1355,9 @@ impl<'a, 'b> Parser<'a, 'b> arg_post_processing!(self, flag, matcher); return Ok(None); - } else if self.is_set(AppSettings::AllowLeadingHyphen) { + } else if self.is_set(AllowLeadingHyphen) { return Ok(None); - } else if self.valid_neg_num { + } else if self.is_set(ValidNegNumFound) { return Ok(None); } @@ -1425,7 +1376,7 @@ impl<'a, 'b> Parser<'a, 'b> // If AllowLeadingHyphen is set, we want to ensure `-val` gets parsed as `-val` and not `-v` `-a` `-l` assuming // `v` `a` and `l` are all, or mostly, valid shorts. - if self.is_set(AppSettings::AllowLeadingHyphen) { + if self.is_set(AllowLeadingHyphen) { let mut good = true; for c in arg.chars() { good = self.short_list.contains(&c); @@ -1433,7 +1384,7 @@ impl<'a, 'b> Parser<'a, 'b> if !good { return Ok(None); } - } else if self.valid_neg_num { + } else if self.is_set(ValidNegNumFound) { // TODO: Add docs about having AllowNegativeNumbers and `-2` as a valid short // May be better to move this to *after* not finding a valid flag/opt? debugln!("Parser::parse_short_arg: Valid negative num..."); @@ -1450,7 +1401,7 @@ impl<'a, 'b> Parser<'a, 'b> .iter() .find(|&o| o.s.short.is_some() && o.s.short.unwrap() == c) { debugln!("Parser::parse_short_arg:iter: Found valid short opt -{} in '{}'", c, arg); - self.valid_arg = true; + self.settings.set(ValidArgFound); // Check for trailing concatenated value let p: Vec<_> = arg.splitn(2, c).collect(); debugln!("Parser::parse_short_arg:iter: arg: {:?}, arg_os: {:?}, full_arg: {:?}", @@ -1478,7 +1429,7 @@ impl<'a, 'b> Parser<'a, 'b> .iter() .find(|&f| f.s.short.is_some() && f.s.short.unwrap() == c) { debugln!("Parser::parse_short_arg:iter: Found valid short flag -{}", c); - self.valid_arg = true; + self.settings.set(ValidArgFound); // Only flags can be help or version try!(self.check_for_help_and_version_char(c)); try!(self.parse_flag(flag, matcher)); @@ -1558,8 +1509,8 @@ impl<'a, 'b> Parser<'a, 'b> debugln!("Parser::add_val_to_arg; arg={}, val={:?}", arg.name(), val); let mut ret = None; debugln!("Parser::add_val_to_arg; trailing_vals={:?}, DontDelimTrailingVals={:?}", - self.trailing_vals, self.is_set(AppSettings::DontDelimitTrailingValues)); - if !(self.trailing_vals && self.is_set(AppSettings::DontDelimitTrailingValues)) { + self.is_set(TrailingValues), self.is_set(DontDelimitTrailingValues)); + if !(self.is_set(TrailingValues) && self.is_set(DontDelimitTrailingValues)) { if let Some(delim) = arg.val_delim() { if val.is_empty_() { ret = try!(self.add_single_val_to_arg(arg, val, matcher)); @@ -1618,7 +1569,7 @@ impl<'a, 'b> Parser<'a, 'b> where A: AnyArg<'a, 'b> + Display { debugln!("Parser::validate_value: val={:?}", val); - if self.is_set(AppSettings::StrictUtf8) && val.to_str().is_none() { + if self.is_set(StrictUtf8) && val.to_str().is_none() { return Err(Error::invalid_utf8(&*self.create_current_usage(matcher, None), self.color())); } @@ -2015,12 +1966,12 @@ impl<'a, 'b> Parser<'a, 'b> .fold(String::new(), |a, s| a + &format!(" {}", s)[..]); let flags = self.needs_flags_tag(); - if flags && !self.is_set(AppSettings::UnifiedHelpMessage) { + if flags && !self.is_set(UnifiedHelpMessage) { usage.push_str(" [FLAGS]"); } else if flags { usage.push_str(" [OPTIONS]"); } - if !self.is_set(AppSettings::UnifiedHelpMessage) && self.has_opts() && + if !self.is_set(UnifiedHelpMessage) && self.has_opts() && self.opts.iter().any(|o| !o.b.settings.is_set(ArgSettings::Required)) { usage.push_str(" [OPTIONS]"); } @@ -2045,9 +1996,9 @@ impl<'a, 'b> Parser<'a, 'b> } - if self.has_subcommands() && !self.is_set(AppSettings::SubcommandRequired) { + if self.has_subcommands() && !self.is_set(SubcommandRequired) { usage.push_str(" [SUBCOMMAND]"); - } else if self.is_set(AppSettings::SubcommandRequired) && self.has_subcommands() { + } else if self.is_set(SubcommandRequired) && self.has_subcommands() { usage.push_str(" "); } } else { @@ -2081,7 +2032,7 @@ impl<'a, 'b> Parser<'a, 'b> }) [..]); usage.push_str(&*r_string); - if self.is_set(AppSettings::SubcommandRequired) { + if self.is_set(SubcommandRequired) { usage.push_str(" "); } } @@ -2191,10 +2142,10 @@ impl<'a, 'b> Parser<'a, 'b> pub fn color(&self) -> ColorWhen { debugln!("Parser::color;"); debug!("Parser::color: Color setting..."); - if self.is_set(AppSettings::ColorNever) { + if self.is_set(ColorNever) { sdebugln!("Never"); ColorWhen::Never - } else if self.is_set(AppSettings::ColorAlways) { + } else if self.is_set(ColorAlways) { sdebugln!("Always"); ColorWhen::Always } else { @@ -2239,34 +2190,3 @@ impl<'a, 'b> Parser<'a, 'b> None } } - -impl<'a, 'b> Clone for Parser<'a, 'b> - where 'a: 'b -{ - fn clone(&self) -> Self { - Parser { - propogated: self.propogated, - required: self.required.clone(), - short_list: self.short_list.clone(), - long_list: self.long_list.clone(), - blacklist: self.blacklist.clone(), - flags: self.flags.clone(), - opts: self.opts.clone(), - positionals: self.positionals.clone(), - subcommands: self.subcommands.clone(), - groups: self.groups.clone(), - global_args: self.global_args.clone(), - overrides: self.overrides.clone(), - help_short: self.help_short, - version_short: self.version_short, - settings: self.settings, - g_settings: self.g_settings, - meta: self.meta.clone(), - trailing_vals: self.trailing_vals, - id: self.id, - valid_neg_num: self.valid_neg_num, - r_ifs: self.r_ifs.clone(), - valid_arg: self.valid_arg, - } - } -} diff --git a/src/app/settings.rs b/src/app/settings.rs index ae71a0c1ac5..42edc6105fc 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -5,40 +5,44 @@ use std::ops::BitOr; bitflags! { flags Flags: u64 { - const SC_NEGATE_REQS = 0b0000000000000000000000000000000001, - const SC_REQUIRED = 0b0000000000000000000000000000000010, - const A_REQUIRED_ELSE_HELP = 0b0000000000000000000000000000000100, - const GLOBAL_VERSION = 0b0000000000000000000000000000001000, - const VERSIONLESS_SC = 0b0000000000000000000000000000010000, - const UNIFIED_HELP = 0b0000000000000000000000000000100000, - const WAIT_ON_ERROR = 0b0000000000000000000000000001000000, - const SC_REQUIRED_ELSE_HELP= 0b0000000000000000000000000010000000, - const NEEDS_LONG_HELP = 0b0000000000000000000000000100000000, - const NEEDS_LONG_VERSION = 0b0000000000000000000000001000000000, - const NEEDS_SC_HELP = 0b0000000000000000000000010000000000, - const DISABLE_VERSION = 0b0000000000000000000000100000000000, - const HIDDEN = 0b0000000000000000000001000000000000, - const TRAILING_VARARG = 0b0000000000000000000010000000000000, - const NO_BIN_NAME = 0b0000000000000000000100000000000000, - const ALLOW_UNK_SC = 0b0000000000000000001000000000000000, - const UTF8_STRICT = 0b0000000000000000010000000000000000, - const UTF8_NONE = 0b0000000000000000100000000000000000, - const LEADING_HYPHEN = 0b0000000000000001000000000000000000, - const NO_POS_VALUES = 0b0000000000000010000000000000000000, - const NEXT_LINE_HELP = 0b0000000000000100000000000000000000, - const DERIVE_DISP_ORDER = 0b0000000000001000000000000000000000, - const COLORED_HELP = 0b0000000000010000000000000000000000, - const COLOR_ALWAYS = 0b0000000000100000000000000000000000, - const COLOR_AUTO = 0b0000000001000000000000000000000000, - const COLOR_NEVER = 0b0000000010000000000000000000000000, - const DONT_DELIM_TRAIL = 0b0000000100000000000000000000000000, - const ALLOW_NEG_NUMS = 0b0000001000000000000000000000000000, - const LOW_INDEX_MUL_POS = 0b0000010000000000000000000000000000, - const DISABLE_HELP_SC = 0b0000100000000000000000000000000000, - const DONT_COLLAPSE_ARGS = 0b0001000000000000000000000000000000, - const ARGS_NEGATE_SCS = 0b0010000000000000000000000000000000, - const PROPAGATE_VALS_DOWN = 0b0100000000000000000000000000000000, - const ALLOW_MISSING_POS = 0b1000000000000000000000000000000000, + const SC_NEGATE_REQS = 0b00000000000000000000000000000000000001, + const SC_REQUIRED = 0b00000000000000000000000000000000000010, + const A_REQUIRED_ELSE_HELP = 0b00000000000000000000000000000000000100, + const GLOBAL_VERSION = 0b00000000000000000000000000000000001000, + const VERSIONLESS_SC = 0b00000000000000000000000000000000010000, + const UNIFIED_HELP = 0b00000000000000000000000000000000100000, + const WAIT_ON_ERROR = 0b00000000000000000000000000000001000000, + const SC_REQUIRED_ELSE_HELP= 0b00000000000000000000000000000010000000, + const NEEDS_LONG_HELP = 0b00000000000000000000000000000100000000, + const NEEDS_LONG_VERSION = 0b00000000000000000000000000001000000000, + const NEEDS_SC_HELP = 0b00000000000000000000000000010000000000, + const DISABLE_VERSION = 0b00000000000000000000000000100000000000, + const HIDDEN = 0b00000000000000000000000001000000000000, + const TRAILING_VARARG = 0b00000000000000000000000010000000000000, + const NO_BIN_NAME = 0b00000000000000000000000100000000000000, + const ALLOW_UNK_SC = 0b00000000000000000000001000000000000000, + const UTF8_STRICT = 0b00000000000000000000010000000000000000, + const UTF8_NONE = 0b00000000000000000000100000000000000000, + const LEADING_HYPHEN = 0b00000000000000000001000000000000000000, + const NO_POS_VALUES = 0b00000000000000000010000000000000000000, + const NEXT_LINE_HELP = 0b00000000000000000100000000000000000000, + const DERIVE_DISP_ORDER = 0b00000000000000001000000000000000000000, + const COLORED_HELP = 0b00000000000000010000000000000000000000, + const COLOR_ALWAYS = 0b00000000000000100000000000000000000000, + const COLOR_AUTO = 0b00000000000001000000000000000000000000, + const COLOR_NEVER = 0b00000000000010000000000000000000000000, + const DONT_DELIM_TRAIL = 0b00000000000100000000000000000000000000, + const ALLOW_NEG_NUMS = 0b00000000001000000000000000000000000000, + const LOW_INDEX_MUL_POS = 0b00000000010000000000000000000000000000, + const DISABLE_HELP_SC = 0b00000000100000000000000000000000000000, + const DONT_COLLAPSE_ARGS = 0b00000001000000000000000000000000000000, + const ARGS_NEGATE_SCS = 0b00000010000000000000000000000000000000, + const PROPAGATE_VALS_DOWN = 0b00000100000000000000000000000000000000, + const ALLOW_MISSING_POS = 0b00001000000000000000000000000000000000, + const TRAILING_VALUES = 0b00010000000000000000000000000000000000, + const VALID_NEG_NUM_FOUND = 0b00100000000000000000000000000000000000, + const PROPOGATED = 0b01000000000000000000000000000000000000, + const VALID_ARG_FOUND = 0b10000000000000000000000000000000000000 } } @@ -94,7 +98,11 @@ impl AppFlags { UnifiedHelpMessage => UNIFIED_HELP, NextLineHelp => NEXT_LINE_HELP, VersionlessSubcommands => VERSIONLESS_SC, - WaitOnError => WAIT_ON_ERROR + WaitOnError => WAIT_ON_ERROR, + TrailingValues => TRAILING_VALUES, + ValidNegNumFound => VALID_NEG_NUM_FOUND, + Propogated => PROPOGATED, + ValidArgFound => VALID_ARG_FOUND } } @@ -807,6 +815,18 @@ pub enum AppSettings { #[doc(hidden)] LowIndexMultiplePositional, + + #[doc(hidden)] + TrailingValues, + + #[doc(hidden)] + ValidNegNumFound, + + #[doc(hidden)] + Propogated, + + #[doc(hidden)] + ValidArgFound, } impl FromStr for AppSettings { @@ -842,6 +862,10 @@ impl FromStr for AppSettings { "unifiedhelpmessage" => Ok(AppSettings::UnifiedHelpMessage), "versionlesssubcommands" => Ok(AppSettings::VersionlessSubcommands), "waitonerror" => Ok(AppSettings::WaitOnError), + "validnegnumfound" => Ok(AppSettings::ValidNegNumFound), + "validargfound" => Ok(AppSettings::ValidArgFound), + "propogated" => Ok(AppSettings::Propogated), + "trailingvalues" => Ok(AppSettings::TrailingValues), _ => Err("unknown AppSetting, cannot convert from str".to_owned()), } } @@ -911,6 +935,14 @@ mod test { AppSettings::VersionlessSubcommands); assert_eq!("waitonerror".parse::().unwrap(), AppSettings::WaitOnError); + assert_eq!("validnegnumfound".parse::().unwrap(), + AppSettings::ValidNegNumFound); + assert_eq!("validargfound".parse::().unwrap(), + AppSettings::ValidArgFound); + assert_eq!("propogated".parse::().unwrap(), + AppSettings::Propogated); + assert_eq!("trailingvalues".parse::().unwrap(), + AppSettings::TrailingValues); assert!("hahahaha".parse::().is_err()); } } diff --git a/src/args/any_arg.rs b/src/args/any_arg.rs index 6b20cca62fb..55245dbad23 100644 --- a/src/args/any_arg.rs +++ b/src/args/any_arg.rs @@ -12,7 +12,6 @@ use args::settings::ArgSettings; #[doc(hidden)] pub trait AnyArg<'n, 'e>: std_fmt::Display { fn name(&self) -> &'n str; - fn id(&self) -> usize; fn overrides(&self) -> Option<&[&'e str]>; fn aliases(&self) -> Option>; fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]>; diff --git a/src/args/arg_builder/base.rs b/src/args/arg_builder/base.rs index fa52016f760..95ebe6637d0 100644 --- a/src/args/arg_builder/base.rs +++ b/src/args/arg_builder/base.rs @@ -5,7 +5,6 @@ pub struct Base<'a, 'b> where 'a: 'b { pub name: &'a str, - pub id: usize, pub help: Option<&'b str>, pub blacklist: Option>, pub settings: ArgFlags, @@ -24,7 +23,5 @@ impl<'n, 'e> Base<'n, 'e> { } impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Base<'n, 'e> { - fn from(a: &'z Arg<'n, 'e>) -> Self { - a.b.clone() - } + fn from(a: &'z Arg<'n, 'e>) -> Self { a.b.clone() } } diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index ec3b5c35c62..eb4f5542ddb 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -50,9 +50,10 @@ impl<'n, 'e> Display for FlagBuilder<'n, 'e> { impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> { fn name(&self) -> &'n str { self.b.name } - fn id(&self) -> usize { self.b.id } fn overrides(&self) -> Option<&[&'e str]> { self.b.overrides.as_ref().map(|o| &o[..]) } - fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { self.b.requires.as_ref().map(|o| &o[..]) } + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { + self.b.requires.as_ref().map(|o| &o[..]) + } fn blacklist(&self) -> Option<&[&'e str]> { self.b.blacklist.as_ref().map(|o| &o[..]) } fn required_unless(&self) -> Option<&[&'e str]> { None } fn is_set(&self, s: ArgSettings) -> bool { self.b.settings.is_set(s) } @@ -70,9 +71,11 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> { fn long(&self) -> Option<&'e str> { self.s.long } fn val_delim(&self) -> Option { None } fn help(&self) -> Option<&'e str> { self.b.help } - fn val_terminator(&self) -> Option<&'e str> {None} + fn val_terminator(&self) -> Option<&'e str> { None } fn default_val(&self) -> Option<&'e OsStr> { None } - fn default_vals_ifs(&self) -> Option, &'e OsStr)>> {None} + fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { + None + } fn longest_filter(&self) -> bool { self.s.long.is_some() } fn aliases(&self) -> Option> { if let Some(ref aliases) = self.s.aliases { diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index 0bb784e43da..0cf37931dbb 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -94,9 +94,10 @@ impl<'n, 'e> Display for OptBuilder<'n, 'e> { impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> { fn name(&self) -> &'n str { self.b.name } - fn id(&self) -> usize { self.b.id } fn overrides(&self) -> Option<&[&'e str]> { self.b.overrides.as_ref().map(|o| &o[..]) } - fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { self.b.requires.as_ref().map(|o| &o[..]) } + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { + self.b.requires.as_ref().map(|o| &o[..]) + } fn blacklist(&self) -> Option<&[&'e str]> { self.b.blacklist.as_ref().map(|o| &o[..]) } fn required_unless(&self) -> Option<&[&'e str]> { self.b.r_unless.as_ref().map(|o| &o[..]) } fn val_names(&self) -> Option<&VecMap<&'e str>> { self.v.val_names.as_ref() } @@ -120,7 +121,9 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> { fn takes_value(&self) -> bool { true } fn help(&self) -> Option<&'e str> { self.b.help } fn default_val(&self) -> Option<&'e OsStr> { self.v.default_val } - fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { self.v.default_vals_ifs.as_ref().map(|vm| vm.values()) } + fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { + self.v.default_vals_ifs.as_ref().map(|vm| vm.values()) + } fn longest_filter(&self) -> bool { true } fn aliases(&self) -> Option> { if let Some(ref aliases) = self.s.aliases { diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index 3d2da818dd0..6d9bdf65313 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -48,7 +48,7 @@ impl<'n, 'e> PosBuilder<'n, 'e> { // If the arg is required, add all it's requirements to master required list if a.is_set(ArgSettings::Required) { if let Some(ref areqs) = a.b.requires { - for name in areqs.iter().filter(|&&(val,_)|val.is_none()).map(|&(_, name)| name) { + for name in areqs.iter().filter(|&&(val, _)| val.is_none()).map(|&(_, name)| name) { reqs.push(name); } } @@ -98,9 +98,10 @@ impl<'n, 'e> Display for PosBuilder<'n, 'e> { impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> { fn name(&self) -> &'n str { self.b.name } - fn id(&self) -> usize { self.b.id } fn overrides(&self) -> Option<&[&'e str]> { self.b.overrides.as_ref().map(|o| &o[..]) } - fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { self.b.requires.as_ref().map(|o| &o[..]) } + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { + self.b.requires.as_ref().map(|o| &o[..]) + } fn blacklist(&self) -> Option<&[&'e str]> { self.b.blacklist.as_ref().map(|o| &o[..]) } fn required_unless(&self) -> Option<&[&'e str]> { self.b.r_unless.as_ref().map(|o| &o[..]) } fn val_names(&self) -> Option<&VecMap<&'e str>> { self.v.val_names.as_ref() } @@ -123,7 +124,9 @@ impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> { fn val_delim(&self) -> Option { self.v.val_delim } fn takes_value(&self) -> bool { true } fn help(&self) -> Option<&'e str> { self.b.help } - fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { self.v.default_vals_ifs.as_ref().map(|vm| vm.values()) } + fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { + self.v.default_vals_ifs.as_ref().map(|vm| vm.values()) + } fn default_val(&self) -> Option<&'e OsStr> { self.v.default_val } fn longest_filter(&self) -> bool { true } fn aliases(&self) -> Option> { None } From 0efa4119632f134fc5b8b9695b007dd94b76735d Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 20 Feb 2017 23:51:20 -0500 Subject: [PATCH 2/9] 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. --- clap-test.rs | 7 +- src/app/macros.rs | 133 +---- src/app/parser.rs | 903 ++++++++++++++++--------------- src/args/arg.rs | 34 +- src/args/arg_builder/switched.rs | 4 +- src/args/arg_matcher.rs | 2 +- src/completions/bash.rs | 38 +- src/completions/powershell.rs | 9 +- src/macros.rs | 252 ++++++++- tests/completions.rs | 310 ++++++----- tests/help.rs | 117 ++-- tests/macros.rs | 5 +- tests/require.rs | 6 +- tests/unique_args.rs | 17 +- 14 files changed, 968 insertions(+), 869 deletions(-) diff --git a/clap-test.rs b/clap-test.rs index ebbf9ea0e0d..faa4b582fb4 100644 --- a/clap-test.rs +++ b/clap-test.rs @@ -9,11 +9,12 @@ mod test { fn compare(l: S, r: S2) -> bool where S: AsRef, - S2: AsRef { + S2: AsRef + { 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!(""); diff --git a/src/app/macros.rs b/src/app/macros.rs index 12af2e1973a..7224fa42479 100644 --- a/src/app/macros.rs +++ b/src/app/macros.rs @@ -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)) - }) - } -} diff --git a/src/app/parser.rs b/src/app/parser.rs index 297ea44ffdc..3b8d18803cc 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -1,5 +1,5 @@ // Std -use std::collections::{BTreeMap, HashMap, VecDeque}; +use std::collections::{BTreeMap, VecDeque}; use std::ffi::{OsStr, OsString}; use std::fmt::Display; use std::fs::File; @@ -20,9 +20,9 @@ use SubCommand; use app::App; use app::help::Help; use app::meta::AppMeta; -use app::settings::{AppFlags, AppSettings}; -use args::{AnyArg, ArgMatcher, Base, Switched, Arg, ArgGroup, FlagBuilder, OptBuilder, PosBuilder}; -use args::MatchedArg; +use app::settings::AppFlags; +use args::{AnyArg, ArgMatcher, Base, Switched, Arg, ArgGroup, FlagBuilder, OptBuilder, PosBuilder, + MatchedArg}; use args::settings::ArgSettings; use completions::ComplGen; use errors::{Error, ErrorKind}; @@ -31,7 +31,7 @@ use fmt::{Colorizer, ColorWhen}; use osstringext::OsStrExt2; use completions::Shell; use suggestions; -use app::settings::AppSettings::*; +use app::settings::AppSettings as AS; #[allow(missing_debug_implementations)] #[doc(hidden)] @@ -46,14 +46,12 @@ pub struct Parser<'a, 'b> pub opts: Vec>, pub positionals: VecMap>, pub subcommands: Vec>, - groups: HashMap<&'a str, ArgGroup<'a>>, + groups: Vec>, pub global_args: Vec>, required: Vec<&'a str>, r_ifs: Vec<(&'a str, &'b str, &'a str)>, blacklist: Vec<&'b str>, overrides: Vec<&'b str>, - pub short_list: Vec, - pub long_list: Vec<&'b str>, help_short: Option, version_short: Option, } @@ -66,24 +64,28 @@ impl<'a, 'b> Parser<'a, 'b> } pub fn help_short(&mut self, s: &str) { - self.help_short = s.trim_left_matches(|c| c == '-') + let c = s.trim_left_matches(|c| c == '-') .chars() - .nth(0); + .nth(0) + .unwrap_or('h'); + self.help_short = Some(c); } pub fn version_short(&mut self, s: &str) { - self.version_short = s.trim_left_matches(|c| c == '-') + let c = s.trim_left_matches(|c| c == '-') .chars() - .nth(0); + .nth(0) + .unwrap_or('V'); + self.version_short = Some(c); } pub fn gen_completions_to(&mut self, for_shell: Shell, buf: &mut W) { - if !self.is_set(Propogated) { + if !self.is_set(AS::Propogated) { self.propogate_help_version(); self.build_bin_names(); self.propogate_globals(); self.propogate_settings(); - self.set(Propogated); + self.set(AS::Propogated); } ComplGen::new(self).generate(for_shell, buf) @@ -110,10 +112,18 @@ impl<'a, 'b> Parser<'a, 'b> // actually adds the arguments pub fn add_arg(&mut self, a: &Arg<'a, 'b>) { - debug_assert!(!(self.flags.iter().any(|f| &f.b.name == &a.b.name) || - self.opts.iter().any(|o| o.b.name == a.b.name) || - self.positionals.values().any(|p| p.b.name == a.b.name)), + debug_assert!(!arg_names!(self).any(|name| name == a.b.name), format!("Non-unique argument name: {} is already in use", a.b.name)); + if let Some(l) = a.s.long { + debug_assert!(!self.contains_long(l), + format!("Argument long must be unique\n\n\t--{} is already in use", + l)); + } + if let Some(s) = a.s.short { + debug_assert!(!self.contains_short(s), + format!("Argument short must be unique\n\n\t-{} is already in use", + s)); + } if let Some(ref r_ifs) = a.r_ifs { for &(arg, val) in r_ifs { self.r_ifs.push((arg, val, a.b.name)); @@ -121,25 +131,16 @@ impl<'a, 'b> Parser<'a, 'b> } if let Some(ref grps) = a.b.groups { for g in grps { - let ag = self.groups.entry(g).or_insert_with(|| ArgGroup::with_name(g)); - ag.args.push(a.b.name); - } - } - if let Some(s) = a.s.short { - debug_assert!(!self.short_list.contains(&s), - format!("Argument short must be unique\n\n\t-{} is already in use", - s)); - self.short_list.push(s); - } - if let Some(l) = a.s.long { - debug_assert!(!self.long_list.contains(&l), - format!("Argument long must be unique\n\n\t--{} is already in use", - l)); - self.long_list.push(l); - if l == "help" { - self.unset(NeedsLongHelp); - } else if l == "version" { - self.unset(NeedsLongVersion); + let mut found = false; + if let Some(ref mut ag) = self.groups.iter_mut().find(|grp| &grp.name == g) { + ag.args.push(a.b.name); + found = true; + } + if !found { + let mut ag = ArgGroup::with_name(g); + ag.args.push(a.b.name); + self.groups.push(ag); + } } } if a.is_set(ArgSettings::Required) { @@ -159,15 +160,25 @@ impl<'a, 'b> Parser<'a, 'b> let pb = PosBuilder::from_arg(a, i as u64, &mut self.required); self.positionals.insert(i, pb); } else if a.is_set(ArgSettings::TakesValue) { + // TODO: use uniquemap + // if let Some(ref als) = a.s.aliases { + // for &(a, _) in als.iter() { + // self.opt_l.push(a); + // } + // } let mut ob = OptBuilder::from_arg(a, &mut self.required); - let id = self.opts.len(); ob.s.unified_ord = self.flags.len() + self.opts.len(); - self.opts.insert(id, ob); + self.opts.push(ob); } else { + // TODO: use uniquemap + // if let Some(ref als) = a.s.aliases { + // for &(a, _) in als.iter() { + // self.flag_l.push(a); + // } + // } let mut fb = FlagBuilder::from(a); - let id = self.flags.len(); fb.s.unified_ord = self.flags.len() + self.opts.len(); - self.flags.insert(id, fb); + self.flags.push(fb); } if a.is_set(ArgSettings::Global) { debug_assert!(!a.is_set(ArgSettings::Required), @@ -180,7 +191,7 @@ impl<'a, 'b> Parser<'a, 'b> pub fn add_group(&mut self, group: ArgGroup<'a>) { if group.required { - self.required.push(group.name.into()); + self.required.push(group.name); if let Some(ref reqs) = group.requires { self.required.extend_from_slice(reqs); } @@ -188,16 +199,15 @@ impl<'a, 'b> Parser<'a, 'b> self.blacklist.extend_from_slice(bl); } } - let mut found = false; - if let Some(ref mut grp) = self.groups.get_mut(&group.name) { + if self.groups.iter().any(|g| g.name == group.name) { + let grp = + self.groups.iter_mut().find(|g| g.name == group.name).expect(INTERNAL_ERROR_MSG); grp.args.extend_from_slice(&group.args); grp.requires = group.requires.clone(); grp.conflicts = group.conflicts.clone(); grp.required = group.required; - found = true; - } - if !found { - self.groups.insert(group.name.into(), group); + } else { + self.groups.push(group); } } @@ -207,7 +217,7 @@ impl<'a, 'b> Parser<'a, 'b> subcmd.p.meta.name); subcmd.p.meta.term_w = self.meta.term_w; if subcmd.p.meta.name == "help" { - self.unset(NeedsSubcommandHelp); + self.unset(AS::NeedsSubcommandHelp); } self.subcommands.push(subcmd); @@ -225,14 +235,14 @@ impl<'a, 'b> Parser<'a, 'b> // We have to create a new scope in order to tell rustc the borrow of `sc` is // done and to recursively call this method { - let vsc = self.settings.is_set(VersionlessSubcommands); - let gv = self.settings.is_set(GlobalVersion); + let vsc = self.settings.is_set(AS::VersionlessSubcommands); + let gv = self.settings.is_set(AS::GlobalVersion); if vsc { - sc.p.set(DisableVersion); + sc.p.set(AS::DisableVersion); } if gv && sc.p.meta.version.is_none() && self.meta.version.is_some() { - sc.p.set(GlobalVersion); + sc.p.set(AS::GlobalVersion); sc.p.meta.version = Some(self.meta.version.unwrap()); } sc.p.settings = sc.p.settings | self.g_settings; @@ -244,8 +254,8 @@ impl<'a, 'b> Parser<'a, 'b> #[cfg_attr(feature = "lints", allow(needless_borrow))] pub fn derive_display_order(&mut self) { - if self.is_set(DeriveDisplayOrder) { - let unified = self.is_set(UnifiedHelpMessage); + if self.is_set(AS::DeriveDisplayOrder) { + let unified = self.is_set(AS::UnifiedHelpMessage); for (i, o) in self.opts .iter_mut() .enumerate() @@ -281,120 +291,120 @@ impl<'a, 'b> Parser<'a, 'b> debugln!("Parser::get_required_from: reqs={:?}, extra={:?}", reqs, extra); - let mut c_flags: Vec<&str> = vec![]; - let mut c_pos: Vec<&str> = vec![]; - let mut c_opt: Vec<&str> = vec![]; - let mut grps: Vec<&str> = vec![]; - macro_rules! categorize { - ($_self:ident, $name:ident, $c_flags:ident, $c_pos:ident, $c_opt:ident, $grps:ident) => { - if $_self.flags.iter().any(|f| &f.b.name == $name) { - $c_flags.push($name); - } else if self.opts.iter().any(|o| &o.b.name == $name) { - $c_opt.push($name); - } else if self.groups.contains_key($name) { - $grps.push($name); - } else { - $c_pos.push($name); + let mut desc_reqs: Vec<&str> = vec![]; + desc_reqs.extend(extra); + let mut new_reqs: Vec<&str> = vec![]; + macro_rules! get_requires { + (@group $a: ident, $v:ident, $p:ident) => {{ + if let Some(rl) = self.groups.iter().filter(|g| g.requires.is_some()).find(|g| &g.name == $a).map(|g| g.requires.as_ref().unwrap()) { + for r in rl { + if !$p.contains(&r) { + debugln!("Parser::get_required_from:iter:{}: adding group req={:?}", $a, r); + $v.push(r); + } + } } - } - }; - for name in reqs { - categorize!(self, name, c_flags, c_pos, c_opt, grps); - } - if let Some(ref name) = extra { - categorize!(self, name, c_flags, c_pos, c_opt, grps); - } - macro_rules! fill_vecs { - ($_self:ident { - $t1:ident => $v1:ident => $i1:ident, - $t2:ident => $v2:ident => $i2:ident, - $t3:ident => $v3:ident => $i3:ident, - $gv:ident, $tmp:ident - }) => { - for a in &$v1 { - if let Some(a) = self.$t1.$i1().filter(|arg| &arg.b.name == a).next() { - if let Some(ref rl) = a.b.requires { - for &(_, r) in rl.iter() { - if !reqs.contains(&r) { - if $_self.$t1.$i1().any(|t| &t.b.name == &r) { - $tmp.push(r); - } else if $_self.$t2.$i2().any(|t| &t.b.name == &r) { - $v2.push(r); - } else if $_self.$t3.$i3().any(|t| &t.b.name == &r) { - $v3.push(r); - } else if $_self.groups.contains_key(r) { - $gv.push(r); - } - } - } + }}; + ($a:ident, $what:ident, $how:ident, $v:ident, $p:ident) => {{ + if let Some(rl) = self.$what.$how().filter(|a| a.b.requires.is_some()).find(|arg| &arg.b.name == $a).map(|a| a.b.requires.as_ref().unwrap()) { + for &(_, r) in rl.iter() { + if !$p.contains(&r) { + debugln!("Parser::get_required_from:iter:{}: adding arg req={:?}",$a, r); + $v.push(r); } } } - $v1.extend(&$tmp); - }; - } - - let mut tmp = vec![]; - fill_vecs!(self { - flags => c_flags => iter, - opts => c_opt => iter, - positionals => c_pos => values, - grps, tmp - }); - tmp.clear(); - fill_vecs!(self { - opts => c_opt => iter, - flags => c_flags => iter, - positionals => c_pos => values, - grps, tmp - }); - tmp.clear(); - fill_vecs!(self { - positionals => c_pos => values, - opts => c_opt => iter, - flags => c_flags => iter, - grps, tmp - }); + }}; + } + // initialize new_reqs + for a in reqs { + get_requires!(a, flags, iter, new_reqs, reqs); + get_requires!(a, opts, iter, new_reqs, reqs); + get_requires!(a, positionals, values, new_reqs, reqs); + get_requires!(@group a, new_reqs, reqs); + } + desc_reqs.extend_from_slice(&*new_reqs); + debugln!("Parser::get_required_from: after init desc_reqs={:?}", + desc_reqs); + loop { + let mut tmp = vec![]; + for a in &new_reqs { + get_requires!(a, flags, iter, tmp, desc_reqs); + get_requires!(a, opts, iter, tmp, desc_reqs); + get_requires!(a, positionals, values, tmp, desc_reqs); + get_requires!(@group a, tmp, desc_reqs); + } + if tmp.is_empty() { + debugln!("Parser::get_required_from: no more children"); + break; + } else { + debugln!("Parser::get_required_from: after iter tmp={:?}", tmp); + debugln!("Parser::get_required_from: after iter new_reqs={:?}", + new_reqs); + desc_reqs.extend_from_slice(&*new_reqs); + new_reqs.clear(); + new_reqs.extend_from_slice(&*tmp); + debugln!("Parser::get_required_from: after iter desc_reqs={:?}", + desc_reqs); + } + } + desc_reqs.extend_from_slice(reqs); + desc_reqs.sort(); + desc_reqs.dedup(); + debugln!("Parser::get_required_from: final desc_reqs={:?}", desc_reqs); let mut ret_val = VecDeque::new(); - c_pos.dedup(); - c_flags.dedup(); - c_opt.dedup(); - grps.dedup(); - let args_in_groups = grps.iter() - .flat_map(|g| self.arg_names_in_group(g)) + let args_in_groups = self.groups + .iter() + .filter(|gn| desc_reqs.contains(&gn.name)) + .flat_map(|g| self.arg_names_in_group(&g.name)) .collect::>(); - let pmap = c_pos.into_iter() - .filter(|&p| matcher.is_none() || !matcher.as_ref().unwrap().contains(p)) - .filter_map(|p| self.positionals.values().find(|x| x.b.name == p)) - .filter(|p| !args_in_groups.contains(&p.b.name)) - .map(|p| (p.index, p)) - .collect::>(); // sort by index - debugln!("Parser::get_required_from: args_in_groups={:?}", args_in_groups); + let pmap = if let Some(ref m) = matcher { + desc_reqs.iter() + .filter(|a| self.positionals.values().any(|p| &&p.b.name == a)) + .filter(|&p| !m.contains(p)) + .filter_map(|p| self.positionals.values().find(|x| &x.b.name == p)) + .filter(|p| !args_in_groups.contains(&p.b.name)) + .map(|p| (p.index, p)) + .collect::>() // sort by index + } else { + desc_reqs.iter() + .filter(|a| self.positionals.values().any(|p| &&p.b.name == a)) + .filter_map(|p| self.positionals.values().find(|x| &x.b.name == p)) + .filter(|p| !args_in_groups.contains(&p.b.name)) + .map(|p| (p.index, p)) + .collect::>() // sort by index + }; + debugln!("Parser::get_required_from: args_in_groups={:?}", + args_in_groups); for &p in pmap.values() { let s = p.to_string(); if args_in_groups.is_empty() || !args_in_groups.contains(&&*s) { ret_val.push_back(s); } } - macro_rules! write_arg { - ($i:expr, $m:ident, $v:ident, $r:ident, $aig:ident) => { - for f in $v { - if $m.is_some() && $m.as_ref().unwrap().contains(f) || $aig.contains(&f) { - continue; - } - $r.push_back($i.filter(|flg| &flg.b.name == &f).next().unwrap().to_string()); - } - } + for a in desc_reqs.iter() + .filter(|name| !self.positionals.values().any(|p| &&p.b.name == name)) + .filter(|name| !self.groups.iter().any(|g| &&g.name == name)) + .filter(|name| !args_in_groups.contains(name)) + .filter(|name| !(matcher.is_some() && matcher.as_ref().unwrap().contains(name))) { + debugln!("Parser::get_required_from:iter:{}:", a); + let arg = find_by_name!(self, a, flags, iter) + .map(|f| f.to_string()) + .unwrap_or_else(|| { + find_by_name!(self, a, opts, iter) + .map(|o| o.to_string()) + .expect(INTERNAL_ERROR_MSG) + }); + ret_val.push_back(arg); } - write_arg!(self.flags.iter(), matcher, c_flags, ret_val, args_in_groups); - write_arg!(self.opts.iter(), matcher, c_opt, ret_val, args_in_groups); let mut g_vec = vec![]; - for g in grps { + for g in desc_reqs.iter().filter(|n| self.groups.iter().any(|g| &&g.name == n)) { let g_string = self.args_in_group(g) .join("|"); g_vec.push(format!("<{}>", &g_string[..g_string.len()])); } + g_vec.sort(); g_vec.dedup(); for g in g_vec { ret_val.push_back(g); @@ -408,23 +418,20 @@ impl<'a, 'b> Parser<'a, 'b> debugln!("Parser::get_args_tag;"); let mut count = 0; 'outer: for p in self.positionals.values().filter(|p| !p.is_set(ArgSettings::Required)) { - debugln!("Parser::get_args_tag:iter:iter: p={};", p.b.name); + debugln!("Parser::get_args_tag:iter:{}:", p.b.name); if let Some(g_vec) = self.groups_for_arg(p.b.name) { for grp_s in &g_vec { - debugln!("Parser::get_args_tag:iter:iter: grp_s={};", grp_s); - if let Some(grp) = self.groups.get(grp_s) { - debug!("Parser::get_args_tag:iter:iter: Is group required...{:?}", grp.required); - if grp.required { - // if it's part of a required group we don't want to count it - continue 'outer; - } + debugln!("Parser::get_args_tag:iter:{}:iter:{};", p.b.name, grp_s); + // if it's part of a required group we don't want to count it + if self.groups.iter().any(|g| g.required && (&g.name == grp_s)) { + continue 'outer; } } } count += 1; debugln!("Parser::get_args_tag:iter: {} Args not required", count); } - if !self.is_set(DontCollapseArgsInUsage) && (count > 1 || self.positionals.len() > 1) { + if !self.is_set(AS::DontCollapseArgsInUsage) && (count > 1 || self.positionals.len() > 1) { return None; // [ARGS] } else if count == 1 { let p = self.positionals @@ -432,7 +439,7 @@ impl<'a, 'b> Parser<'a, 'b> .find(|p| !p.is_set(ArgSettings::Required)) .expect(INTERNAL_ERROR_MSG); return Some(format!(" [{}]{}", p.name_no_brackets(), p.multiple_str())); - } else if self.is_set(DontCollapseArgsInUsage) && !self.positionals.is_empty() { + } else if self.is_set(AS::DontCollapseArgsInUsage) && !self.positionals.is_empty() { return Some(self.positionals .values() .filter(|p| !p.is_set(ArgSettings::Required)) @@ -457,11 +464,9 @@ impl<'a, 'b> Parser<'a, 'b> if let Some(g_vec) = self.groups_for_arg(f.b.name) { for grp_s in &g_vec { debugln!("Parser::needs_flags_tag:iter:iter: grp_s={};", grp_s); - if let Some(grp) = self.groups.get(grp_s) { - debug!("Parser::needs_flags_tag:iter:iter: Is group required...{:?}", grp.required); - if grp.required { - continue 'outer; - } + if self.groups.iter().any(|g| &g.name == grp_s && g.required) { + debug!("Parser::needs_flags_tag:iter:iter: Group is required"); + continue 'outer; } } } @@ -486,13 +491,13 @@ impl<'a, 'b> Parser<'a, 'b> pub fn has_subcommands(&self) -> bool { !self.subcommands.is_empty() } #[inline] - pub fn is_set(&self, s: AppSettings) -> bool { self.settings.is_set(s) } + pub fn is_set(&self, s: AS) -> bool { self.settings.is_set(s) } #[inline] - pub fn set(&mut self, s: AppSettings) { self.settings.set(s) } + pub fn set(&mut self, s: AS) { self.settings.set(s) } #[inline] - pub fn unset(&mut self, s: AppSettings) { self.settings.unset(s) } + pub fn unset(&mut self, s: AS) { self.settings.unset(s) } #[cfg_attr(feature = "lints", allow(block_in_if_condition_stmt))] pub fn verify_positionals(&mut self) { @@ -504,11 +509,11 @@ impl<'a, 'b> Parser<'a, 'b> // but no 2) if let Some((idx, p)) = self.positionals.iter().rev().next() { debug_assert!(!(idx != self.positionals.len()), - format!("Found positional argument \"{}\" who's index is {} but there are \ + format!("Found positional argument \"{}\" who's index is {} but there are \ only {} positional arguments defined", - p.b.name, - idx, - self.positionals.len())); + p.b.name, + idx, + self.positionals.len())); } // Next we verify that only the highest index has a .multiple(true) (if any) @@ -520,36 +525,37 @@ impl<'a, 'b> Parser<'a, 'b> }) { debug_assert!({ - let mut it = self.positionals.values().rev(); - // Either the final positional is required - it.next().unwrap().is_set(ArgSettings::Required) + let mut it = self.positionals.values().rev(); + // Either the final positional is required + it.next().unwrap().is_set(ArgSettings::Required) // Or the second to last has a terminator set || it.next().unwrap().v.terminator.is_some() - }, - "When using a positional argument with .multiple(true) that is *not the last* \ + }, + "When using a positional argument with .multiple(true) that is *not the last* \ positional argument, the last positional argument (i.e the one with the highest \ - index) *must* have .required(true) set." - ); + index) *must* have .required(true) set."); debug_assert!({ - let num = self.positionals.len() - 1; - self.positionals.get(num).unwrap().is_set(ArgSettings::Multiple) - }, - "Only the last positional argument, or second to last positional argument may be set to .multiple(true)"); + let num = self.positionals.len() - 1; + self.positionals.get(num).unwrap().is_set(ArgSettings::Multiple) + }, + "Only the last positional argument, or second to last positional argument may be set to .multiple(true)"); - self.set(LowIndexMultiplePositional); + self.set(AS::LowIndexMultiplePositional); } - debug_assert!(self.positionals.values() - .filter(|p| p.b.settings.is_set(ArgSettings::Multiple) - && p.v.num_vals.is_none()) - .map(|_| 1) - .sum::() <= 1, - "Only one positional argument with .multiple(true) set is allowed per command"); + debug_assert!(self.positionals + .values() + .filter(|p| { + p.b.settings.is_set(ArgSettings::Multiple) && p.v.num_vals.is_none() + }) + .map(|_| 1) + .sum::() <= 1, + "Only one positional argument with .multiple(true) set is allowed per command"); // If it's required we also need to ensure all previous positionals are // required too - if self.is_set(AllowMissingPositional) { + if self.is_set(AS::AllowMissingPositional) { let mut found = false; let mut foundx2 = false; for p in self.positionals.values().rev() { @@ -557,10 +563,10 @@ impl<'a, 'b> Parser<'a, 'b> // [arg1] is Ok // [arg1] Is not debug_assert!(p.b.settings.is_set(ArgSettings::Required), - "Found positional argument which is not required with a lower index \ + "Found positional argument which is not required with a lower index \ than a required positional argument by two or more: {:?} index {}", - p.b.name, - p.index); + p.b.name, + p.index); } else if p.b.settings.is_set(ArgSettings::Required) { if found { foundx2 = true; @@ -577,10 +583,10 @@ impl<'a, 'b> Parser<'a, 'b> for p in self.positionals.values().rev() { if found { debug_assert!(p.b.settings.is_set(ArgSettings::Required), - "Found positional argument which is not required with a lower index \ + "Found positional argument which is not required with a lower index \ than a required positional argument: {:?} index {}", - p.b.name, - p.index); + p.b.name, + p.index); } else if p.b.settings.is_set(ArgSettings::Required) { found = true; continue; @@ -606,7 +612,7 @@ impl<'a, 'b> Parser<'a, 'b> #[inline] fn possible_subcommand(&self, arg_os: &OsStr) -> bool { debugln!("Parser::possible_subcommand: arg={:?}", arg_os); - if self.is_set(ArgsNegateSubcommands) && self.is_set(ValidArgFound) { + if self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound) { return false; } self.subcommands @@ -674,9 +680,7 @@ impl<'a, 'b> Parser<'a, 'b> .unwrap_or(&self.meta.name), self.color())); } - bin_name = format!("{} {}", - bin_name, - &*sc.meta.name); + bin_name = format!("{} {}", bin_name, &*sc.meta.name); } sc.clone() }; @@ -699,13 +703,15 @@ impl<'a, 'b> Parser<'a, 'b> #[cfg_attr(feature = "lints", allow(wrong_self_convention))] #[inline] fn is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: Option<&'a str>) -> bool { - debugln!("Parser::is_new_arg: arg={:?}, Needs Val of={:?}", arg_os, needs_val_of); - let app_wide_settings = if self.is_set(AllowLeadingHyphen) { + debugln!("Parser::is_new_arg: arg={:?}, Needs Val of={:?}", + arg_os, + needs_val_of); + let app_wide_settings = if self.is_set(AS::AllowLeadingHyphen) { true - } else if self.is_set(AllowNegativeNumbers) { + } else if self.is_set(AS::AllowNegativeNumbers) { let a = arg_os.to_string_lossy(); if a.parse::().is_ok() || a.parse::().is_ok() { - self.set(ValidNegNumFound); + self.set(AS::ValidNegNumFound); true } else { false @@ -724,7 +730,8 @@ impl<'a, 'b> Parser<'a, 'b> } else { false }; - debugln!("Parser::is_new_arg: Does arg allow leading hyphen...{:?}", arg_allows_tac); + debugln!("Parser::is_new_arg: Does arg allow leading hyphen...{:?}", + arg_allows_tac); // Is this a new argument, or values from a previous option? debug!("Parser::is_new_arg: Starts new arg..."); @@ -771,17 +778,19 @@ impl<'a, 'b> Parser<'a, 'b> let mut pos_counter = 1; while let Some(arg) = it.next() { let arg_os = arg.into(); - debugln!("Parser::get_matches_with: Begin parsing '{:?}' ({:?})", arg_os, &*arg_os.as_bytes()); + debugln!("Parser::get_matches_with: Begin parsing '{:?}' ({:?})", + arg_os, + &*arg_os.as_bytes()); - self.unset(ValidNegNumFound); + self.unset(AS::ValidNegNumFound); // Is this a new argument, or values from a previous option? let starts_new_arg = self.is_new_arg(&arg_os, needs_val_of); // Has the user already passed '--'? Meaning only positional args follow - if !self.is_set(TrailingValues) { + if !self.is_set(AS::TrailingValues) { // Does the arg match a subcommand name, or any of it's aliases (if defined) if self.possible_subcommand(&arg_os) { - if &*arg_os == "help" && self.is_set(NeedsSubcommandHelp) { + if &*arg_os == "help" && self.is_set(AS::NeedsSubcommandHelp) { try!(self.parse_help_subcommand(it)); } subcmd_name = Some(arg_os.to_str().expect(INVALID_UTF8).to_owned()); @@ -803,12 +812,12 @@ impl<'a, 'b> Parser<'a, 'b> if arg_os.len_() == 2 { // The user has passed '--' which means only positional args follow no // matter what they start with - self.set(TrailingValues); + self.set(AS::TrailingValues); continue; } needs_val_of = try!(self.parse_long_arg(matcher, &arg_os)); - if !(needs_val_of.is_none() && self.is_set(AllowLeadingHyphen)) { + if !(needs_val_of.is_none() && self.is_set(AS::AllowLeadingHyphen)) { continue; } } else if arg_os.starts_with(b"-") && arg_os.len_() != 1 { @@ -818,7 +827,7 @@ impl<'a, 'b> Parser<'a, 'b> needs_val_of = try!(self.parse_short_arg(matcher, &arg_os)); // If it's None, we then check if one of those two AppSettings was set if needs_val_of.is_none() { - if self.is_set(AllowNegativeNumbers) { + if self.is_set(AS::AllowNegativeNumbers) { if !(arg_os.to_string_lossy().parse::().is_ok() || arg_os.to_string_lossy().parse::().is_ok()) { return Err(Error::unknown_argument(&*arg_os.to_string_lossy(), @@ -826,7 +835,7 @@ impl<'a, 'b> Parser<'a, 'b> &*self.create_current_usage(matcher, None), self.color())); } - } else if !self.is_set(AllowLeadingHyphen) { + } else if !self.is_set(AS::AllowLeadingHyphen) { continue; } } else { @@ -835,7 +844,7 @@ impl<'a, 'b> Parser<'a, 'b> } } - if !self.is_set(ArgsNegateSubcommands) { + if !self.is_set(AS::ArgsNegateSubcommands) { if let Some(cdate) = suggestions::did_you_mean(&*arg_os.to_string_lossy(), self.subcommands .iter() @@ -854,13 +863,16 @@ impl<'a, 'b> Parser<'a, 'b> } } - let low_index_mults = self.is_set(LowIndexMultiplePositional) && + let low_index_mults = self.is_set(AS::LowIndexMultiplePositional) && !self.positionals.is_empty() && pos_counter == (self.positionals.len() - 1); - let missing_pos = self.is_set(AllowMissingPositional) && !self.positionals.is_empty() && + let missing_pos = self.is_set(AS::AllowMissingPositional) && + !self.positionals.is_empty() && pos_counter == (self.positionals.len() - 1); - debugln!("Parser::get_matches_with: Low index multiples...{:?}", low_index_mults); - debugln!("Parser::get_matches_with: Positional counter...{}", pos_counter); + debugln!("Parser::get_matches_with: Low index multiples...{:?}", + low_index_mults); + debugln!("Parser::get_matches_with: Positional counter...{}", + pos_counter); if low_index_mults || missing_pos { if let Some(na) = it.peek() { let n = (*na).clone().into(); @@ -889,13 +901,13 @@ impl<'a, 'b> Parser<'a, 'b> } if let Some(p) = self.positionals.get(pos_counter) { parse_positional!(self, p, arg_os, pos_counter, matcher); - self.settings.set(ValidArgFound); - } else if self.is_set(AllowExternalSubcommands) { + self.settings.set(AS::ValidArgFound); + } else if self.is_set(AS::AllowExternalSubcommands) { // Get external subcommand name let sc_name = match arg_os.to_str() { Some(s) => s.to_string(), None => { - if !self.is_set(StrictUtf8) { + if !self.is_set(AS::StrictUtf8) { return Err(Error::invalid_utf8(&*self.create_current_usage(matcher, None), self.color())); @@ -908,7 +920,7 @@ impl<'a, 'b> Parser<'a, 'b> let mut sc_m = ArgMatcher::new(); while let Some(v) = it.next() { let a = v.into(); - if a.to_str().is_none() && !self.is_set(StrictUtf8) { + if a.to_str().is_none() && !self.is_set(AS::StrictUtf8) { return Err(Error::invalid_utf8(&*self.create_current_usage(matcher, None), self.color())); } @@ -919,7 +931,8 @@ impl<'a, 'b> Parser<'a, 'b> name: sc_name, matches: sc_m.into(), }); - } else if !(self.is_set(AllowLeadingHyphen) || self.is_set(AllowNegativeNumbers)) { + } else if !(self.is_set(AS::AllowLeadingHyphen) || + self.is_set(AS::AllowNegativeNumbers)) { return Err(Error::unknown_argument(&*arg_os.to_string_lossy(), "", &*self.create_current_usage(matcher, None), @@ -949,12 +962,12 @@ impl<'a, 'b> Parser<'a, 'b> .expect(INTERNAL_ERROR_MSG); try!(self.parse_subcommand(sc_name, matcher, it)); } - } else if self.is_set(SubcommandRequired) { + } else if self.is_set(AS::SubcommandRequired) { let bn = self.meta.bin_name.as_ref().unwrap_or(&self.meta.name); return Err(Error::missing_subcommand(bn, &self.create_current_usage(matcher, None), self.color())); - } else if self.is_set(SubcommandRequiredElseHelp) { + } else if self.is_set(AS::SubcommandRequiredElseHelp) { debugln!("parser::get_matches_with: SubcommandRequiredElseHelp=true"); let mut out = vec![]; try!(self.write_help_err(&mut out)); @@ -995,14 +1008,14 @@ impl<'a, 'b> Parser<'a, 'b> } try!(self.validate_blacklist(matcher)); - if !(self.is_set(SubcommandsNegateReqs) && subcmd_name.is_some()) && !reqs_validated { + if !(self.is_set(AS::SubcommandsNegateReqs) && subcmd_name.is_some()) && !reqs_validated { try!(self.validate_required(matcher)); } try!(self.validate_matched_args(matcher)); matcher.usage(self.create_usage(&[])); if matcher.is_empty() && matcher.subcommand_name().is_none() && - self.is_set(ArgRequiredElseHelp) { + self.is_set(AS::ArgRequiredElseHelp) { let mut out = vec![]; try!(self.write_help_err(&mut out)); return Err(Error { @@ -1029,22 +1042,25 @@ impl<'a, 'b> Parser<'a, 'b> if sc.p.meta.bin_name.is_none() { sdebugln!("No"); let bin_name = format!("{}{}{}", - self.meta - .bin_name - .as_ref() - .unwrap_or(&self.meta.name.clone()), - if self.meta.bin_name.is_some() { - " " - } else { - "" - }, - &*sc.p.meta.name); - debugln!("Parser::build_bin_names:iter: Setting bin_name of {} to {}", self.meta.name, bin_name); + self.meta + .bin_name + .as_ref() + .unwrap_or(&self.meta.name.clone()), + if self.meta.bin_name.is_some() { + " " + } else { + "" + }, + &*sc.p.meta.name); + debugln!("Parser::build_bin_names:iter: Setting bin_name of {} to {}", + self.meta.name, + bin_name); sc.p.meta.bin_name = Some(bin_name); } else { sdebugln!("yes ({:?})", sc.p.meta.bin_name); } - debugln!("Parser::build_bin_names:iter: Calling build_bin_names from...{}", sc.p.meta.name); + debugln!("Parser::build_bin_names:iter: Calling build_bin_names from...{}", + sc.p.meta.name); sc.p.build_bin_names(); } } @@ -1060,7 +1076,7 @@ impl<'a, 'b> Parser<'a, 'b> use std::fmt::Write; debugln!("Parser::parse_subcommand;"); let mut mid_string = String::new(); - if !self.is_set(SubcommandsNegateReqs) { + if !self.is_set(AS::SubcommandsNegateReqs) { let mut hs: Vec<&str> = self.required.iter().map(|n| &**n).collect(); for k in matcher.arg_names() { hs.push(k); @@ -1097,7 +1113,8 @@ impl<'a, 'b> Parser<'a, 'b> "" }, &*sc.p.meta.name)); - debugln!("Parser::parse_subcommand: About to parse sc={}", sc.p.meta.name); + debugln!("Parser::parse_subcommand: About to parse sc={}", + sc.p.meta.name); debugln!("Parser::parse_subcommand: sc settings={:#?}", sc.p.settings); try!(sc.p.get_matches_with(&mut sc_matcher, it)); matcher.subcommand(SubCommand { @@ -1118,11 +1135,11 @@ impl<'a, 'b> Parser<'a, 'b> } let mut res = vec![]; debugln!("Parser::groups_for_arg: Searching through groups..."); - for (gn, grp) in &self.groups { + for grp in &self.groups { for a in &grp.args { if a == &name { - sdebugln!("\tFound '{}'", gn); - res.push(*gn); + sdebugln!("\tFound '{}'", grp.name); + res.push(&*grp.name); } } } @@ -1137,17 +1154,17 @@ impl<'a, 'b> Parser<'a, 'b> let mut g_vec = vec![]; let mut args = vec![]; - for n in &self.groups[group].args { + for n in &self.groups.iter().find(|g| g.name == group).expect(INTERNAL_ERROR_MSG).args { if let Some(f) = self.flags.iter().find(|f| &f.b.name == n) { args.push(f.to_string()); } else if let Some(f) = self.opts.iter().find(|o| &o.b.name == n) { args.push(f.to_string()); - } else if self.groups.contains_key(&**n) { - g_vec.push(*n); } else if let Some(p) = self.positionals .values() .find(|p| &p.b.name == n) { args.push(p.b.name.to_owned()); + } else { + g_vec.push(*n); } } @@ -1162,17 +1179,17 @@ impl<'a, 'b> Parser<'a, 'b> let mut g_vec = vec![]; let mut args = vec![]; - for n in &self.groups[group].args { - if self.groups.contains_key(&*n) { + for n in &self.groups.iter().find(|g| g.name == group).expect(INTERNAL_ERROR_MSG).args { + if self.groups.iter().any(|g| &g.name == &*n) { + args.extend(self.arg_names_in_group(&*n)); g_vec.push(*n); } else { args.push(*n); } } - for av in g_vec.iter().map(|g| self.arg_names_in_group(g)) { - args.extend(av); - } + // TODO: faster way to sort/dedup? + args.sort(); args.dedup(); args.iter().map(|s| *s).collect() } @@ -1180,12 +1197,11 @@ impl<'a, 'b> Parser<'a, 'b> pub fn create_help_and_version(&mut self) { debugln!("Parser::create_help_and_version;"); // name is "hclap_help" because flags are sorted by name - if self.is_set(NeedsLongHelp) { + if !self.contains_long("help") { debugln!("Parser::create_help_and_version: Building --help"); - if self.help_short.is_none() && !self.short_list.contains(&'h') { + if self.help_short.is_none() && !self.contains_short('h') { self.help_short = Some('h'); } - let id = self.flags.len(); let arg = FlagBuilder { b: Base { name: "hclap_help", @@ -1198,18 +1214,13 @@ impl<'a, 'b> Parser<'a, 'b> ..Default::default() }, }; - if let Some(h) = self.help_short { - self.short_list.push(h); - } - self.long_list.push("help"); - self.flags.insert(id, arg); + self.flags.push(arg); } - if !self.is_set(DisableVersion) && self.is_set(NeedsLongVersion) { + if !self.is_set(AS::DisableVersion) && !self.contains_long("version") { debugln!("Parser::create_help_and_version: Building --version"); - if self.version_short.is_none() && !self.short_list.contains(&'V') { + if self.version_short.is_none() && !self.contains_short('V') { self.version_short = Some('V'); } - let id = self.flags.len(); // name is "vclap_version" because flags are sorted by name let arg = FlagBuilder { b: Base { @@ -1223,14 +1234,10 @@ impl<'a, 'b> Parser<'a, 'b> ..Default::default() }, }; - if let Some(v) = self.version_short { - self.short_list.push(v); - } - self.long_list.push("version"); - self.flags.insert(id, arg); + self.flags.push(arg); } - if !self.subcommands.is_empty() && !self.is_set(DisableHelpSubcommand) && - self.is_set(NeedsSubcommandHelp) { + if !self.subcommands.is_empty() && !self.is_set(AS::DisableHelpSubcommand) && + self.is_set(AS::NeedsSubcommandHelp) { debugln!("Parser::create_help_and_version: Building help"); self.subcommands .push(App::new("help") @@ -1264,11 +1271,11 @@ impl<'a, 'b> Parser<'a, 'b> debugln!("Parser::check_for_help_and_version_str;"); debug!("Parser::check_for_help_and_version_str: Checking if --{} is help or version...", arg.to_str().unwrap()); - if arg == "help" && self.is_set(NeedsLongHelp) { + if arg == "help" && self.is_set(AS::NeedsLongHelp) { sdebugln!("Help"); try!(self._help()); } - if arg == "version" && self.is_set(NeedsLongVersion) { + if arg == "version" && self.is_set(AS::NeedsLongVersion) { sdebugln!("Version"); try!(self._version()); } @@ -1279,15 +1286,16 @@ impl<'a, 'b> Parser<'a, 'b> fn check_for_help_and_version_char(&self, arg: char) -> ClapResult<()> { debugln!("Parser::check_for_help_and_version_char;"); - debug!("Parser::check_for_help_and_version_char: Checking if -{} is help or version...", arg); + debug!("Parser::check_for_help_and_version_char: Checking if -{} is help or version...", + arg); if let Some(h) = self.help_short { - if arg == h && self.is_set(NeedsLongHelp) { + if arg == h && self.is_set(AS::NeedsLongHelp) { sdebugln!("Help"); try!(self._help()); } } if let Some(v) = self.version_short { - if arg == v && self.is_set(NeedsLongVersion) { + if arg == v && self.is_set(AS::NeedsLongVersion) { sdebugln!("Version"); try!(self._version()); } @@ -1335,16 +1343,18 @@ impl<'a, 'b> Parser<'a, 'b> full_arg.trim_left_matches(b'-') }; - if let Some(opt) = find_by_long!(self, &arg, opts) { - debugln!("Parser::parse_long_arg: Found valid opt '{}'", opt.to_string()); - self.settings.set(ValidArgFound); + if let Some(opt) = find_opt_by_long!(@os self, &arg) { + debugln!("Parser::parse_long_arg: Found valid opt '{}'", + opt.to_string()); + self.settings.set(AS::ValidArgFound); let ret = try!(self.parse_opt(val, opt, val.is_some(), matcher)); arg_post_processing!(self, opt, matcher); return Ok(ret); - } else if let Some(flag) = find_by_long!(self, &arg, flags) { - debugln!("Parser::parse_long_arg: Found valid flag '{}'", flag.to_string()); - self.settings.set(ValidArgFound); + } else if let Some(flag) = find_flag_by_long!(@os self, &arg) { + debugln!("Parser::parse_long_arg: Found valid flag '{}'", + flag.to_string()); + self.settings.set(AS::ValidArgFound); // Only flags could be help or version, and we need to check the raw long // so this is the first point to check try!(self.check_for_help_and_version_str(arg)); @@ -1355,9 +1365,9 @@ impl<'a, 'b> Parser<'a, 'b> arg_post_processing!(self, flag, matcher); return Ok(None); - } else if self.is_set(AllowLeadingHyphen) { + } else if self.is_set(AS::AllowLeadingHyphen) { return Ok(None); - } else if self.is_set(ValidNegNumFound) { + } else if self.is_set(AS::ValidNegNumFound) { return Ok(None); } @@ -1370,21 +1380,18 @@ impl<'a, 'b> Parser<'a, 'b> full_arg: &OsStr) -> ClapResult> { debugln!("Parser::parse_short_arg;"); - // let mut utf8 = true; let arg_os = full_arg.trim_left_matches(b'-'); let arg = arg_os.to_string_lossy(); - // If AllowLeadingHyphen is set, we want to ensure `-val` gets parsed as `-val` and not `-v` `-a` `-l` assuming - // `v` `a` and `l` are all, or mostly, valid shorts. - if self.is_set(AllowLeadingHyphen) { - let mut good = true; - for c in arg.chars() { - good = self.short_list.contains(&c); - } - if !good { + // If AllowLeadingHyphen is set, we want to ensure `-val` gets parsed as `-val` and not + // `-v` `-a` `-l` assuming `v` `a` and `l` are all, or mostly, valid shorts. + if self.is_set(AS::AllowLeadingHyphen) { + if arg.chars().any(|c| !self.contains_short(c)) { + debugln!("Parser::parse_short_arg: Leading Hyphen Allowed and -{} isn't a valid short", + arg); return Ok(None); } - } else if self.is_set(ValidNegNumFound) { + } else if self.is_set(AS::ValidNegNumFound) { // TODO: Add docs about having AllowNegativeNumbers and `-2` as a valid short // May be better to move this to *after* not finding a valid flag/opt? debugln!("Parser::parse_short_arg: Valid negative num..."); @@ -1397,18 +1404,20 @@ impl<'a, 'b> Parser<'a, 'b> // concatenated value: -oval // Option: -o // Value: val - if let Some(opt) = self.opts - .iter() - .find(|&o| o.s.short.is_some() && o.s.short.unwrap() == c) { - debugln!("Parser::parse_short_arg:iter: Found valid short opt -{} in '{}'", c, arg); - self.settings.set(ValidArgFound); + if let Some(opt) = find_opt_by_short!(self, c) { + debugln!("Parser::parse_short_arg:iter: Found valid short opt -{} in '{}'", + c, + arg); + self.settings.set(AS::ValidArgFound); // Check for trailing concatenated value let p: Vec<_> = arg.splitn(2, c).collect(); debugln!("Parser::parse_short_arg:iter: arg: {:?}, arg_os: {:?}, full_arg: {:?}", arg, arg_os, full_arg); - debugln!("Parser::parse_short_arg:iter: p[0]: {:?}, p[1]: {:?}", p[0].as_bytes(), p[1].as_bytes()); + debugln!("Parser::parse_short_arg:iter: p[0]: {:?}, p[1]: {:?}", + p[0].as_bytes(), + p[1].as_bytes()); let i = p[0].as_bytes().len() + 1; let val = if p[1].as_bytes().len() > 0 { debugln!("Parser::parse_short_arg:iter: setting val: {:?} (bytes), {:?} (ascii)", @@ -1425,11 +1434,10 @@ impl<'a, 'b> Parser<'a, 'b> arg_post_processing!(self, opt, matcher); return Ok(ret); - } else if let Some(flag) = self.flags - .iter() - .find(|&f| f.s.short.is_some() && f.s.short.unwrap() == c) { - debugln!("Parser::parse_short_arg:iter: Found valid short flag -{}", c); - self.settings.set(ValidArgFound); + } else if let Some(flag) = find_flag_by_short!(self, c) { + debugln!("Parser::parse_short_arg:iter: Found valid short flag -{}", + c); + self.settings.set(AS::ValidArgFound); // Only flags can be help or version try!(self.check_for_help_and_version_char(c)); try!(self.parse_flag(flag, matcher)); @@ -1437,9 +1445,7 @@ impl<'a, 'b> Parser<'a, 'b> // Must be called here due to mutablilty arg_post_processing!(self, flag, matcher); } else { - let mut arg = String::new(); - arg.push('-'); - arg.push(c); + let arg = format!("-{}", c); return Err(Error::unknown_argument(&*arg, "", &*self.create_current_usage(matcher, None), @@ -1457,7 +1463,6 @@ impl<'a, 'b> Parser<'a, 'b> -> ClapResult> { debugln!("Parser::parse_opt; opt={}, val={:?}", opt.b.name, val); debugln!("Parser::parse_opt; opt.settings={:?}", opt.b.settings); - validate_multiples!(self, opt, matcher); let mut has_eq = false; debug!("Parser::parse_opt; Checking for val..."); @@ -1472,7 +1477,9 @@ impl<'a, 'b> Parser<'a, 'b> self.color())); } sdebugln!("Found - {:?}, len: {}", v, v.len_()); - debugln!("Parser::parse_opt: {:?} contains '='...{:?}", fv, fv.starts_with(&[b'='])); + debugln!("Parser::parse_opt: {:?} contains '='...{:?}", + fv, + fv.starts_with(&[b'='])); try!(self.add_val_to_arg(opt, v, matcher)); } else if opt.is_set(ArgSettings::RequireEquals) && !opt.is_set(ArgSettings::EmptyValues) { sdebugln!("None, but requires equals...Error"); @@ -1509,8 +1516,9 @@ impl<'a, 'b> Parser<'a, 'b> debugln!("Parser::add_val_to_arg; arg={}, val={:?}", arg.name(), val); let mut ret = None; debugln!("Parser::add_val_to_arg; trailing_vals={:?}, DontDelimTrailingVals={:?}", - self.is_set(TrailingValues), self.is_set(DontDelimitTrailingValues)); - if !(self.is_set(TrailingValues) && self.is_set(DontDelimitTrailingValues)) { + self.is_set(AS::TrailingValues), + self.is_set(AS::DontDelimitTrailingValues)); + if !(self.is_set(AS::TrailingValues) && self.is_set(AS::DontDelimitTrailingValues)) { if let Some(delim) = arg.val_delim() { if val.is_empty_() { ret = try!(self.add_single_val_to_arg(arg, val, matcher)); @@ -1556,55 +1564,67 @@ impl<'a, 'b> Parser<'a, 'b> } } - // The validation must come AFTER inserting into 'matcher' or the usage string - // can't be built - self.validate_value(arg, v, matcher) + if matcher.needs_more_vals(arg) { + return Ok(Some(arg.name())); + } + Ok(None) } - fn validate_value(&self, - arg: &A, - val: &OsStr, - matcher: &ArgMatcher<'a>) - -> ClapResult> + fn validate_values(&self, + arg: &A, + ma: &MatchedArg, + matcher: &ArgMatcher<'a>) + -> ClapResult<()> where A: AnyArg<'a, 'b> + Display { - debugln!("Parser::validate_value: val={:?}", val); - if self.is_set(StrictUtf8) && val.to_str().is_none() { - return Err(Error::invalid_utf8(&*self.create_current_usage(matcher, None), - self.color())); - } - if let Some(p_vals) = arg.possible_vals() { - let val_str = val.to_string_lossy(); - if !p_vals.contains(&&*val_str) { - return Err(Error::invalid_value(val_str, - p_vals, - arg, - &*self.create_current_usage(matcher, None), - self.color())); - } - } - if !arg.is_set(ArgSettings::EmptyValues) && val.is_empty_() && - matcher.contains(&*arg.name()) { - return Err(Error::empty_value(arg, - &*self.create_current_usage(matcher, None), - self.color())); - } - if let Some(vtor) = arg.validator() { - if let Err(e) = vtor(val.to_string_lossy().into_owned()) { - return Err(Error::value_validation(Some(arg), e, self.color())); + debugln!("Parser::validate_values: arg={:?}", arg.name()); + for (_, val) in &ma.vals { + if self.is_set(AS::StrictUtf8) && val.to_str().is_none() { + debugln!("Parser::validate_values: invalid UTF-8 found in val {:?}", + val); + return Err(Error::invalid_utf8(&*self.create_current_usage(matcher, None), + self.color())); + } + if let Some(p_vals) = arg.possible_vals() { + debugln!("Parser::validate_values: possible_vals={:?}", p_vals); + let val_str = val.to_string_lossy(); + if !p_vals.contains(&&*val_str) { + return Err(Error::invalid_value(val_str, + p_vals, + arg, + &*self.create_current_usage(matcher, None), + self.color())); + } } - } - if let Some(vtor) = arg.validator_os() { - if let Err(e) = vtor(val) { - return Err(Error::value_validation(Some(arg), - (*e).to_string_lossy().to_string(), - self.color())); + if !arg.is_set(ArgSettings::EmptyValues) && val.is_empty_() && + matcher.contains(&*arg.name()) { + debugln!("Parser::validate_values: illegal empty val found"); + return Err(Error::empty_value(arg, + &*self.create_current_usage(matcher, None), + self.color())); + } + if let Some(vtor) = arg.validator() { + debug!("Parser::validate_values: checking validator..."); + if let Err(e) = vtor(val.to_string_lossy().into_owned()) { + sdebugln!("error"); + return Err(Error::value_validation(Some(arg), e, self.color())); + } else { + sdebugln!("good"); + } + } + if let Some(vtor) = arg.validator_os() { + debug!("Parser::validate_values: checking validator_os..."); + if let Err(e) = vtor(&val) { + sdebugln!("error"); + return Err(Error::value_validation(Some(arg), + (*e).to_string_lossy().to_string(), + self.color())); + } else { + sdebugln!("good"); + } } } - if matcher.needs_more_vals(arg) { - return Ok(Some(arg.name())); - } - Ok(None) + Ok(()) } fn parse_flag(&self, @@ -1612,7 +1632,6 @@ impl<'a, 'b> Parser<'a, 'b> matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { debugln!("Parser::parse_flag;"); - validate_multiples!(self, flag, matcher); matcher.inc_occurrence_of(flag.b.name); // Increment or create the group "args" @@ -1655,11 +1674,13 @@ impl<'a, 'b> Parser<'a, 'b> } for name in &self.blacklist { - debugln!("Parser::validate_blacklist:iter: Checking blacklisted name: {}", name); - if self.groups.contains_key(name) { + debugln!("Parser::validate_blacklist:iter: Checking blacklisted name: {}", + name); + if self.groups.iter().any(|g| &g.name == name) { debugln!("Parser::validate_blacklist:iter: groups contains it..."); for n in self.arg_names_in_group(name) { - debugln!("Parser::validate_blacklist:iter:iter: Checking arg '{}' in group...", n); + debugln!("Parser::validate_blacklist:iter:iter: Checking arg '{}' in group...", + n); if matcher.contains(n) { debugln!("Parser::validate_blacklist:iter:iter: matcher contains it..."); return Err(build_err!(self, &n, matcher)); @@ -1676,17 +1697,24 @@ impl<'a, 'b> Parser<'a, 'b> fn validate_matched_args(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { debugln!("Parser::validate_matched_args;"); for (name, ma) in matcher.iter() { - debugln!("Parser::validate_matched_args:iter: name={}", name); + debugln!("Parser::validate_matched_args:iter:{}: vals={:#?}", + name, + ma.vals); if let Some(opt) = find_by_name!(self, name, opts, iter) { try!(self.validate_arg_num_vals(opt, ma, matcher)); + try!(self.validate_values(opt, ma, matcher)); try!(self.validate_arg_requires(opt, ma, matcher)); + try!(self.validate_arg_num_occurs(opt, ma, matcher)); } else if let Some(flag) = find_by_name!(self, name, flags, iter) { - // Only requires try!(self.validate_arg_requires(flag, ma, matcher)); + try!(self.validate_arg_num_occurs(flag, ma, matcher)); } else if let Some(pos) = find_by_name!(self, name, positionals, values) { try!(self.validate_arg_num_vals(pos, ma, matcher)); + try!(self.validate_arg_num_occurs(pos, ma, matcher)); + try!(self.validate_values(pos, ma, matcher)); try!(self.validate_arg_requires(pos, ma, matcher)); - } else if let Some(grp) = self.groups.get(name) { + } else { + let grp = self.groups.iter().find(|g| &g.name == name).expect(INTERNAL_ERROR_MSG); if let Some(ref g_reqs) = grp.requires { if g_reqs.iter().any(|&n| !matcher.contains(n)) { return self.missing_required_error(matcher, None); @@ -1697,6 +1725,23 @@ impl<'a, 'b> Parser<'a, 'b> Ok(()) } + fn validate_arg_num_occurs(&self, + a: &A, + ma: &MatchedArg, + matcher: &ArgMatcher) + -> ClapResult<()> + where A: AnyArg<'a, 'b> + Display + { + debugln!("Parser::validate_arg_num_occurs: a={};", a.name()); + if ma.occurs > 1 && !a.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(matcher, None), + self.color())); + } + Ok(()) + } + fn validate_arg_num_vals(&self, a: &A, ma: &MatchedArg, @@ -1818,20 +1863,10 @@ impl<'a, 'b> Parser<'a, 'b> fn validate_required(&self, matcher: &ArgMatcher) -> ClapResult<()> { debugln!("Parser::validate_required: required={:?};", self.required); 'outer: for name in &self.required { - debugln!("Parser::validate_required:iter: name={}", name); + debugln!("Parser::validate_required:iter:{}:", name); if matcher.contains(name) { continue 'outer; } - if let Some(grp) = self.groups.get(name) { - for arg in &grp.args { - if matcher.contains(arg) { - continue 'outer; - } - } - } - if self.groups.values().any(|g| g.args.contains(name)) { - continue 'outer; - } if let Some(a) = find_by_name!(self, name, flags, iter) { if self.is_missing_required_ok(a, matcher) { continue 'outer; @@ -1861,61 +1896,70 @@ impl<'a, 'b> Parser<'a, 'b> Ok(()) } - fn is_missing_required_ok(&self, a: &A, matcher: &ArgMatcher) -> bool + fn check_conflicts(&self, a: &A, matcher: &ArgMatcher) -> Option where A: AnyArg<'a, 'b> { - debugln!("Parser::is_missing_required_ok: a={}", a.name()); - if let Some(bl) = a.blacklist() { - debugln!("Parser::is_missing_required_ok: Conflicts found...{:?}", bl); - for n in bl.iter() { - debugln!("Parser::is_missing_required_ok:iter: conflict={}", n); - if matcher.contains(n) || - self.groups - .get(n) - .map_or(false, |g| g.args.iter().any(|an| matcher.contains(an))) { - return true; - } - } + debugln!("Parser::check_conflicts: a={:?};", a.name()); + a.blacklist().map(|bl| { + bl.iter().any(|conf| { + matcher.contains(conf) || + self.groups + .iter() + .find(|g| &g.name == conf) + .map_or(false, |g| g.args.iter().any(|arg| matcher.contains(arg))) + }) + }) + } + + fn check_required_unless(&self, a: &A, matcher: &ArgMatcher) -> Option + where A: AnyArg<'a, 'b> + { + debugln!("Parser::check_required_unless: a={:?};", a.name()); + macro_rules! check { + ($how:ident, $_self:ident, $a:ident, $m:ident) => {{ + $a.required_unless().map(|ru| { + ru.iter().$how(|n| { + $m.contains(n) || { + if let Some(grp) = $_self.groups.iter().find(|g| &g.name == n) { + grp.args.iter().any(|arg| $m.contains(arg)) + } else { + false + } + } + }) + }) + }}; } - if let Some(ru) = a.required_unless() { - debugln!("Parser::is_missing_required_ok: Required unless found...{:?}", ru); - let mut found_any = false; - for n in ru.iter() { - debugln!("Parser::is_missing_required_ok:iter: ru={}", n); - if matcher.contains(n) || - self.groups - .get(n) - .map_or(false, |g| g.args.iter().any(|an| matcher.contains(an))) { - if !a.is_set(ArgSettings::RequiredUnlessAll) { - debugln!("Parser::is_missing_required_ok:iter: Doesn't require all...returning true"); - return true; - } - debugln!("Parser::is_missing_required_ok:iter: Requires all...next"); - found_any = true; - } else if a.is_set(ArgSettings::RequiredUnlessAll) { - debugln!("Parser::is_missing_required_ok:iter: Not in matcher, or group and requires all...returning false"); - return false; - } - } - return found_any; + if a.is_set(ArgSettings::RequiredUnlessAll) { + check!(all, self, a, matcher) + } else { + check!(any, self, a, matcher) } - false + } + + #[inline] + fn is_missing_required_ok(&self, a: &A, matcher: &ArgMatcher) -> bool + where A: AnyArg<'a, 'b> + { + debugln!("Parser::is_missing_required_ok: a={}", a.name()); + self.check_conflicts(a, matcher).unwrap_or(false) || + self.check_required_unless(a, matcher).unwrap_or(false) } fn did_you_mean_error(&self, arg: &str, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { // Didn't match a flag or option...maybe it was a typo and close to one let suffix = suggestions::did_you_mean_suffix(arg, - self.long_list.iter(), + longs!(self), suggestions::DidYouMeanMessageStyle::LongFlag); // Add the arg to the matches to build a proper usage string if let Some(name) = suffix.1 { - if let Some(opt) = find_by_long!(self, &name, opts) { + if let Some(opt) = find_opt_by_long!(self, &name) { self.groups_for_arg(&*opt.b.name) .and_then(|grps| Some(matcher.inc_occurrences_of(&*grps))); matcher.insert(&*opt.b.name); - } else if let Some(flg) = find_by_long!(self, &name, flags) { + } else if let Some(flg) = find_flag_by_long!(self, &name) { self.groups_for_arg(&*flg.b.name) .and_then(|grps| Some(matcher.inc_occurrences_of(&*grps))); matcher.insert(&*flg.b.name); @@ -1966,12 +2010,12 @@ impl<'a, 'b> Parser<'a, 'b> .fold(String::new(), |a, s| a + &format!(" {}", s)[..]); let flags = self.needs_flags_tag(); - if flags && !self.is_set(UnifiedHelpMessage) { + if flags && !self.is_set(AS::UnifiedHelpMessage) { usage.push_str(" [FLAGS]"); } else if flags { usage.push_str(" [OPTIONS]"); } - if !self.is_set(UnifiedHelpMessage) && self.has_opts() && + if !self.is_set(AS::UnifiedHelpMessage) && self.has_opts() && self.opts.iter().any(|o| !o.b.settings.is_set(ArgSettings::Required)) { usage.push_str(" [OPTIONS]"); } @@ -1996,9 +2040,9 @@ impl<'a, 'b> Parser<'a, 'b> } - if self.has_subcommands() && !self.is_set(SubcommandRequired) { + if self.has_subcommands() && !self.is_set(AS::SubcommandRequired) { usage.push_str(" [SUBCOMMAND]"); - } else if self.is_set(SubcommandRequired) && self.has_subcommands() { + } else if self.is_set(AS::SubcommandRequired) && self.has_subcommands() { usage.push_str(" "); } } else { @@ -2032,7 +2076,7 @@ impl<'a, 'b> Parser<'a, 'b> }) [..]); usage.push_str(&*r_string); - if self.is_set(SubcommandRequired) { + if self.is_set(AS::SubcommandRequired) { usage.push_str(" "); } } @@ -2048,20 +2092,20 @@ impl<'a, 'b> Parser<'a, 'b> if bn.contains(' ') { // Incase we're dealing with subcommands i.e. git mv is translated to git-mv write!(w, - "{} {}", - bn.replace(" ", "-"), - self.meta.version.unwrap_or("".into())) + "{} {}", + bn.replace(" ", "-"), + self.meta.version.unwrap_or("".into())) } else { write!(w, - "{} {}", - &self.meta.name[..], - self.meta.version.unwrap_or("".into())) + "{} {}", + &self.meta.name[..], + self.meta.version.unwrap_or("".into())) } } else { write!(w, - "{} {}", - &self.meta.name[..], - self.meta.version.unwrap_or("".into())) + "{} {}", + &self.meta.name[..], + self.meta.version.unwrap_or("".into())) } } @@ -2142,10 +2186,10 @@ impl<'a, 'b> Parser<'a, 'b> pub fn color(&self) -> ColorWhen { debugln!("Parser::color;"); debug!("Parser::color: Color setting..."); - if self.is_set(ColorNever) { + if self.is_set(AS::ColorNever) { sdebugln!("Never"); ColorWhen::Never - } else if self.is_set(ColorAlways) { + } else if self.is_set(AS::ColorAlways) { sdebugln!("Always"); ColorWhen::Always } else { @@ -2170,7 +2214,8 @@ impl<'a, 'b> Parser<'a, 'b> #[cfg_attr(feature = "lints", allow(explicit_iter_loop))] pub fn find_subcommand(&'b self, sc: &str) -> Option<&'b App<'a, 'b>> { debugln!("Parser::find_subcommand: sc={}", sc); - debugln!("Parser::find_subcommand: Currently in Parser...{}", self.meta.bin_name.as_ref().unwrap()); + debugln!("Parser::find_subcommand: Currently in Parser...{}", + self.meta.bin_name.as_ref().unwrap()); for s in self.subcommands.iter() { if s.p.meta.bin_name.as_ref().unwrap_or(&String::new()) == sc || (s.p.meta.aliases.is_some() && @@ -2189,4 +2234,10 @@ impl<'a, 'b> Parser<'a, 'b> } None } + + #[inline] + fn contains_long(&self, l: &str) -> bool { longs!(self).any(|al| al == &l) } + + #[inline] + fn contains_short(&self, s: char) -> bool { shorts!(self).any(|arg_s| arg_s == &s) } } diff --git a/src/args/arg.rs b/src/args/arg.rs index bb6bc50e7fd..53cd262fc30 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -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 diff --git a/src/args/arg_builder/switched.rs b/src/args/arg_builder/switched.rs index 777ff1d590b..224b2f2b24b 100644 --- a/src/args/arg_builder/switched.rs +++ b/src/args/arg_builder/switched.rs @@ -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> { diff --git a/src/args/arg_matcher.rs b/src/args/arg_matcher.rs index 27f57c6dcdb..3052acd91f6 100644 --- a/src/args/arg_matcher.rs +++ b/src/args/arg_matcher.rs @@ -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()); diff --git a/src/completions/bash.rs b/src/completions/bash.rs index ab94865b8e5..a5333828afb 100644 --- a/src/completions/bash.rs +++ b/src/completions/bash.rs @@ -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 diff --git a/src/completions/powershell.rs b/src/completions/powershell.rs index 7c842b737da..b70da452a06 100644 --- a/src/completions/powershell.rs +++ b/src/completions/powershell.rs @@ -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)); } diff --git a/src/macros.rs b/src/macros.rs index 277098e79b9..2ce6d67972c 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -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. ") -/// (about: "Does awesome things") +/// let matches = clap_app!(myapp => +/// (version: "1.0") +/// (author: "Kevin K. ") +/// (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. ") +/// (@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. ") /// (@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 ` = `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 ` = `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))) + }}; +} diff --git a/tests/completions.rs b/tests/completions.rs index 8b4f843ffd0..e41fc9d10d6 100644 --- a/tests/completions.rs +++ b/tests/completions.rs @@ -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)); -} \ No newline at end of file +"#; + +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)); +} diff --git a/tests/help.rs b/tests/help.rs index 0393f057717..2390458edcb 100644 --- a/tests/help.rs +++ b/tests/help.rs @@ -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. ") - .version("0.1") - .args_from_usage(" + .subcommand(SubCommand::with_name("subcmd").subcommand(SubCommand::with_name("multi") + .about("tests subcommands") + .author("Kevin K. ") + .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] diff --git a/tests/macros.rs b/tests/macros.rs index b835f8076db..cc25e1bfb8e 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -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")); } diff --git a/tests/require.rs b/tests/require.rs index f8eb4105823..31033ef3592 100644 --- a/tests/require.rs +++ b/tests/require.rs @@ -18,7 +18,7 @@ static COND_REQ_IN_USAGE: &'static str = "error: The following required argument --output USAGE: - test --target --input --output + test --input --output --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); -} \ No newline at end of file +} diff --git a/tests/unique_args.rs b/tests/unique_args.rs index b8e65df0c81..871024872c3 100644 --- a/tests/unique_args.rs +++ b/tests/unique_args.rs @@ -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")]); } From 85a636d539bce0b8b55ceb23d6ffc3694bbe4d0f Mon Sep 17 00:00:00 2001 From: Kevin K Date: Sat, 25 Feb 2017 12:34:42 -0500 Subject: [PATCH 3/9] tests(Benches): adds real world benchmarks --- Cargo.toml | 3 +- benches/04_new_help.rs | 209 +++++------------ benches/05_ripgrep.rs | 511 +++++++++++++++++++++++++++++++++++++++++ benches/06_rustup.rs | 453 ++++++++++++++++++++++++++++++++++++ 4 files changed, 1021 insertions(+), 155 deletions(-) create mode 100644 benches/05_ripgrep.rs create mode 100644 benches/06_rustup.rs diff --git a/Cargo.toml b/Cargo.toml index f60906953f1..7aa58a715d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,8 @@ clippy = { version = "~0.0.112", optional = true } atty = { version = "0.2.2", optional = true } [dev-dependencies] -regex = "0.2" +regex = "~0.1.80" +lazy_static = "~0.2" [features] default = ["suggestions", "color", "wrap_help"] diff --git a/benches/04_new_help.rs b/benches/04_new_help.rs index abc7a453dc4..f033efb5a19 100644 --- a/benches/04_new_help.rs +++ b/benches/04_new_help.rs @@ -23,11 +23,11 @@ fn app_example1<'b, 'c>() -> App<'b, 'c> { .author("Kevin K. ") .about("Does awesome things") .args_from_usage("-c, --config=[FILE] 'Sets a custom config file' - 'Sets an optional output file' - -d... 'Turn debugging information on'") + 'Sets an optional output file' + -d... 'Turn debugging information on'") .subcommand(SubCommand::with_name("test") - .about("does testing things") - .arg_from_usage("-l, --list 'lists test values'")) + .about("does testing things") + .arg_from_usage("-l, --list 'lists test values'")) } fn app_example2<'b, 'c>() -> App<'b, 'c> { @@ -39,37 +39,19 @@ fn app_example2<'b, 'c>() -> App<'b, 'c> { fn app_example3<'b, 'c>() -> App<'b, 'c> { App::new("MyApp") - // All application settings go here... - - // A simple "Flag" argument example (i.e. "-d") using the builder pattern .arg(Arg::with_name("debug") - .help("turn on debugging information") - .short("d")) - - // Two arguments, one "Option" argument (i.e. one that takes a value) such - // as "-c some", and one positional argument (i.e. "myapp some_file") - .args(&[ - Arg::with_name("config") + .help("turn on debugging information") + .short("d")) + .args(&[Arg::with_name("config") .help("sets the config file to use") .takes_value(true) .short("c") .long("config"), - Arg::with_name("input") + Arg::with_name("input") .help("the input file to use") .index(1) - .required(true) - ]) - - // *Note* the following two examples are convienience methods, if you wish - // to still get the full configurability of Arg::with_name() and the readability - // of arg_from_usage(), you can instantiate a new Arg with Arg::from_usage() and - // still be able to set all the additional properties, just like Arg::with_name() - // - // - // One "Flag" using a usage string + .required(true)]) .arg_from_usage("--license 'display the license file'") - - // Two args, one "Positional", and one "Option" using a usage string .args_from_usage("[output] 'Supply an output file to use' -i, --int=[IFACE] 'Set an interface to use'") } @@ -80,142 +62,79 @@ fn app_example4<'b, 'c>() -> App<'b, 'c> { .version("1.0") .author("Kevin K. ") .arg(Arg::with_name("debug") - .help("turn on debugging information") - .short("d") - .long("debug")) + .help("turn on debugging information") + .short("d") + .long("debug")) .arg(Arg::with_name("config") - .help("sets the config file to use") - .short("c") - .long("config")) + .help("sets the config file to use") + .short("c") + .long("config")) .arg(Arg::with_name("input") - .help("the input file to use") - .index(1) - .required(true)) + .help("the input file to use") + .index(1) + .required(true)) } fn app_example5<'b, 'c>() -> App<'b, 'c> { - App::new("MyApp") - // Regular App configuration goes here... - - // We'll add a flag that represents an awesome meter... - // - // I'll explain each possible setting that "flags" accept. Keep in mind - // that you DO NOT need to set each of these for every flag, only the ones - // you want for your individual case. - .arg(Arg::with_name("awesome") - .help("turns up the awesome") // Displayed when showing help info - .short("a") // Trigger this arg with "-a" - .long("awesome") // Trigger this arg with "--awesome" - .multiple(true) // This flag should allow multiple - // occurrences such as "-aaa" or "-a -a" - .requires("config") // Says, "If the user uses -a, they MUST - // also use this other 'config' arg too" - // Can also specifiy a list using - // requires_all(Vec<&str>) - .conflicts_with("output") // Opposite of requires(), says "if the - // user uses -a, they CANNOT use 'output'" - // also has a mutually_excludes_all(Vec<&str>) - ) + App::new("MyApp").arg(Arg::with_name("awesome") + .help("turns up the awesome") + .short("a") + .long("awesome") + .multiple(true) + .requires("config") + .conflicts_with("output")) } fn app_example6<'b, 'c>() -> App<'b, 'c> { App::new("MyApp") - // Regular App configuration goes here... - - // We'll add two positional arguments, a input file, and a config file. - // - // I'll explain each possible setting that "positionals" accept. Keep in - // mind that you DO NOT need to set each of these for every flag, only the - // ones that apply to your individual case. .arg(Arg::with_name("input") - .help("the input file to use") // Displayed when showing help info - .index(1) // Set the order in which the user must - // specify this argument (Starts at 1) - .requires("config") // Says, "If the user uses "input", they MUST - // also use this other 'config' arg too" - // Can also specifiy a list using - // requires_all(Vec<&str>) - .conflicts_with("output") // Opposite of requires(), says "if the - // user uses -a, they CANNOT use 'output'" - // also has a mutually_excludes_all(Vec<&str>) - .required(true) // By default this argument MUST be present - // NOTE: mutual exclusions take precedence over - // required arguments - ) + .help("the input file to use") + .index(1) + .requires("config") + .conflicts_with("output") + .required(true)) .arg(Arg::with_name("config") - .help("the config file to use") - .index(2)) // Note, we do not need to specify required(true) - // if we don't want to, because "input" already - // requires "config" - // Note, we also do not need to specify requires("input") - // because requires lists are automatically two-way + .help("the config file to use") + .index(2)) } fn app_example7<'b, 'c>() -> App<'b, 'c> { App::new("MyApp") - // Regular App configuration goes here... - - // Assume we an application that accepts an input file via the "-i file" - // or the "--input file" (as wel as "--input=file"). - // Below every setting supported by option arguments is discussed. - // NOTE: You DO NOT need to specify each setting, only those which apply - // to your particular case. + .arg(Arg::with_name("config")) + .arg(Arg::with_name("output")) .arg(Arg::with_name("input") - .help("the input file to use") // Displayed when showing help info - .takes_value(true) // MUST be set to true in order to be an "option" argument - .short("i") // This argument is triggered with "-i" - .long("input") // This argument is triggered with "--input" - .multiple(true) // Set to true if you wish to allow multiple occurrences - // such as "-i file -i other_file -i third_file" - .required(true) // By default this argument MUST be present - // NOTE: mutual exclusions take precedence over - // required arguments - .requires("config") // Says, "If the user uses "input", they MUST - // also use this other 'config' arg too" - // Can also specifiy a list using - // requires_all(Vec<&str>) - .conflicts_with("output") // Opposite of requires(), says "if the - // user uses -a, they CANNOT use 'output'" - // also has a conflicts_with_all(Vec<&str>) - ) + .help("the input file to use") + .takes_value(true) + .short("i") + .long("input") + .multiple(true) + .required(true) + .requires("config") + .conflicts_with("output")) } fn app_example8<'b, 'c>() -> App<'b, 'c> { App::new("MyApp") - // Regular App configuration goes here... - - // Assume we an application that accepts an input file via the "-i file" - // or the "--input file" (as wel as "--input=file"). - // Below every setting supported by option arguments is discussed. - // NOTE: You DO NOT need to specify each setting, only those which apply - // to your particular case. + .arg(Arg::with_name("config")) + .arg(Arg::with_name("output")) .arg(Arg::with_name("input") - .help("the input file to use") // Displayed when showing help info - .takes_value(true) // MUST be set to true in order to be an "option" argument - .short("i") // This argument is triggered with "-i" - .long("input") // This argument is triggered with "--input" - .multiple(true) // Set to true if you wish to allow multiple occurrences - // such as "-i file -i other_file -i third_file" - .required(true) // By default this argument MUST be present - // NOTE: mutual exclusions take precedence over - // required arguments - .requires("config") // Says, "If the user uses "input", they MUST - // also use this other 'config' arg too" - // Can also specifiy a list using - // requires_all(Vec<&str>) - .conflicts_with("output") // Opposite of requires(), says "if the - // user uses -a, they CANNOT use 'output'" - // also has a conflicts_with_all(Vec<&str>) - ) + .help("the input file to use") + .takes_value(true) + .short("i") + .long("input") + .multiple(true) + .required(true) + .requires("config") + .conflicts_with("output")) } fn app_example10<'b, 'c>() -> App<'b, 'c> { App::new("myapp") .about("does awesome things") .arg(Arg::with_name("CONFIG") - .help("The config file to use (default is \"config.json\")") - .short("c") - .takes_value(true)) + .help("The config file to use (default is \"config.json\")") + .short("c") + .takes_value(true)) } #[bench] @@ -274,24 +193,6 @@ fn example10(b: &mut Bencher) { #[bench] fn example4_template(b: &mut Bencher) { -/* -MyApp 1.0 -Kevin K. -Parses an input file to do awesome things - -USAGE: - test [FLAGS] - -FLAGS: - -c, --config sets the config file to use - -d, --debug turn on debugging information - -h, --help Prints help information - -V, --version Prints version information - -ARGS: - the input file to use -*/ - let app = app_example4().template("{bin} {version}\n{author}\n{about}\n\nUSAGE:\n {usage}\n\nFLAGS:\n{flags}\n\nARGS:\n{args}\n"); b.iter(|| build_help(&app)); } diff --git a/benches/05_ripgrep.rs b/benches/05_ripgrep.rs new file mode 100644 index 00000000000..f7868ccf01a --- /dev/null +++ b/benches/05_ripgrep.rs @@ -0,0 +1,511 @@ +// Used to simulate a fairly large number of options/flags and parsing with thousands of positional +// args +// +// CLI used is adapted from ripgrep 48a8a3a691220f9e5b2b08f4051abe8655ea7e8a + +#![feature(test)] + +extern crate clap; +extern crate test; +#[macro_use] +extern crate lazy_static; + +use clap::{App, AppSettings, Arg, ArgSettings}; + +use test::Bencher; +use std::collections::HashMap; + +#[bench] +fn build_app_short(b: &mut Bencher) { b.iter(|| app_short()); } + +#[bench] +fn build_app_long(b: &mut Bencher) { b.iter(|| app_long()); } + +#[bench] +fn parse_clean(b: &mut Bencher) { b.iter(|| app_short().get_matches_from(vec!["rg", "pat"])); } + + +const ABOUT: &'static str = " +ripgrep (rg) recursively searches your current directory for a regex pattern. + +ripgrep's regex engine uses finite automata and guarantees linear time +searching. Because of this, features like backreferences and arbitrary +lookaround are not supported. + +Project home page: https://github.com/BurntSushi/ripgrep + +Use -h for short descriptions and --help for more details."; + +const USAGE: &'static str = " + rg [OPTIONS] [ ...] + rg [OPTIONS] [-e PATTERN | -f FILE ]... [ ...] + rg [OPTIONS] --files [ ...] + rg [OPTIONS] --type-list"; + +const TEMPLATE: &'static str = "\ +{bin} {version} +{author} +{about} + +USAGE:{usage} + +ARGS: +{positionals} + +OPTIONS: +{unified}"; + +/// Build a clap application with short help strings. +pub fn app_short() -> App<'static, 'static> { app(false, |k| USAGES[k].short) } + +/// Build a clap application with long help strings. +pub fn app_long() -> App<'static, 'static> { app(true, |k| USAGES[k].long) } + +/// Build a clap application parameterized by usage strings. +/// +/// The function given should take a clap argument name and return a help +/// string. `app` will panic if a usage string is not defined. +/// +/// This is an intentionally stand-alone module so that it can be used easily +/// in a `build.rs` script to build shell completion files. +fn app(next_line_help: bool, doc: F) -> App<'static, 'static> + where F: Fn(&'static str) -> &'static str +{ + let arg = |name| Arg::with_name(name).help(doc(name)).next_line_help(next_line_help); + let flag = |name| arg(name).long(name); + + App::new("ripgrep") + .author("BurntSushi") // simulating since it's only a bench + .version("0.4.0") // Simulating + .about(ABOUT) + .max_term_width(100) + .setting(AppSettings::UnifiedHelpMessage) + .usage(USAGE) + .template(TEMPLATE) + // Handle help/version manually to make their output formatting + // consistent with short/long views. + .arg(arg("help-short").short("h")) + .arg(flag("help")) + .arg(flag("version").short("V")) + // First, set up primary positional/flag arguments. + .arg(arg("pattern") + .required_unless_one(&[ + "file", "files", "help-short", "help", "regexp", "type-list", + "version", + ])) + .arg(arg("path").multiple(true)) + .arg(flag("regexp").short("e") + .takes_value(true).multiple(true).number_of_values(1) + .set(ArgSettings::AllowLeadingHyphen) + .value_name("pattern")) + .arg(flag("files") + // This should also conflict with `pattern`, but the first file + // path will actually be in `pattern`. + .conflicts_with_all(&["file", "regexp", "type-list"])) + .arg(flag("type-list") + .conflicts_with_all(&["file", "files", "pattern", "regexp"])) + // Second, set up common flags. + .arg(flag("text").short("a")) + .arg(flag("count").short("c")) + .arg(flag("color") + .value_name("WHEN") + .takes_value(true) + .hide_possible_values(true) + .possible_values(&["never", "auto", "always", "ansi"])) + .arg(flag("colors").value_name("SPEC") + .takes_value(true).multiple(true).number_of_values(1)) + .arg(flag("fixed-strings").short("F")) + .arg(flag("glob").short("g") + .takes_value(true).multiple(true).number_of_values(1) + .value_name("GLOB")) + .arg(flag("ignore-case").short("i")) + .arg(flag("line-number").short("n")) + .arg(flag("no-line-number").short("N")) + .arg(flag("quiet").short("q")) + .arg(flag("type").short("t") + .takes_value(true).multiple(true).number_of_values(1) + .value_name("TYPE")) + .arg(flag("type-not").short("T") + .takes_value(true).multiple(true).number_of_values(1) + .value_name("TYPE")) + .arg(flag("unrestricted").short("u") + .multiple(true)) + .arg(flag("invert-match").short("v")) + .arg(flag("word-regexp").short("w")) + // Third, set up less common flags. + .arg(flag("after-context").short("A") + .value_name("NUM").takes_value(true) + .validator(validate_number)) + .arg(flag("before-context").short("B") + .value_name("NUM").takes_value(true) + .validator(validate_number)) + .arg(flag("context").short("C") + .value_name("NUM").takes_value(true) + .validator(validate_number)) + .arg(flag("column")) + .arg(flag("context-separator") + .value_name("SEPARATOR").takes_value(true)) + .arg(flag("debug")) + .arg(flag("file").short("f") + .value_name("FILE").takes_value(true) + .multiple(true).number_of_values(1)) + .arg(flag("files-with-matches").short("l")) + .arg(flag("files-without-match")) + .arg(flag("with-filename").short("H")) + .arg(flag("no-filename")) + .arg(flag("heading").overrides_with("no-heading")) + .arg(flag("no-heading").overrides_with("heading")) + .arg(flag("hidden")) + .arg(flag("ignore-file") + .value_name("FILE").takes_value(true) + .multiple(true).number_of_values(1)) + .arg(flag("follow").short("L")) + .arg(flag("max-count") + .short("m").value_name("NUM").takes_value(true) + .validator(validate_number)) + .arg(flag("maxdepth") + .value_name("NUM").takes_value(true) + .validator(validate_number)) + .arg(flag("mmap")) + .arg(flag("no-messages")) + .arg(flag("no-mmap")) + .arg(flag("no-ignore")) + .arg(flag("no-ignore-parent")) + .arg(flag("no-ignore-vcs")) + .arg(flag("null")) + .arg(flag("path-separator").value_name("SEPARATOR").takes_value(true)) + .arg(flag("pretty").short("p")) + .arg(flag("replace").short("r").value_name("ARG").takes_value(true)) + .arg(flag("case-sensitive").short("s")) + .arg(flag("smart-case").short("S")) + .arg(flag("sort-files")) + .arg(flag("threads") + .short("j").value_name("ARG").takes_value(true) + .validator(validate_number)) + .arg(flag("vimgrep")) + .arg(flag("type-add") + .value_name("TYPE").takes_value(true) + .multiple(true).number_of_values(1)) + .arg(flag("type-clear") + .value_name("TYPE").takes_value(true) + .multiple(true).number_of_values(1)) +} + +struct Usage { + short: &'static str, + long: &'static str, +} + +macro_rules! doc { + ($map:expr, $name:expr, $short:expr) => { + doc!($map, $name, $short, $short) + }; + ($map:expr, $name:expr, $short:expr, $long:expr) => { + $map.insert($name, Usage { + short: $short, + long: concat!($long, "\n "), + }); + }; +} + +lazy_static! { + static ref USAGES: HashMap<&'static str, Usage> = { + let mut h = HashMap::new(); + doc!(h, "help-short", + "Show short help output.", + "Show short help output. Use --help to show more details."); + doc!(h, "help", + "Show verbose help output.", + "When given, more details about flags are provided."); + doc!(h, "version", + "Prints version information."); + + doc!(h, "pattern", + "A regular expression used for searching.", + "A regular expression used for searching. Multiple patterns \ + may be given. To match a pattern beginning with a -, use [-]."); + doc!(h, "regexp", + "A regular expression used for searching.", + "A regular expression used for searching. Multiple patterns \ + may be given. To match a pattern beginning with a -, use [-]."); + doc!(h, "path", + "A file or directory to search.", + "A file or directory to search. Directories are searched \ + recursively."); + doc!(h, "files", + "Print each file that would be searched.", + "Print each file that would be searched without actually \ + performing the search. This is useful to determine whether a \ + particular file is being searched or not."); + doc!(h, "type-list", + "Show all supported file types.", + "Show all supported file types and their corresponding globs."); + + doc!(h, "text", + "Search binary files as if they were text."); + doc!(h, "count", + "Only show count of matches for each file."); + doc!(h, "color", + "When to use color. [default: auto]", + "When to use color in the output. The possible values are \ + never, auto, always or ansi. The default is auto. When always \ + is used, coloring is attempted based on your environment. When \ + ansi used, coloring is forcefully done using ANSI escape color \ + codes."); + doc!(h, "colors", + "Configure color settings and styles.", + "This flag specifies color settings for use in the output. \ + This flag may be provided multiple times. Settings are applied \ + iteratively. Colors are limited to one of eight choices: \ + red, blue, green, cyan, magenta, yellow, white and black. \ + Styles are limited to nobold, bold, nointense or intense.\n\n\ + The format of the flag is {type}:{attribute}:{value}. {type} \ + should be one of path, line or match. {attribute} can be fg, bg \ + or style. {value} is either a color (for fg and bg) or a text \ + style. A special format, {type}:none, will clear all color \ + settings for {type}.\n\nFor example, the following command will \ + change the match color to magenta and the background color for \ + line numbers to yellow:\n\n\ + rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo."); + doc!(h, "fixed-strings", + "Treat the pattern as a literal string.", + "Treat the pattern as a literal string instead of a regular \ + expression. When this flag is used, special regular expression \ + meta characters such as (){}*+. do not need to be escaped."); + doc!(h, "glob", + "Include or exclude files/directories.", + "Include or exclude files/directories for searching that \ + match the given glob. This always overrides any other \ + ignore logic. Multiple glob flags may be used. Globbing \ + rules match .gitignore globs. Precede a glob with a ! \ + to exclude it."); + doc!(h, "ignore-case", + "Case insensitive search.", + "Case insensitive search. This is overridden by \ + --case-sensitive."); + doc!(h, "line-number", + "Show line numbers.", + "Show line numbers (1-based). This is enabled by default when \ + searching in a tty."); + doc!(h, "no-line-number", + "Suppress line numbers.", + "Suppress line numbers. This is enabled by default when NOT \ + searching in a tty."); + doc!(h, "quiet", + "Do not print anything to stdout.", + "Do not print anything to stdout. If a match is found in a file, \ + stop searching. This is useful when ripgrep is used only for \ + its exit code."); + doc!(h, "type", + "Only search files matching TYPE.", + "Only search files matching TYPE. Multiple type flags may be \ + provided. Use the --type-list flag to list all available \ + types."); + doc!(h, "type-not", + "Do not search files matching TYPE.", + "Do not search files matching TYPE. Multiple type-not flags may \ + be provided. Use the --type-list flag to list all available \ + types."); + doc!(h, "unrestricted", + "Reduce the level of \"smart\" searching.", + "Reduce the level of \"smart\" searching. A single -u \ + won't respect .gitignore (etc.) files. Two -u flags will \ + additionally search hidden files and directories. Three \ + -u flags will additionally search binary files. -uu is \ + roughly equivalent to grep -r and -uuu is roughly \ + equivalent to grep -a -r."); + doc!(h, "invert-match", + "Invert matching.", + "Invert matching. Show lines that don't match given patterns."); + doc!(h, "word-regexp", + "Only show matches surrounded by word boundaries.", + "Only show matches surrounded by word boundaries. This is \ + equivalent to putting \\b before and after all of the search \ + patterns."); + + doc!(h, "after-context", + "Show NUM lines after each match."); + doc!(h, "before-context", + "Show NUM lines before each match."); + doc!(h, "context", + "Show NUM lines before and after each match."); + doc!(h, "column", + "Show column numbers", + "Show column numbers (1-based). This only shows the column \ + numbers for the first match on each line. This does not try \ + to account for Unicode. One byte is equal to one column. This \ + implies --line-number."); + doc!(h, "context-separator", + "Set the context separator string. [default: --]", + "The string used to separate non-contiguous context lines in the \ + output. Escape sequences like \\x7F or \\t may be used. The \ + default value is --."); + doc!(h, "debug", + "Show debug messages.", + "Show debug messages. Please use this when filing a bug report."); + doc!(h, "file", + "Search for patterns from the given file.", + "Search for patterns from the given file, with one pattern per \ + line. When this flag is used or multiple times or in \ + combination with the -e/--regexp flag, then all patterns \ + provided are searched. Empty pattern lines will match all input \ + lines, and the newline is not counted as part of the pattern."); + doc!(h, "files-with-matches", + "Only show the path of each file with at least one match."); + doc!(h, "files-without-match", + "Only show the path of each file that contains zero matches."); + doc!(h, "with-filename", + "Show file name for each match.", + "Prefix each match with the file name that contains it. This is \ + the default when more than one file is searched."); + doc!(h, "no-filename", + "Never show the file name for a match.", + "Never show the file name for a match. This is the default when \ + one file is searched."); + doc!(h, "heading", + "Show matches grouped by each file.", + "This shows the file name above clusters of matches from each \ + file instead of showing the file name for every match. This is \ + the default mode at a tty."); + doc!(h, "no-heading", + "Don't group matches by each file.", + "Don't group matches by each file. If -H/--with-filename is \ + enabled, then file names will be shown for every line matched. \ + This is the default mode when not at a tty."); + doc!(h, "hidden", + "Search hidden files and directories.", + "Search hidden files and directories. By default, hidden files \ + and directories are skipped."); + doc!(h, "ignore-file", + "Specify additional ignore files.", + "Specify additional ignore files for filtering file paths. \ + Ignore files should be in the gitignore format and are matched \ + relative to the current working directory. These ignore files \ + have lower precedence than all other ignore files. When \ + specifying multiple ignore files, earlier files have lower \ + precedence than later files."); + doc!(h, "follow", + "Follow symbolic links."); + doc!(h, "max-count", + "Limit the number of matches.", + "Limit the number of matching lines per file searched to NUM."); + doc!(h, "maxdepth", + "Descend at most NUM directories.", + "Limit the depth of directory traversal to NUM levels beyond \ + the paths given. A value of zero only searches the \ + starting-points themselves.\n\nFor example, \ + 'rg --maxdepth 0 dir/' is a no-op because dir/ will not be \ + descended into. 'rg --maxdepth 1 dir/' will search only the \ + direct children of dir/."); + doc!(h, "mmap", + "Searching using memory maps when possible.", + "Search using memory maps when possible. This is enabled by \ + default when ripgrep thinks it will be faster. Note that memory \ + map searching doesn't currently support all options, so if an \ + incompatible option (e.g., --context) is given with --mmap, \ + then memory maps will not be used."); + doc!(h, "no-messages", + "Suppress all error messages.", + "Suppress all error messages. This is equivalent to redirecting \ + stderr to /dev/null."); + doc!(h, "no-mmap", + "Never use memory maps.", + "Never use memory maps, even when they might be faster."); + doc!(h, "no-ignore", + "Don't respect ignore files.", + "Don't respect ignore files (.gitignore, .ignore, etc.). This \ + implies --no-ignore-parent and --no-ignore-vcs."); + doc!(h, "no-ignore-parent", + "Don't respect ignore files in parent directories.", + "Don't respect ignore files (.gitignore, .ignore, etc.) in \ + parent directories."); + doc!(h, "no-ignore-vcs", + "Don't respect VCS ignore files", + "Don't respect version control ignore files (.gitignore, etc.). \ + This implies --no-ignore-parent. Note that .ignore files will \ + continue to be respected."); + doc!(h, "null", + "Print NUL byte after file names", + "Whenever a file name is printed, follow it with a NUL byte. \ + This includes printing file names before matches, and when \ + printing a list of matching files such as with --count, \ + --files-with-matches and --files. This option is useful for use \ + with xargs."); + doc!(h, "path-separator", + "Path separator to use when printing file paths.", + "The path separator to use when printing file paths. This \ + defaults to your platform's path separator, which is / on Unix \ + and \\ on Windows. This flag is intended for overriding the \ + default when the environment demands it (e.g., cygwin). A path \ + separator is limited to a single byte."); + doc!(h, "pretty", + "Alias for --color always --heading -n."); + doc!(h, "replace", + "Replace matches with string given.", + "Replace every match with the string given when printing \ + results. Neither this flag nor any other flag will modify your \ + files.\n\nCapture group indices (e.g., $5) and names \ + (e.g., $foo) are supported in the replacement string.\n\n\ + Note that the replacement by default replaces each match, and \ + NOT the entire line. To replace the entire line, you should \ + match the entire line."); + doc!(h, "case-sensitive", + "Search case sensitively.", + "Search case sensitively. This overrides -i/--ignore-case and \ + -S/--smart-case."); + doc!(h, "smart-case", + "Smart case search.", + "Searches case insensitively if the pattern is all lowercase. \ + Search case sensitively otherwise. This is overridden by \ + either -s/--case-sensitive or -i/--ignore-case."); + doc!(h, "sort-files", + "Sort results by file path. Implies --threads=1.", + "Sort results by file path. Note that this currently \ + disables all parallelism and runs search in a single thread."); + doc!(h, "threads", + "The approximate number of threads to use.", + "The approximate number of threads to use. A value of 0 (which \ + is the default) causes ripgrep to choose the thread count \ + using heuristics."); + doc!(h, "vimgrep", + "Show results in vim compatible format.", + "Show results with every match on its own line, including \ + line numbers and column numbers. With this option, a line with \ + more than one match will be printed more than once."); + + doc!(h, "type-add", + "Add a new glob for a file type.", + "Add a new glob for a particular file type. Only one glob can be \ + added at a time. Multiple --type-add flags can be provided. \ + Unless --type-clear is used, globs are added to any existing \ + globs defined inside of ripgrep.\n\nNote that this MUST be \ + passed to every invocation of ripgrep. Type settings are NOT \ + persisted.\n\nExample: \ + rg --type-add 'foo:*.foo' -tfoo PATTERN.\n\n\ + --type-add can also be used to include rules from other types \ + with the special include directive. The include directive \ + permits specifying one or more other type names (separated by a \ + comma) that have been defined and its rules will automatically \ + be imported into the type specified. For example, to create a \ + type called src that matches C++, Python and Markdown files, one \ + can use:\n\n\ + --type-add 'src:include:cpp,py,md'\n\n\ + Additional glob rules can still be added to the src type by \ + using the --type-add flag again:\n\n\ + --type-add 'src:include:cpp,py,md' --type-add 'src:*.foo'\n\n\ + Note that type names must consist only of Unicode letters or \ + numbers. Punctuation characters are not allowed."); + doc!(h, "type-clear", + "Clear globs for given file type.", + "Clear the file type globs previously defined for TYPE. This \ + only clears the default type definitions that are found inside \ + of ripgrep.\n\nNote that this MUST be passed to every \ + invocation of ripgrep. Type settings are NOT persisted."); + + h + }; +} + +fn validate_number(s: String) -> Result<(), String> { + s.parse::().map(|_| ()).map_err(|err| err.to_string()) +} diff --git a/benches/06_rustup.rs b/benches/06_rustup.rs new file mode 100644 index 00000000000..35c5fc46ba5 --- /dev/null +++ b/benches/06_rustup.rs @@ -0,0 +1,453 @@ +// Used to simulate a fairly large number of subcommands +// +// CLI used is from rustup 408ed84f0e50511ed44a405dd91365e5da588790 + +#![feature(test)] + +extern crate clap; +extern crate test; + +use clap::{App, AppSettings, Arg, Shell, SubCommand, ArgGroup}; + +use test::Bencher; + +#[bench] +fn build_app(b: &mut Bencher) { b.iter(|| build_cli()); } + +#[bench] +fn parse_clean(b: &mut Bencher) { b.iter(|| build_cli().get_matches_from(vec![""])); } + +pub fn build_cli() -> App<'static, 'static> { + App::new("rustup") + .version("0.9.0") // Simulating + .about("The Rust toolchain installer") + .after_help(RUSTUP_HELP) + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::DeriveDisplayOrder) + // .setting(AppSettings::SubcommandRequiredElseHelp) + .arg(Arg::with_name("verbose") + .help("Enable verbose output") + .short("v") + .long("verbose")) + .subcommand(SubCommand::with_name("show") + .about("Show the active and installed toolchains") + .after_help(SHOW_HELP)) + .subcommand(SubCommand::with_name("install") + .about("Update Rust toolchains") + .after_help(TOOLCHAIN_INSTALL_HELP) + .setting(AppSettings::Hidden) // synonym for 'toolchain install' + .arg(Arg::with_name("toolchain") + .required(true))) + .subcommand(SubCommand::with_name("update") + .about("Update Rust toolchains") + .after_help(UPDATE_HELP) + .arg(Arg::with_name("toolchain").required(false)) + .arg(Arg::with_name("no-self-update") + .help("Don't perform self update when running the `rustup` command") + .long("no-self-update") + .takes_value(false) + .hidden(true))) + .subcommand(SubCommand::with_name("default") + .about("Set the default toolchain") + .after_help(DEFAULT_HELP) + .arg(Arg::with_name("toolchain").required(true))) + .subcommand(SubCommand::with_name("toolchain") + .about("Modify or query the installed toolchains") + .after_help(TOOLCHAIN_HELP) + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::DeriveDisplayOrder) + // .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("list").about("List installed toolchains")) + .subcommand(SubCommand::with_name("install") + .about("Install or update a given toolchain") + .arg(Arg::with_name("toolchain").required(true))) + .subcommand(SubCommand::with_name("uninstall") + .about("Uninstall a toolchain") + .arg(Arg::with_name("toolchain").required(true))) + .subcommand(SubCommand::with_name("link") + .about("Create a custom toolchain by symlinking to a directory") + .arg(Arg::with_name("toolchain").required(true)) + .arg(Arg::with_name("path").required(true))) + .subcommand(SubCommand::with_name("update") + .setting(AppSettings::Hidden) // synonym for 'install' + .arg(Arg::with_name("toolchain") + .required(true))) + .subcommand(SubCommand::with_name("add") + .setting(AppSettings::Hidden) // synonym for 'install' + .arg(Arg::with_name("toolchain") + .required(true))) + .subcommand(SubCommand::with_name("remove") + .setting(AppSettings::Hidden) // synonym for 'uninstall' + .arg(Arg::with_name("toolchain") + .required(true)))) + .subcommand(SubCommand::with_name("target") + .about("Modify a toolchain's supported targets") + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::DeriveDisplayOrder) + // .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("list") + .about("List installed and available targets") + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true))) + .subcommand(SubCommand::with_name("add") + .about("Add a target to a Rust toolchain") + .arg(Arg::with_name("target").required(true)) + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true))) + .subcommand(SubCommand::with_name("remove") + .about("Remove a target from a Rust toolchain") + .arg(Arg::with_name("target").required(true)) + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true))) + .subcommand(SubCommand::with_name("install") + .setting(AppSettings::Hidden) // synonym for 'add' + .arg(Arg::with_name("target") + .required(true)) + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true))) + .subcommand(SubCommand::with_name("uninstall") + .setting(AppSettings::Hidden) // synonym for 'remove' + .arg(Arg::with_name("target") + .required(true)) + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true)))) + .subcommand(SubCommand::with_name("component") + .about("Modify a toolchain's installed components") + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::DeriveDisplayOrder) + // .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("list") + .about("List installed and available components") + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true))) + .subcommand(SubCommand::with_name("add") + .about("Add a component to a Rust toolchain") + .arg(Arg::with_name("component").required(true)) + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true)) + .arg(Arg::with_name("target") + .long("target") + .takes_value(true))) + .subcommand(SubCommand::with_name("remove") + .about("Remove a component from a Rust toolchain") + .arg(Arg::with_name("component").required(true)) + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true)) + .arg(Arg::with_name("target") + .long("target") + .takes_value(true)))) + .subcommand(SubCommand::with_name("override") + .about("Modify directory toolchain overrides") + .after_help(OVERRIDE_HELP) + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::DeriveDisplayOrder) + // .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("list").about("List directory toolchain overrides")) + .subcommand(SubCommand::with_name("set") + .about("Set the override toolchain for a directory") + .arg(Arg::with_name("toolchain").required(true))) + .subcommand(SubCommand::with_name("unset") + .about("Remove the override toolchain for a directory") + .after_help(OVERRIDE_UNSET_HELP) + .arg(Arg::with_name("path") + .long("path") + .takes_value(true) + .help("Path to the directory")) + .arg(Arg::with_name("nonexistent") + .long("nonexistent") + .takes_value(false) + .help("Remove override toolchain for all nonexistent directories"))) + .subcommand(SubCommand::with_name("add") + .setting(AppSettings::Hidden) // synonym for 'set' + .arg(Arg::with_name("toolchain") + .required(true))) + .subcommand(SubCommand::with_name("remove") + .setting(AppSettings::Hidden) // synonym for 'unset' + .about("Remove the override toolchain for a directory") + .arg(Arg::with_name("path") + .long("path") + .takes_value(true)) + .arg(Arg::with_name("nonexistent") + .long("nonexistent") + .takes_value(false) + .help("Remove override toolchain for all nonexistent directories")))) + .subcommand(SubCommand::with_name("run") + .about("Run a command with an environment configured for a given toolchain") + .after_help(RUN_HELP) + .setting(AppSettings::TrailingVarArg) + .arg(Arg::with_name("toolchain").required(true)) + .arg(Arg::with_name("command") + .required(true) + .multiple(true) + .use_delimiter(false))) + .subcommand(SubCommand::with_name("which") + .about("Display which binary will be run for a given command") + .arg(Arg::with_name("command").required(true))) + .subcommand(SubCommand::with_name("doc") + .about("Open the documentation for the current toolchain") + .after_help(DOC_HELP) + .arg(Arg::with_name("book") + .long("book") + .help("The Rust Programming Language book")) + .arg(Arg::with_name("std") + .long("std") + .help("Standard library API documentation")) + .group(ArgGroup::with_name("page").args(&["book", "std"]))) + .subcommand(SubCommand::with_name("man") + .about("View the man page for a given command") + .arg(Arg::with_name("command").required(true)) + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true))) + .subcommand(SubCommand::with_name("self") + .about("Modify the rustup installation") + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::DeriveDisplayOrder) + // .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("update") + .about("Download and install updates to rustup")) + .subcommand(SubCommand::with_name("uninstall") + .about("Uninstall rustup.") + .arg(Arg::with_name("no-prompt").short("y"))) + .subcommand(SubCommand::with_name("upgrade-data") + .about("Upgrade the internal data format."))) + .subcommand(SubCommand::with_name("telemetry") + .about("rustup telemetry commands") + .setting(AppSettings::Hidden) + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::DeriveDisplayOrder) + // .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("enable").about("Enable rustup telemetry")) + .subcommand(SubCommand::with_name("disable").about("Disable rustup telemetry")) + .subcommand(SubCommand::with_name("analyze").about("Analyze stored telemetry"))) + .subcommand(SubCommand::with_name("set") + .about("Alter rustup settings") + // .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("default-host") + .about("The triple used to identify toolchains when not specified") + .arg(Arg::with_name("host_triple").required(true)))) + .subcommand(SubCommand::with_name("completions") + .about("Generate completion scripts for your shell") + .after_help(COMPLETIONS_HELP) + .setting(AppSettings::ArgRequiredElseHelp) + .arg(Arg::with_name("shell").possible_values(&Shell::variants()))) +} + +static RUSTUP_HELP: &'static str = r" +rustup installs The Rust Programming Language from the official +release channels, enabling you to easily switch between stable, beta, +and nightly compilers and keep them updated. It makes cross-compiling +simpler with binary builds of the standard library for common platforms. + +If you are new to Rust consider running `rustup doc --book` +to learn Rust."; + +static SHOW_HELP: &'static str = r" +Shows the name of the active toolchain and the version of `rustc`. + +If the active toolchain has installed support for additional +compilation targets, then they are listed as well. + +If there are multiple toolchains installed then all installed +toolchains are listed as well."; + +static UPDATE_HELP: &'static str = r" +With no toolchain specified, the `update` command updates each of the +installed toolchains from the official release channels, then updates +rustup itself. + +If given a toolchain argument then `update` updates that toolchain, +the same as `rustup toolchain install`. + +'toolchain' specifies a toolchain name, such as 'stable', 'nightly', +or '1.8.0'. For more information see `rustup help toolchain`."; + +static TOOLCHAIN_INSTALL_HELP: &'static str = r" +Installs a specific rust toolchain. + +The 'install' command is an alias for 'rustup update '. + +'toolchain' specifies a toolchain name, such as 'stable', 'nightly', +or '1.8.0'. For more information see `rustup help toolchain`."; + +static DEFAULT_HELP: &'static str = r" +Sets the default toolchain to the one specified. If the toolchain is +not already installed then it is installed first."; + +static TOOLCHAIN_HELP: &'static str = r" +Many `rustup` commands deal with *toolchains*, a single installation +of the Rust compiler. `rustup` supports multiple types of +toolchains. The most basic track the official release channels: +'stable', 'beta' and 'nightly'; but `rustup` can also install +toolchains from the official archives, for alternate host platforms, +and from local builds. + +Standard release channel toolchain names have the following form: + + [-][-] + + = stable|beta|nightly| + = YYYY-MM-DD + = + +'channel' is either a named release channel or an explicit version +number, such as '1.8.0'. Channel names can be optionally appended with +an archive date, as in 'nightly-2014-12-18', in which case the +toolchain is downloaded from the archive for that date. + +Finally, the host may be specified as a target triple. This is most +useful for installing a 32-bit compiler on a 64-bit platform, or for +installing the [MSVC-based toolchain] on Windows. For example: + + rustup toolchain install stable-x86_64-pc-windows-msvc + +For convenience, elements of the target triple that are omitted will be +inferred, so the above could be written: + + $ rustup default stable-msvc + +Toolchain names that don't name a channel instead can be used to name +custom toolchains with the `rustup toolchain link` command."; + +static OVERRIDE_HELP: &'static str = r" +Overrides configure rustup to use a specific toolchain when +running in a specific directory. + +Directories can be assigned their own Rust toolchain with +`rustup override`. When a directory has an override then +any time `rustc` or `cargo` is run inside that directory, +or one of its child directories, the override toolchain +will be invoked. + +To pin to a specific nightly: + + rustup override set nightly-2014-12-18 + +Or a specific stable release: + + rustup override set 1.0.0 + +To see the active toolchain use `rustup show`. To remove the override +and use the default toolchain again, `rustup override unset`."; + +static OVERRIDE_UNSET_HELP: &'static str = r" +If `--path` argument is present, removes the override toolchain for +the specified directory. If `--nonexistent` argument is present, removes +the override toolchain for all nonexistent directories. Otherwise, +removes the override toolchain for the current directory."; + +static RUN_HELP: &'static str = r" +Configures an environment to use the given toolchain and then runs +the specified program. The command may be any program, not just +rustc or cargo. This can be used for testing arbitrary toolchains +without setting an override. + +Commands explicitly proxied by `rustup` (such as `rustc` and `cargo`) +also have a shorthand for this available. The toolchain can be set by +using `+toolchain` as the first argument. These are equivalent: + + cargo +nightly build + + rustup run nightly cargo build"; + +static DOC_HELP: &'static str = r" +Opens the documentation for the currently active toolchain with the +default browser. + +By default, it opens the documentation index. Use the various flags to +open specific pieces of documentation."; + +static COMPLETIONS_HELP: &'static str = r" +One can generate a completion script for `rustup` that is compatible with +a given shell. The script is output on `stdout` allowing one to re-direct +the output to the file of their choosing. Where you place the file will +depend on which shell, and which operating system you are using. Your +particular configuration may also determine where these scripts need +to be placed. + +Here are some common set ups for the three supported shells under +Unix and similar operating systems (such as GNU/Linux). + +BASH: + +Completion files are commonly stored in `/etc/bash_completion.d/` + +Run the command: + +`rustup completions bash > /etc/bash_completion.d/rustup.bash-completion` + +This installs the completion script. You may have to log out and log +back in to your shell session for the changes to take affect. + +FISH: + +Fish completion files are commonly stored in +`$HOME/.config/fish/completions` + +Run the command: +`rustup completions fish > ~/.config/fish/completions/rustup.fish` + +This installs the completion script. You may have to log out and log +back in to your shell session for the changes to take affect. + +ZSH: + +ZSH completions are commonly stored in any directory listed in your +`$fpath` variable. To use these completions, you must either add the +generated script to one of those directories, or add your own +to this list. + +Adding a custom directory is often the safest best if you're unsure +of which directory to use. First create the directory, for this +example we'll create a hidden directory inside our `$HOME` directory + +`mkdir ~/.zfunc` + +Then add the following lines to your `.zshrc` just before `compinit` + +`fpath+=~/.zfunc` + +Now you can install the completions script using the following command + +`rustup completions zsh > ~/.zfunc/_rustup` + +You must then either log out and log back in, or simply run + +`exec zsh` + +For the new completions to take affect. + +CUSTOM LOCATIONS: + +Alternatively, you could save these files to the place of your choosing, +such as a custom directory inside your $HOME. Doing so will require you +to add the proper directives, such as `source`ing inside your login +script. Consult your shells documentation for how to add such directives. + +POWERSHELL: + +The powershell completion scripts require PowerShell v5.0+ (which comes +Windows 10, but can be downloaded separately for windows 7 or 8.1). + +First, check if a profile has already been set + +`PS C:\> Test-Path $profile` + +If the above command returns `False` run the following + +`PS C:\> New-Item -path $profile -type file --force` + +Now open the file provided by `$profile` (if you used the `New-Item` command +it will be `%USERPROFILE%\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1` + +Next, we either save the completions file into our profile, or into a separate file +and source it inside our profile. To save the completions into our profile simply +use"; From 0a922e5f6111112064a6d6ee69141f85533629e2 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 27 Feb 2017 22:30:33 -0500 Subject: [PATCH 4/9] tests: adds parsing cases to ripgrep bench --- benches/05_ripgrep.rs | 243 ++++++++++++++++++++++++++++++++++++++++++ src/app/parser.rs | 12 --- 2 files changed, 243 insertions(+), 12 deletions(-) diff --git a/benches/05_ripgrep.rs b/benches/05_ripgrep.rs index f7868ccf01a..42a43c92284 100644 --- a/benches/05_ripgrep.rs +++ b/benches/05_ripgrep.rs @@ -24,6 +24,249 @@ fn build_app_long(b: &mut Bencher) { b.iter(|| app_long()); } #[bench] fn parse_clean(b: &mut Bencher) { b.iter(|| app_short().get_matches_from(vec!["rg", "pat"])); } +#[bench] +fn parse_complex(b: &mut Bencher) { + b.iter(|| { + app_short().get_matches_from(vec!["rg", + "pat", + "-cFlN", + "-pqr", + "--null", + "--no-filename", + "--no-messages", + "-SH", + "-C5", + "--follow", + "-e some"]) + }); +} + +#[bench] +fn parse_lots(b: &mut Bencher) { + b.iter(|| { + app_short() + .get_matches_from(vec!["rg", "pat", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some"]) + }); +} const ABOUT: &'static str = " ripgrep (rg) recursively searches your current directory for a regex pattern. diff --git a/src/app/parser.rs b/src/app/parser.rs index 3b8d18803cc..6f4105c1f28 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -160,22 +160,10 @@ impl<'a, 'b> Parser<'a, 'b> let pb = PosBuilder::from_arg(a, i as u64, &mut self.required); self.positionals.insert(i, pb); } else if a.is_set(ArgSettings::TakesValue) { - // TODO: use uniquemap - // if let Some(ref als) = a.s.aliases { - // for &(a, _) in als.iter() { - // self.opt_l.push(a); - // } - // } let mut ob = OptBuilder::from_arg(a, &mut self.required); ob.s.unified_ord = self.flags.len() + self.opts.len(); self.opts.push(ob); } else { - // TODO: use uniquemap - // if let Some(ref als) = a.s.aliases { - // for &(a, _) in als.iter() { - // self.flag_l.push(a); - // } - // } let mut fb = FlagBuilder::from(a); fb.s.unified_ord = self.flags.len() + self.opts.len(); self.flags.push(fb); From 677f323bcefc8ccebfff15cb5fbce4352ed021e2 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 27 Feb 2017 23:36:11 -0500 Subject: [PATCH 5/9] tests: adds tests to ensure borrowed args don't break --- tests/borrowed.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/borrowed.rs diff --git a/tests/borrowed.rs b/tests/borrowed.rs new file mode 100644 index 00000000000..e7a184b5795 --- /dev/null +++ b/tests/borrowed.rs @@ -0,0 +1,19 @@ +extern crate clap; +extern crate regex; + +use clap::{App, Arg, SubCommand}; + +include!("../clap-test.rs"); + +#[test] +fn borrowed_args() { + let arg = Arg::with_name("some").short("s").long("some").help("other help"); + let arg2 = Arg::with_name("some2").short("S").long("some-thing").help("other help"); + let result = App::new("sub_command_negate") + .arg(Arg::with_name("test").index(1)) + .arg(&arg) + .arg(&arg2) + .subcommand(SubCommand::with_name("sub1").arg(&arg)) + .get_matches_from_safe(vec!["prog"]); + assert!(result.is_ok()); +} From 8da0303bc02db5fe047cfc0631a9da41d9dc60f7 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 27 Feb 2017 23:37:25 -0500 Subject: [PATCH 6/9] perf: vastly reduces the amount of cloning when adding non-global args minus when they're added from App::args which is forced to clone --- src/app/mod.rs | 11 ++-- src/app/parser.rs | 86 +++++++++++++++++++++++++----- src/args/arg_builder/base.rs | 1 + src/args/arg_builder/flag.rs | 12 ++++- src/args/arg_builder/option.rs | 28 +++++----- src/args/arg_builder/positional.rs | 26 ++++----- src/args/arg_builder/switched.rs | 1 + src/args/arg_builder/valued.rs | 15 ++++-- 8 files changed, 129 insertions(+), 51 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index f53da523b6b..67683f0e64f 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -6,7 +6,6 @@ mod meta; mod help; // Std -use std::borrow::Borrow; use std::env; use std::ffi::{OsStr, OsString}; use std::fmt; @@ -639,8 +638,8 @@ impl<'a, 'b> App<'a, 'b> { /// # ; /// ``` /// [argument]: ./struct.Arg.html - pub fn arg> + 'a>(mut self, a: A) -> Self { - self.p.add_arg(a.borrow()); + pub fn arg>>(mut self, a: A) -> Self { + self.p.add_arg(a.into()); self } @@ -660,7 +659,7 @@ impl<'a, 'b> App<'a, 'b> { /// [arguments]: ./struct.Arg.html pub fn args(mut self, args: &[Arg<'a, 'b>]) -> Self { for arg in args { - self.p.add_arg(arg); + self.p.add_arg_ref(arg); } self } @@ -683,7 +682,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`Arg`]: ./struct.Arg.html /// [`Arg::from_usage`]: ./struct.Arg.html#method.from_usage pub fn arg_from_usage(mut self, usage: &'a str) -> Self { - self.p.add_arg(&Arg::from_usage(usage)); + self.p.add_arg(Arg::from_usage(usage)); self } @@ -715,7 +714,7 @@ impl<'a, 'b> App<'a, 'b> { if l.is_empty() { continue; } - self.p.add_arg(&Arg::from_usage(l)); + self.p.add_arg(Arg::from_usage(l)); } self } diff --git a/src/app/parser.rs b/src/app/parser.rs index 6f4105c1f28..9253b5dfd96 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -110,8 +110,8 @@ impl<'a, 'b> Parser<'a, 'b> self.gen_completions_to(for_shell, &mut file) } - // actually adds the arguments - pub fn add_arg(&mut self, a: &Arg<'a, 'b>) { + #[inline] + fn debug_asserts(&self, a: &Arg) { debug_assert!(!arg_names!(self).any(|name| name == a.b.name), format!("Non-unique argument name: {} is already in use", a.b.name)); if let Some(l) = a.s.long { @@ -124,11 +124,33 @@ impl<'a, 'b> Parser<'a, 'b> format!("Argument short must be unique\n\n\t-{} is already in use", s)); } + let i = if a.index.is_none() { + (self.positionals.len() + 1) + } else { + a.index.unwrap() as usize + }; + debug_assert!(!self.positionals.contains_key(i), + format!("Argument \"{}\" has the same index as another positional \ + argument\n\n\tPerhaps try .multiple(true) to allow one positional argument \ + to take multiple values", + a.b.name)); + debug_assert!(!(a.is_set(ArgSettings::Required) && a.is_set(ArgSettings::Global)), + format!("Global arguments cannot be required.\n\n\t'{}' is marked as \ + global and required", + a.b.name)); + } + + #[inline] + fn add_conditional_reqs(&mut self, a: &Arg<'a, 'b>) { if let Some(ref r_ifs) = a.r_ifs { for &(arg, val) in r_ifs { self.r_ifs.push((arg, val, a.b.name)); } } + } + + #[inline] + fn add_arg_groups(&mut self, a: &Arg<'a, 'b>) { if let Some(ref grps) = a.b.groups { for g in grps { let mut found = false; @@ -143,24 +165,64 @@ impl<'a, 'b> Parser<'a, 'b> } } } + } + + #[inline] + fn add_reqs(&mut self, a: &Arg<'a, 'b>) { if a.is_set(ArgSettings::Required) { + // If the arg is required, add all it's requirements to master required list + if let Some(ref areqs) = a.b.requires { + for name in areqs.iter().filter(|&&(val, _)| val.is_none()).map(|&(_, name)| name) { + self.required.push(name); + } + } self.required.push(a.b.name); } + } + + // actually adds the arguments + pub fn add_arg(&mut self, a: Arg<'a, 'b>) { + // if it's global we have to clone anyways + if a.is_set(ArgSettings::Global) { + return self.add_arg_ref(&a); + } + self.debug_asserts(&a); + self.add_conditional_reqs(&a); + self.add_arg_groups(&a); + self.add_reqs(&a); if a.index.is_some() || (a.s.short.is_none() && a.s.long.is_none()) { let i = if a.index.is_none() { (self.positionals.len() + 1) } else { a.index.unwrap() as usize }; - debug_assert!(!self.positionals.contains_key(i), - format!("Argument \"{}\" has the same index as another positional \ - argument\n\n\tPerhaps try .multiple(true) to allow one positional argument \ - to take multiple values", - a.b.name)); - let pb = PosBuilder::from_arg(a, i as u64, &mut self.required); + self.positionals.insert(i, PosBuilder::from_arg(a, i as u64)); + } else if a.is_set(ArgSettings::TakesValue) { + let mut ob = OptBuilder::from(a); + ob.s.unified_ord = self.flags.len() + self.opts.len(); + self.opts.push(ob); + } else { + let mut fb = FlagBuilder::from(a); + fb.s.unified_ord = self.flags.len() + self.opts.len(); + self.flags.push(fb); + } + } + // actually adds the arguments but from a borrow (which means we have to do some clonine) + pub fn add_arg_ref(&mut self, a: &Arg<'a, 'b>) { + self.debug_asserts(&a); + self.add_conditional_reqs(&a); + self.add_arg_groups(&a); + self.add_reqs(&a); + if a.index.is_some() || (a.s.short.is_none() && a.s.long.is_none()) { + let i = if a.index.is_none() { + (self.positionals.len() + 1) + } else { + a.index.unwrap() as usize + }; + let pb = PosBuilder::from_arg_ref(a, i as u64); self.positionals.insert(i, pb); } else if a.is_set(ArgSettings::TakesValue) { - let mut ob = OptBuilder::from_arg(a, &mut self.required); + let mut ob = OptBuilder::from(a); ob.s.unified_ord = self.flags.len() + self.opts.len(); self.opts.push(ob); } else { @@ -169,10 +231,6 @@ impl<'a, 'b> Parser<'a, 'b> self.flags.push(fb); } if a.is_set(ArgSettings::Global) { - debug_assert!(!a.is_set(ArgSettings::Required), - format!("Global arguments cannot be required.\n\n\t'{}' is marked as \ - global and required", - a.b.name)); self.global_args.push(a.into()); } } @@ -589,7 +647,7 @@ impl<'a, 'b> Parser<'a, 'b> // done and to recursively call this method { for a in &self.global_args { - sc.p.add_arg(a); + sc.p.add_arg_ref(a); } } sc.p.propogate_globals(); diff --git a/src/args/arg_builder/base.rs b/src/args/arg_builder/base.rs index 95ebe6637d0..1b5e534c223 100644 --- a/src/args/arg_builder/base.rs +++ b/src/args/arg_builder/base.rs @@ -1,3 +1,4 @@ + use args::{ArgSettings, Arg, ArgFlags}; #[derive(Debug, Clone, Default)] diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index eb4f5542ddb..6dc372434cb 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -4,6 +4,7 @@ use std::fmt::{Display, Formatter, Result}; use std::rc::Rc; use std::result::Result as StdResult; use std::ffi::{OsStr, OsString}; +use std::mem; // Third Party use vec_map::{self, VecMap}; @@ -27,8 +28,6 @@ impl<'n, 'e> FlagBuilder<'n, 'e> { impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for FlagBuilder<'a, 'b> { fn from(a: &'z Arg<'a, 'b>) -> Self { - // No need to check for index() or takes_value() as that is handled above - FlagBuilder { b: Base::from(a), s: Switched::from(a), @@ -36,6 +35,15 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for FlagBuilder<'a, 'b> { } } +impl<'a, 'b> From> for FlagBuilder<'a, 'b> { + fn from(mut a: Arg<'a, 'b>) -> Self { + FlagBuilder { + b: mem::replace(&mut a.b, Base::default()), + s: mem::replace(&mut a.s, Switched::default()), + } + } +} + impl<'n, 'e> Display for FlagBuilder<'n, 'e> { fn fmt(&self, f: &mut Formatter) -> Result { if let Some(l) = self.s.long { diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index 0cf37931dbb..75f14a73136 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -3,6 +3,7 @@ use std::fmt::{Display, Formatter, Result}; use std::rc::Rc; use std::result::Result as StdResult; use std::ffi::{OsStr, OsString}; +use std::mem; // Third Party use vec_map::{self, VecMap}; @@ -23,23 +24,26 @@ pub struct OptBuilder<'n, 'e> impl<'n, 'e> OptBuilder<'n, 'e> { pub fn new(name: &'n str) -> Self { OptBuilder { b: Base::new(name), ..Default::default() } } +} - pub fn from_arg(a: &Arg<'n, 'e>, reqs: &mut Vec<&'n str>) -> Self { - // No need to check for .index() as that is handled above - let ob = OptBuilder { +impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for OptBuilder<'n, 'e> { + fn from(a: &'z Arg<'n, 'e>) -> Self { + OptBuilder { b: Base::from(a), s: Switched::from(a), v: Valued::from(a), - }; - // If the arg is required, add all it's requirements to master required list - if a.is_set(ArgSettings::Required) { - if let Some(ref areqs) = a.b.requires { - for r in areqs.iter().filter(|r| r.0.is_none()) { - reqs.push(r.1); - } - } } - ob + } +} + +impl<'n, 'e> From> for OptBuilder<'n, 'e> { + fn from(mut a: Arg<'n, 'e>) -> Self { + a.v.fill_in(); + OptBuilder { + b: mem::replace(&mut a.b, Base::default()), + s: mem::replace(&mut a.s, Switched::default()), + v: mem::replace(&mut a.v, Valued::default()), + } } } diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index 6d9bdf65313..73fde4c1525 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -4,6 +4,7 @@ use std::fmt::{Display, Formatter, Result}; use std::rc::Rc; use std::result::Result as StdResult; use std::ffi::{OsStr, OsString}; +use std::mem; // Third Party use vec_map::{self, VecMap}; @@ -32,10 +33,7 @@ impl<'n, 'e> PosBuilder<'n, 'e> { } } - pub fn from_arg(a: &Arg<'n, 'e>, idx: u64, reqs: &mut Vec<&'n str>) -> Self { - // Create the Positional Argument Builder with each HashSet = None to only - // allocate - // those that require it + pub fn from_arg_ref(a: &Arg<'n, 'e>, idx: u64) -> Self { let mut pb = PosBuilder { b: Base::from(a), v: Valued::from(a), @@ -45,17 +43,21 @@ impl<'n, 'e> PosBuilder<'n, 'e> { (a.v.num_vals.is_some() && a.v.num_vals.unwrap() > 1) { pb.b.settings.set(ArgSettings::Multiple); } - // If the arg is required, add all it's requirements to master required list - if a.is_set(ArgSettings::Required) { - if let Some(ref areqs) = a.b.requires { - for name in areqs.iter().filter(|&&(val, _)| val.is_none()).map(|&(_, name)| name) { - reqs.push(name); - } - } - } pb } + pub fn from_arg(mut a: Arg<'n, 'e>, idx: u64) -> Self { + if a.v.max_vals.is_some() || a.v.min_vals.is_some() || + (a.v.num_vals.is_some() && a.v.num_vals.unwrap() > 1) { + a.b.settings.set(ArgSettings::Multiple); + } + PosBuilder { + b: mem::replace(&mut a.b, Base::default()), + v: mem::replace(&mut a.v, Valued::default()), + index: idx, + } + } + pub fn multiple_str(&self) -> &str { if self.b.settings.is_set(ArgSettings::Multiple) && self.v.val_names.is_none() { "..." diff --git a/src/args/arg_builder/switched.rs b/src/args/arg_builder/switched.rs index 224b2f2b24b..e42586c260a 100644 --- a/src/args/arg_builder/switched.rs +++ b/src/args/arg_builder/switched.rs @@ -1,3 +1,4 @@ + use Arg; #[derive(Debug)] diff --git a/src/args/arg_builder/valued.rs b/src/args/arg_builder/valued.rs index d98e3c34592..93d63cce0b0 100644 --- a/src/args/arg_builder/valued.rs +++ b/src/args/arg_builder/valued.rs @@ -41,14 +41,19 @@ impl<'n, 'e> Default for Valued<'n, 'e> { } } -impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Valued<'n, 'e> { - fn from(a: &'z Arg<'n, 'e>) -> Self { - let mut v = a.v.clone(); - if let Some(ref vec) = a.v.val_names { +impl<'n, 'e> Valued<'n, 'e> { + pub fn fill_in(&mut self) { + if let Some(ref vec) = self.val_names { if vec.len() > 1 { - v.num_vals = Some(vec.len() as u64); + self.num_vals = Some(vec.len() as u64); } } + } +} + +impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Valued<'n, 'e> { + fn from(a: &'z Arg<'n, 'e>) -> Self { + let mut v = a.v.clone(); if let Some(ref vec) = a.v.val_names { if vec.len() > 1 { v.num_vals = Some(vec.len() as u64); From a822ad8c27d7215266b0388118dfe37f07d7d675 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 27 Feb 2017 23:39:34 -0500 Subject: [PATCH 7/9] tests: adds a bench for rustups subcommand parsing --- benches/06_rustup.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/benches/06_rustup.rs b/benches/06_rustup.rs index 35c5fc46ba5..b70996e823e 100644 --- a/benches/06_rustup.rs +++ b/benches/06_rustup.rs @@ -17,6 +17,11 @@ fn build_app(b: &mut Bencher) { b.iter(|| build_cli()); } #[bench] fn parse_clean(b: &mut Bencher) { b.iter(|| build_cli().get_matches_from(vec![""])); } +#[bench] +fn parse_subcommands(b: &mut Bencher) { + b.iter(|| build_cli().get_matches_from(vec!["rustup override add stable"])); +} + pub fn build_cli() -> App<'static, 'static> { App::new("rustup") .version("0.9.0") // Simulating From 5471b67121ae7658a75428f3057e7af7ac179f06 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 27 Feb 2017 23:49:51 -0500 Subject: [PATCH 8/9] chore: updates version to next release num --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7aa58a715d1..e29abea9c0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "clap" -version = "2.20.5" +version = "2.21.0" authors = ["Kevin K. "] exclude = ["examples/*", "clap-test/*", "tests/*", "benches/*", "*.png", "clap-perf/*", "*.dot"] repository = "https://github.com/kbknapp/clap-rs.git" @@ -29,8 +29,8 @@ clippy = { version = "~0.0.112", optional = true } atty = { version = "0.2.2", optional = true } [dev-dependencies] -regex = "~0.1.80" -lazy_static = "~0.2" +regex = "0.2" +lazy_static = "0.2" [features] default = ["suggestions", "color", "wrap_help"] From ceec86d3bdf5ecafa495c2aa4809c716f4a727aa Mon Sep 17 00:00:00 2001 From: Kevin K Date: Tue, 28 Feb 2017 08:46:21 -0500 Subject: [PATCH 9/9] tests: fixes lifetime issue in a test --- clap-test.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/clap-test.rs b/clap-test.rs index faa4b582fb4..9b218172197 100644 --- a/clap-test.rs +++ b/clap-test.rs @@ -13,8 +13,10 @@ mod test { { 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", "")[..], ""); - let right = re.replace_all(&r.as_ref().trim().replace("\r", "")[..], ""); + let ls = l.as_ref().trim().replace("\r", ""); + let rs = r.as_ref().trim().replace("\r", ""); + let left = re.replace_all(&*ls, ""); + let right = re.replace_all(&*rs, ""); let b = left == right; if !b { println!("");