diff --git a/benches/03_complex.rs b/benches/03_complex.rs index 2be8683d7a8..1ed1fa59159 100644 --- a/benches/03_complex.rs +++ b/benches/03_complex.rs @@ -212,7 +212,7 @@ fn parse_sc_positional(b: &mut Bencher) { #[bench] fn parse_complex1(b: &mut Bencher) { - b.iter(|| create_app!().get_matches_from(vec!["", "-ff", "-o", "option1", "arg1", "-O", "fast", "arg2", "--multvals", "one", "two", "three"])); + b.iter(|| create_app!().get_matches_from(vec!["", "-ff", "-o", "option1", "arg1", "-O", "fast", "arg2", "--multvals", "one", "two", "emacs"])); } #[bench] diff --git a/clap-tests/run_tests.py b/clap-tests/run_tests.py index c0eeb81437c..741ec297ee3 100755 --- a/clap-tests/run_tests.py +++ b/clap-tests/run_tests.py @@ -217,14 +217,14 @@ positional present with value: too subcmd NOT present''' -_mult_vals_more = '''error: The argument '--multvals' was supplied more than once, but does not support multiple values +_mult_vals_more = '''error: The argument '--multvals ' was supplied more than once, but does not support multiple occurrences USAGE: \tclaptests --multvals For more information try --help''' -_mult_vals_few = '''error: The argument '--multvals ' requires a value but none was supplied +_mult_vals_few = '''error: The argument '--multvals ' requires 2 values, but 1 was provided USAGE: \tclaptests --multvals diff --git a/src/app/macros.rs b/src/app/macros.rs new file mode 100644 index 00000000000..6973dec5db4 --- /dev/null +++ b/src/app/macros.rs @@ -0,0 +1,130 @@ +macro_rules! remove_overriden { + ($me:ident, $name:expr) => ({ + if let Some(ref o) = $me.opts.iter().filter(|o| &o.name == $name).next() { + if let Some(ref ora) = o.requires { + for a in ora { + vec_remove!($me.required, a); + } + } + if let Some(ref ora) = o.blacklist { + for a in ora { + vec_remove!($me.blacklist, a); + } + } + if let Some(ref ora) = o.overrides { + for a in ora { + vec_remove!($me.overrides, a); + } + } + } else if let Some(ref o) = $me.flags.iter().filter(|f| &f.name == $name).next() { + if let Some(ref ora) = o.requires { + for a in ora { + vec_remove!($me.required, a); + } + } + if let Some(ref ora) = o.blacklist { + for a in ora { + vec_remove!($me.blacklist, a); + } + } + if let Some(ref ora) = o.overrides { + for a in ora { + vec_remove!($me.overrides, a); + } + } + } else if let Some(p) = $me.positionals.values().filter(|p| &&p.name == &$name).next() { + if let Some(ref ora) = p.requires { + for a in ora { + vec_remove!($me.required, a); + } + } + if let Some(ref ora) = p.blacklist { + for a in ora { + vec_remove!($me.blacklist, a); + } + } + if let Some(ref ora) = p.overrides { + for a in ora { + vec_remove!($me.overrides, a); + } + } + } + }) +} + +macro_rules! arg_post_processing( + ($me:ident, $arg:ident, $matcher:ident) => ({ + use args::AnyArg; + // Handle POSIX overrides + if $me.overrides.contains(&$arg.name()) { + if let Some(ref name) = $me.overriden_from($arg.name(), $matcher) { + $matcher.remove(name); + remove_overriden!($me, name); + } + } + if let Some(or) = $arg.overrides() { + for pa in or { + $matcher.remove(pa); + remove_overriden!($me, pa); + $me.overrides.push(pa); + vec_remove!($me.required, pa); + } + } + // Handle conflicts + if let Some(bl) = $arg.blacklist() { + for name in bl { + $me.blacklist.push(name); + vec_remove!($me.overrides, name); + vec_remove!($me.required, name); + } + } + + // Add all required args which aren't already found in matcher to the master + // list + if let Some(reqs) = $arg.requires() { + for n in reqs { + if $matcher.contains(n) { + continue; + } + + $me.required.push(n); + } + } + + _handle_group_reqs!($me, $arg); + }) +); + +macro_rules! _handle_group_reqs{ + ($me:ident, $arg:ident) => ({ + use args::AnyArg; + for grp in $me.groups.values() { + let mut found = false; + for name in grp.args.iter() { + if name == &$arg.name() { + vec_remove!($me.required, name); + if let Some(ref reqs) = grp.requires { + for r in reqs { + $me.required.push(r); + } + } + if let Some(ref bl) = grp.conflicts { + for b in bl { + $me.blacklist.push(b); + } + } + found = true; + break; + } + } + if found { + for name in grp.args.iter() { + if name == &$arg.name() { continue } + vec_remove!($me.required, name); + + $me.blacklist.push(name); + } + } + } + }) +} diff --git a/src/app/mod.rs b/src/app/mod.rs index 191b9994740..2d814af6527 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,5 +1,7 @@ #[allow(dead_code)] mod settings; +#[macro_use] +mod macros; pub use self::settings::AppSettings; @@ -16,16 +18,14 @@ use std::fmt::Display; use yaml_rust::Yaml; use vec_map::VecMap; -use args::{Arg, ArgMatches, MatchedArg, SubCommand}; +use args::{Arg, AnyArg, ArgGroup, ArgMatches, ArgMatcher, SubCommand}; use args::{FlagBuilder, OptBuilder, PosBuilder}; use args::settings::{ArgFlags, ArgSettings}; -use args::{ArgGroup, AnyArg}; use fmt::Format; use self::settings::AppFlags; use suggestions; use errors::{ClapError, ClapErrorType, ClapResult, error_builder}; - const INTERNAL_ERROR_MSG: &'static str = "Fatal internal error. Please consider filing a bug \ report at https://github.com/kbknapp/clap-rs/issues"; @@ -364,7 +364,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { /// Sets a custom usage string to override the auto-generated usage string. /// /// This will be displayed to the user when errors are found in argument parsing, or when you - /// call `ArgMatches::usage()` + /// call `ArgMatcher::usage()` /// /// **NOTE:** You do not need to specify the "USAGE: \n\t" portion, as that will /// still be applied by `clap`, you only need to specify the portion starting @@ -1030,13 +1030,13 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { // after all arguments were parsed, but before any subcommands have been parsed // (so as to // give subcommands their own usage recursively) - fn create_usage(&self, matches: &[&'ar str]) -> ClapResult { + fn create_usage(&self, matcher: &[&'ar str]) -> ClapResult { let mut usage = String::with_capacity(75); usage.push_str("USAGE:\n\t"); if let Some(u) = self.usage_str { usage.push_str(u); - } else if !matches.is_empty() { - try!(self.usage_from_matches(&mut usage, matches)); + } else if !matcher.is_empty() { + try!(self.usage_from_matcher(&mut usage, matcher)); } else { usage.push_str(&*self.usage .as_ref() @@ -1094,10 +1094,10 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { // Creates a context aware usage string, or "smart usage" from currently used // args, and // requirements - fn usage_from_matches(&self, usage: &mut String, matches: &[&'ar str]) -> ClapResult<()> { + fn usage_from_matcher(&self, usage: &mut String, matcher: &[&'ar str]) -> ClapResult<()> { use std::fmt::Write; let mut hs: Vec<&str> = self.required.iter().map(|n| *n).collect(); - for n in matches { + for n in matcher { hs.push(*n); } let reqs = self.get_required_from(&hs, None); @@ -1605,7 +1605,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { // before parsing incase we run into a subcommand self.propogate_globals(); - let mut matches = ArgMatches::new(); + let mut matcher = ArgMatcher::new(); let mut it = itr.into_iter(); // Get the name of the program (argument 1 of env::args()) and determine the @@ -1629,17 +1629,17 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { } // do the real parsing - if let Err(e) = self.get_matches_with(&mut matches, &mut it, lossy) { + if let Err(e) = self.get_matches_with(&mut matcher, &mut it, lossy) { return Err(e); } - Ok(matches) + Ok(matcher.into()) } // The actual parsing function #[cfg_attr(feature="lints", allow(while_let_on_iterator))] fn get_matches_with(&mut self, - matches: &mut ArgMatches<'ar, 'ar>, + matcher: &mut ArgMatcher<'ar>, it: &mut I, lossy: bool) -> ClapResult<()> @@ -1654,7 +1654,6 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { let mut subcmd_name: Option = None; let mut needs_val_of: Option<&str> = None; let mut pos_counter = 1; - let mut val_counter = 0; while let Some(arg) = it.next() { let arg_cow = match arg.as_ref().to_str() { Some(s) => s.into(), @@ -1662,377 +1661,166 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { if !lossy { return Err( error_builder::InvalidUnicode( - &*try!(self.create_current_usage(matches))) + &*try!(self.create_current_usage(matcher))) ); } arg.as_ref().to_string_lossy() } }; let arg_slice: &str = arg_cow.borrow(); - let mut skip = false; // we need to know if we're parsing a new argument, or the value of previous // argument, // perhaps one with multiple values such as --option val1 val2. We determine // when to // stop parsing multiple values by finding a '-' - let new_arg = if arg_slice.starts_with("-") { - // If we come to a single `-` it's a value, not a new argument...this happens - // when - // one wants to use the Unix standard of '-' to mean 'stdin' - !(arg_slice.len() == 1) - } else { - false - }; + // let new_arg = if arg_slice.starts_with("-") { + // // If we come to a single `-` it's a value, not a new argument...this happens + // // when + // // one wants to use the Unix standard of '-' to mean 'stdin' + // arg_slice.len() > 1 + // } else { + // true + // }; // pos_only is determined later, and set true when a user uses the Unix // standard of // '--' to mean only positionals follow - if !pos_only && !new_arg && - !self.subcommands.iter().any(|s| s.name_slice == arg_slice) { - // Check to see if parsing a value from an option - if let Some(nvo) = needs_val_of { - // get the OptBuilder so we can check the settings - if let Some(ref opt) = self.opts.iter().filter(|o| &o.name == &nvo).next() { - try!(opt.validate_value(arg_slice, matches, self)); - - if let Some(ref vec) = self.groups_for_arg(opt.name) { - for grp in vec { - if let Some(ref mut o) = matches.args.get_mut(grp) { - o.occurrences = if opt.settings - .is_set(&ArgSettings::Multiple) { - o.occurrences + 1 - } else { - 1 - }; - // Values must be inserted in order...the user may care about - // that! - if let Some(ref mut vals) = o.values { - let len = vals.len() as u8 + 1; - vals.insert(len, arg_slice.to_owned()); - } - } + // If the user passed `--` we don't check for subcommands, because the argument + // they + // may be trying to pass might match a subcommand name - } - } - // save the value to matched option - if let Some(ref mut o) = matches.args.get_mut(opt.name) { - // if it's multiple; the occurrences are increased when originally - // found - o.occurrences = if opt.settings.is_set(&ArgSettings::Multiple) { - o.occurrences + 1 - } else { - skip = true; - 1 - }; - - // Options always have values, even if it's empty, so we can unwrap() - if let Some(ref mut vals) = o.values { - - // Values must be inserted in order...the user may care about that! - let len = vals.len() as u8 + 1; - vals.insert(len, arg_slice.to_owned()); - - // Now that the values have been added, we can ensure we haven't - // gone over any max_limits, or if we've reached the exact number - // of values we can stop parsing values, and go back to arguments. - // - // For example, if we define an option with exactly 2 values and - // the users passes: - // $ my_prog --option val1 val2 pos1 - // we stop parsing values of --option after val2, if the user - // hadn't defined an exact or max value, pos1 would be parsed as a - // value of --option - if let Some(num) = opt.max_vals { - if len != num { - continue; - } - } else if let Some(num) = opt.num_vals { - if opt.settings.is_set(&ArgSettings::Multiple) { - val_counter += 1; - if val_counter != num { - continue; - } else { - val_counter = 0; - } - } else { - // if we need more values, get the next value - if len != num { - continue; - } - } - } else if !skip { - // get the next value from the iterator - continue; - } - } + let starts_new_arg = if arg_slice.starts_with("-") { + !(arg_slice.len() == 1) + } else { + false + }; + if !pos_only { + if !starts_new_arg { + // Check to see if parsing a value from an option + if let Some(nvo) = needs_val_of { + // get the OptBuilder so we can check the settings + if let Some(opt) = self.opts.iter().filter(|o| &o.name == &nvo).next() { + needs_val_of = try!(self.add_val_to_arg(opt, arg_slice, matcher)); + // get the next value from the iterator + continue; } - skip = true; } } - } - - // if we're done getting values from an option, get the next arg from the - // iterator - if skip { - needs_val_of = None; - continue; - } else if let Some(ref name) = needs_val_of { - // We've reached more values for an option than it possibly accepts - if let Some(ref o) = self.opts.iter().filter(|o| &o.name == name).next() { - if !o.settings.is_set(&ArgSettings::Multiple) { - return Err(error_builder::EmptyValue( - &*o.to_string(), - &*try!(self.create_current_usage(matches)) - )); + let mut skip = false; + if arg_slice.starts_with("--") { + if arg_slice.len() == 2 { + // The user has passed '--' which means only positional args follow no matter + // what they start with + pos_only = true; + continue; } - } - } - - if arg_slice.starts_with("--") && !pos_only { - if arg_slice.len() == 2 { - // The user has passed '--' which means only positional args follow no matter - // what they start with - pos_only = true; - continue; - } - // This arg is either an option or flag using a long (i.e. '--something') - match self.parse_long_arg(matches, arg_slice) { - Ok(r) => needs_val_of = r, - Err(e) => return Err(e), - } - } else if arg_slice.starts_with("-") && arg_slice.len() != 1 && !pos_only { - // Multiple or single flag(s), or single option (could be '-SbG' or '-o') - match self.parse_short_arg(matches, arg_slice) { - Ok(r) => needs_val_of = r, - Err(e) => return Err(e), + needs_val_of = try!(self.parse_long_arg(matcher, arg_slice)); + } else if arg_slice.starts_with("-") && arg_slice.len() != 1 { + needs_val_of = try!(self.parse_short_arg(matcher, arg_slice)); + } else { + skip = true; } - } else { - // Positional or Subcommand - // - // If the user passed `--` we don't check for subcommands, because the argument - // they - // may be trying to pass might match a subcommand name - if !pos_only { - if self.subcommands.iter().any(|s| s.name_slice == arg_slice) { - if arg_slice == "help" && - self.settings.is_set(&AppSettings::NeedsSubcommandHelp) { - try!(self.print_help()); - return Err(ClapError { - error: String::new(), - error_type: ClapErrorType::HelpDisplayed, - }); - } - subcmd_name = Some(arg_slice.to_owned()); - break; - } else if let Some(candidate) = suggestions::did_you_mean( - arg_slice, - self.subcommands - .iter() - .map(|s| &s.name)) { - return Err(error_builder::InvalidSubcommand( - arg_slice, - candidate, - self.bin_name.as_ref().unwrap_or(&self.name), - &*try!(self.create_current_usage(matches)))); + if !skip { continue; } + if self.subcommands.iter().any(|s| s.name_slice == arg_slice) { + if arg_slice == "help" && + self.settings.is_set(&AppSettings::NeedsSubcommandHelp) { + return self._help(); } + subcmd_name = Some(arg_slice.to_owned()); + break; + } else if let Some(candidate) = suggestions::did_you_mean( + arg_slice, + self.subcommands.iter().map(|s| &s.name)) { + return Err(error_builder::InvalidSubcommand( + arg_slice, + candidate, + self.bin_name.as_ref().unwrap_or(&self.name), + &*try!(self.create_current_usage(matcher)))); } + } - // Did the developer even define any valid positionals? Since we reached this - // far, it's not a subcommand - if self.positionals.is_empty() { - return Err(error_builder::UnexpectedArgument( - arg_slice, - self.bin_name.as_ref().unwrap_or(&self.name), - &*try!(self.create_current_usage(matches)))); - } else if let Some(p) = self.positionals.get(&pos_counter) { - // Make sure this one doesn't conflict with anything - if self.blacklist.contains(&p.name) { - return Err(error_builder::ArgumentConflict( - p.to_string(), - self.blacklisted_from(p.name, &matches), - try!(self.create_current_usage(matches)))); - } - - if let Some(ref p_vals) = p.possible_vals { - if !p_vals.contains(&arg_slice) { - return Err( - error_builder::InvalidValue(arg_slice, - p_vals, - &p.to_string(), - &*try!(self.create_current_usage(matches)))); - } - } - // Have we made the update yet? - let mut done = false; - if p.settings.is_set(&ArgSettings::Multiple) { - if let Some(num) = p.num_vals { - if let Some(ref ma) = matches.args.get(p.name) { - if let Some(ref vals) = ma.values { - if vals.len() as u8 == num { - return Err(error_builder::TooManyValues( - arg_slice, - &*p.to_string(), - &*try!(self.create_current_usage(matches)))); - } - } - } - } - // Check if it's already existing and update if so... - if let Some(ref mut pos) = matches.args.get_mut(p.name) { - done = true; - pos.occurrences += 1; - if let Some(ref mut vals) = pos.values { - let len = (vals.len() + 1) as u8; - vals.insert(len, arg_slice.to_owned()); - } - } + if let Some(p) = self.positionals.get(&pos_counter) { + try!(self.validate_arg(p, matcher)); - if !pos_only && - (self.settings.is_set(&AppSettings::TrailingVarArg) && - pos_counter == self.positionals.len()) { - pos_only = true; - } - } else { - // Only increment the positional counter if it doesn't allow multiples - pos_counter += 1; - } - // Was an update made, or is this the first occurrence? - if !done { - if self.overrides.contains(&p.name) { - if let Some(ref name) = self.overriden_from(p.name, matches) { - matches.args.remove(name); - remove_overriden!(self, name); - } - } - if let Some(ref or) = p.overrides { - for pa in or { - matches.args.remove(pa); - remove_overriden!(self, pa); - self.overrides.push(pa); - } - } - let mut bm = BTreeMap::new(); - if let Some(ref vtor) = p.validator { - let f = &*vtor; - if let Err(ref e) = f(arg_slice.to_owned()) { - return Err(error_builder::ValueValidationError(&*e)); - } - } - bm.insert(1, arg_slice.to_owned()); - if let Some(ref vec) = self.groups_for_arg(p.name) { - for grp in vec { - matches.args.insert(grp, - MatchedArg { - occurrences: 1, - values: Some(bm.clone()), - }); - } - } - matches.args.insert(p.name, - MatchedArg { - occurrences: 1, - values: Some(bm), - }); - - if let Some(ref bl) = p.blacklist { - for name in bl { - self.blacklist.push(name); - } - } - - // Because of the macro call, we have to create a temp variable - if let Some(ref reqs) = p.requires { - // Add all required args which aren't already found in matches to the - // final required list - for n in reqs { - if matches.args.contains_key(n) { - continue; - } - - self.required.push(n); - } - } + try!(self.add_val_to_arg(p, arg_slice, matcher)); - if p.settings.is_set(&ArgSettings::Required) { - // for macro call - let name = &p.name; - vec_remove!(self.required, name); - } + if !pos_only && + (self.settings.is_set(&AppSettings::TrailingVarArg) && + pos_counter == self.positionals.len()) { + pos_only = true; + } + arg_post_processing!(self, p, matcher); - parse_group_reqs!(self, p); - } - } else { - return Err(error_builder::UnexpectedArgument( - arg_slice, - self.bin_name.as_ref().unwrap_or(&self.name), - &*try!(self.create_current_usage(matches)))); + // Only increment the positional counter if it doesn't allow multiples + if !p.settings.is_set(&ArgSettings::Multiple) { + pos_counter += 1; } + } else { + return Err(error_builder::UnexpectedArgument( + arg_slice, + self.bin_name.as_ref().unwrap_or(&self.name), + &*try!(self.create_current_usage(matcher)))); } } - if let Some(ref a) = needs_val_of { - if let Some(o) = self.opts.iter().filter(|o| &o.name == a).next() { - if o.settings.is_set(&ArgSettings::Multiple) && self.required.is_empty() { - let should_err = match matches.values_of(o.name) { + if let Some(a) = needs_val_of { + if let Some(o) = self.opts.iter().filter(|o| &o.name == &a).next() { + if (o.settings.is_set(&ArgSettings::Multiple) && self.required.is_empty()) || + !o.settings.is_set(&ArgSettings::Multiple){ + let should_err = match matcher.values_of(o.name) { Some(ref v) => v.is_empty(), None => true, }; if should_err { return Err(error_builder::EmptyValue( &*o.to_string(), - &*try!(self.create_current_usage(matches)) + &*try!(self.create_current_usage(matcher)) )); } - } else if !o.settings.is_set(&ArgSettings::Multiple) { - return Err(error_builder::EmptyValue( - &*o.to_string(), - &*try!(self.create_current_usage(matches)) - )); } else { - debugln!("Remaining Required Arg..."); - debugln!("required={:#?}", self.required); return Err(error_builder::MissingRequiredArgument( - self.get_required_from(&self.required, Some(matches)) + &*self.get_required_from(&self.required, Some(matcher)) .iter() .fold(String::new(), |acc, s| acc + &format!("\n\t{}", Format::Error(s))[..]), - try!(self.create_current_usage(matches)))); + &*try!(self.create_current_usage(matcher)))); } } else { return Err(error_builder::EmptyValue(&*format!("{}", self.positionals .values() - .filter(|p| &p.name == a) + .filter(|p| &p.name == &a) .next() - .unwrap()), - &*try!(self.create_current_usage(matches)))); + .expect(INTERNAL_ERROR_MSG)), + &*try!(self.create_current_usage(matcher)))); } } - let res = self.validate_blacklist(matches); - if res.is_err() { - return res; + if let Err(e) = self.validate_blacklist(matcher) { + return Err(e); } - let res = self.validate_num_args(matches); - if res.is_err() { - return res; + if let Err(e) = self.validate_num_args(matcher) { + return Err(e); } - matches.usage = Some(try!(self.create_usage(&[]))); + matcher.usage(try!(self.create_usage(&[]))); + if !(self.settings.is_set(&AppSettings::SubcommandsNegateReqs) && subcmd_name.is_some()) { + try!(self.validate_required(&matcher)); + } if let Some(sc_name) = subcmd_name { use std::fmt::Write; let mut mid_string = String::new(); if !self.settings.is_set(&AppSettings::SubcommandsNegateReqs) { let mut hs = self.required.iter().map(|n| *n).collect::>(); - for k in matches.args.keys() { + for k in matcher.arg_names() { hs.push(*k); } - let reqs = self.get_required_from(&hs, Some(matches)); + let reqs = self.get_required_from(&hs, Some(matcher)); for s in reqs.iter() { write!(&mut mid_string, " {}", s).ok().expect(INTERNAL_ERROR_MSG); @@ -2043,7 +1831,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { .iter_mut() .filter(|s| s.name_slice == &sc_name) .next() { - let mut new_matches = ArgMatches::new(); + let mut sc_matcher = ArgMatcher::new(); // bin_name should be parent's bin_name + [] + the sc's name separated by // a space sc.usage = Some(format!("{}{}{}", @@ -2062,18 +1850,18 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { "" }, &*sc.name)); - if let Err(e) = sc.get_matches_with(&mut new_matches, it, lossy) { + if let Err(e) = sc.get_matches_with(&mut sc_matcher, it, lossy) { e.exit(); } - matches.subcommand = Some(Box::new(SubCommand { + matcher.subcommand(SubCommand { name: sc.name_slice, - matches: new_matches, - })); + matches: sc_matcher.into(), + }); } } else if self.settings.is_set(&AppSettings::SubcommandRequired) { let bn = self.bin_name.as_ref().unwrap_or(&self.name); return Err(error_builder::MissingSubcommand(bn, - &try!(self.create_current_usage(matches)))); + &try!(self.create_current_usage(matcher)))); } else if self.settings.is_set(&AppSettings::SubcommandRequiredElseHelp) { let mut out = vec![]; try!(self.write_help(&mut out)); @@ -2082,16 +1870,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { error_type: ClapErrorType::MissingArgumentOrSubcommand, }); } - if (!self.settings.is_set(&AppSettings::SubcommandsNegateReqs) || - matches.subcommand_name().is_none()) && self.validate_required(&matches) { - return Err(error_builder::MissingRequiredArgument( - &*self.get_required_from(&self.required, Some(matches)) - .iter() - .fold(String::new(), - |acc, s| acc + &format!("\n\t{}", Format::Error(s))[..]), - &*try!(self.create_current_usage(matches)))); - } - if matches.args.is_empty() && matches.subcommand_name().is_none() && + if matcher.is_empty() && matcher.subcommand_name().is_none() && self.settings.is_set(&AppSettings::ArgRequiredElseHelp) { let mut out = vec![]; try!(self.write_help(&mut out)); @@ -2137,9 +1916,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { } #[doc(hidden)] - pub fn create_current_usage(&self, matches: &ArgMatches) -> ClapResult { - self.create_usage(&matches.args - .keys() + pub fn create_current_usage(&self, matcher: &ArgMatcher) -> ClapResult { + self.create_usage(&matcher.arg_names() .filter(|k| { if let Some(o) = self.opts .iter() @@ -2245,7 +2023,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { fn get_required_from(&self, reqs: &[&'ar str], - matches: Option<&ArgMatches>) + matcher: Option<&ArgMatcher>) -> VecDeque { let mut c_flags = vec![]; let mut c_pos = vec![]; @@ -2336,7 +2114,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { let mut pmap = BTreeMap::new(); for p in c_pos.into_iter() { - if matches.is_some() && matches.as_ref().unwrap().is_present(p) { + if matcher.is_some() && matcher.as_ref().unwrap().contains(p) { continue; } if let Some(p) = self.positionals.values().filter(|x| &x.name == p).next() { @@ -2347,7 +2125,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { ret_val.push_back(s); } for f in c_flags.into_iter() { - if matches.is_some() && matches.as_ref().unwrap().is_present(f) { + if matcher.is_some() && matcher.as_ref().unwrap().contains(f) { continue; } ret_val.push_back(format!("{}", @@ -2358,7 +2136,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { .unwrap())); } for o in c_opt.into_iter() { - if matches.is_some() && matches.as_ref().unwrap().is_present(o) { + if matcher.is_some() && matcher.as_ref().unwrap().contains(o) { continue; } ret_val.push_back(format!("{}", @@ -2437,8 +2215,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { } } - fn blacklisted_from(&self, name: &str, matches: &ArgMatches) -> Option { - for k in matches.args.keys() { + fn blacklisted_from(&self, name: &str, matcher: &ArgMatcher) -> Option { + for k in matcher.arg_names() { if let Some(f) = self.flags.iter().filter(|f| &f.name == k).next() { if let Some(ref bl) = f.blacklist { if bl.contains(&name) { @@ -2464,8 +2242,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { None } - fn overriden_from(&self, name: &'ar str, matches: &ArgMatches) -> Option<&'ar str> { - for k in matches.args.keys() { + fn overriden_from(&self, name: &'ar str, matcher: &ArgMatcher) -> Option<&'ar str> { + for k in matcher.arg_names() { if let Some(f) = self.flags.iter().filter(|f| &f.name == k).next() { if let Some(ref bl) = f.overrides { if bl.contains(&name) { @@ -2537,636 +2315,279 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { } } - fn check_for_help_and_version(&self, arg: char) -> ClapResult<()> { - if let Some(h) = self.help_short { - if h == arg { - try!(self.print_help()); - return Err(ClapError { - error: String::new(), - error_type: ClapErrorType::HelpDisplayed, - }); - } + fn check_for_help_and_version_str(&self, arg: &str) -> ClapResult<()> { + if arg == "help" && self.settings.is_set(&AppSettings::NeedsLongHelp) { + try!(self._help()); } - if let Some(v) = self.version_short { - if v == arg { - let out = io::stdout(); - let mut buf_w = BufWriter::new(out.lock()); - try!(self.print_version(&mut buf_w)); - return Err(ClapError { - error: String::new(), - error_type: ClapErrorType::VersionDisplayed, - }); - } + if arg == "version" && self.settings.is_set(&AppSettings::NeedsLongVersion) { + try!(self._version()); } Ok(()) } - fn parse_long_arg<'av>(&mut self, - matches: &mut ArgMatches<'ar, 'ar>, - full_arg: &'av str) - -> ClapResult> { - let mut arg = full_arg.trim_left_matches(|c| c == '-'); - - if arg == "help" && self.settings.is_set(&AppSettings::NeedsLongHelp) { - try!(self.print_help()); - return Err(ClapError { - error: String::new(), - error_type: ClapErrorType::HelpDisplayed, - }); - } else if arg == "version" && self.settings.is_set(&AppSettings::NeedsLongVersion) { - let out = io::stdout(); - let mut buf_w = BufWriter::new(out.lock()); - try!(self.print_version(&mut buf_w)); - return Err(ClapError { - error: String::new(), - error_type: ClapErrorType::VersionDisplayed, - }); - } + fn check_for_help_and_version_char(&self, arg: char) -> ClapResult<()> { + if let Some(h) = self.help_short { if arg == h { try!(self._help()); } } + if let Some(v) = self.version_short { if arg == v { try!(self._version()); } } + Ok(()) + } - let mut arg_val: Option<&'av str> = None; + fn _help(&self) -> ClapResult<()> { + try!(self.print_help()); + Err(ClapError { + error: String::new(), + error_type: ClapErrorType::HelpDisplayed, + }) + } - if arg.contains("=") { - let arg_vec: Vec<_> = arg.split("=").collect(); - arg = arg_vec[0]; - if let Some(ref v) = self.opts - .iter() - .filter(|&v| v.long.is_some() && v.long.unwrap() == arg) - .next() { - // prevents "--config= value" typo - if arg_vec[1].len() == 0 && !v.settings.is_set(&ArgSettings::EmptyValues) { - if let Some(ref vec) = self.groups_for_arg(v.name) { - for grp in vec { - matches.args.insert(grp, - MatchedArg { - occurrences: 1, - values: None, - }); - } - } - matches.args.insert(v.name, - MatchedArg { - occurrences: 1, - values: None, - }); - return Err(error_builder::EmptyValue( - &*format!("--{}", arg), - &*try!(self.create_current_usage(matches)) - )); - } - arg_val = Some(arg_vec[1]); - } - } + fn _version(&self) -> ClapResult<()> { + let out = io::stdout(); + let mut buf_w = BufWriter::new(out.lock()); + try!(self.print_version(&mut buf_w)); + Err(ClapError { + error: String::new(), + error_type: ClapErrorType::VersionDisplayed, + }) + } - if let Some(ref v) = self.opts - .iter() - .filter(|&v| v.long.is_some() && v.long.unwrap() == arg) - .next() { - // Ensure this option isn't on the master mutually excludes list - if self.blacklist.contains(&v.name) { - matches.args.remove(v.name); - return Err(error_builder::ArgumentConflict( - format!("--{}", arg), - self.blacklisted_from(v.name, &matches), - try!(self.create_current_usage(matches)))); - } - if self.overrides.contains(&v.name) { - debugln!("it is..."); - debugln!("checking who defined it..."); - if let Some(ref name) = self.overriden_from(v.name, matches) { - debugln!("found {}", name); - if let Some(ref vec) = self.groups_for_arg(v.name) { - for grp in vec { - matches.args.remove(grp); - } - } - matches.args.remove(name); - remove_overriden!(self, name); - } - } - if let Some(ref or) = v.overrides { - for pa in or { - if let Some(ref vec) = self.groups_for_arg(v.name) { - for grp in vec { - matches.args.remove(grp); - } - } - matches.args.remove(pa); - remove_overriden!(self, pa); - self.overrides.push(pa); - } - } + fn parse_long_arg<'av>(&mut self, + matcher: &mut ArgMatcher<'ar>, + full_arg: &'av str) + -> ClapResult> { + let mut val = ""; + let arg = if full_arg.contains("=") { + let parts: Vec<_> = full_arg.trim_left_matches(|c| c == '-').splitn(2, "=").collect(); + val = parts[1]; + parts[0] + } else { + full_arg.trim_left_matches(|c| c == '-') + }; - if matches.args.contains_key(v.name) { - if !v.settings.is_set(&ArgSettings::Multiple) { - return Err(error_builder::UnexpectedMultipleUsage( - &*format!("--{}", arg), - &*try!(self.create_current_usage(matches)))); - } - if let Some(av) = arg_val { - if let Some(ref vec) = self.groups_for_arg(v.name) { - for grp in vec { - if let Some(ref mut o) = matches.args.get_mut(grp) { - o.occurrences += 1; - if let Some(ref mut vals) = o.values { - let len = (vals.len() + 1) as u8; - vals.insert(len, av.to_owned()); - } - } - } - } - if let Some(ref mut o) = matches.args.get_mut(v.name) { - o.occurrences += 1; - if let Some(ref mut vals) = o.values { - let len = (vals.len() + 1) as u8; - vals.insert(len, av.to_owned()); - } - } - // The validation must come AFTER inserting into 'matches' or the usage string - // can't be built - if let Err(e) = self.validate_value(v, av, matches) { - return Err(e); - } - } - } else { - let mut bm = BTreeMap::new(); - if let Some(val) = arg_val { - bm.insert(1, val.to_owned()); - if let Some(ref vec) = self.groups_for_arg(v.name) { - for grp in vec { - matches.args.insert(grp, - MatchedArg { - occurrences: 1, - values: Some(bm.clone()), - }); - } - } - matches.args.insert(v.name, - MatchedArg { - occurrences: 1, - values: Some(bm), - }); - // The validation must come AFTER inserting into 'matches' or the usage string - // can't be built - if let Err(e) = self.validate_value(v, val, matches) { - return Err(e); - } - } else { - if let Some(ref vec) = self.groups_for_arg(v.name) { - for grp in vec { - matches.args.insert(grp, - MatchedArg { - occurrences: 1, - values: Some(bm.clone()), - }); - } - } - matches.args.insert(v.name, - MatchedArg { - occurrences: 0, - values: Some(bm), - }); - } - } - if let Some(ref bl) = v.blacklist { - for name in bl { - self.blacklist.push(name); - vec_remove!(self.overrides, name); - vec_remove!(self.required, name); - } - } + if let Some(opt) = self.opts + .iter() + .filter(|v| v.long.is_some() && v.long.unwrap() == arg) + .next() { + let ret = try!(self.parse_opt(val, opt, matcher)); + arg_post_processing!(self, opt, matcher); - if let Some(ref reqs) = v.requires { - // Add all required args which aren't already found in matches to the - // final required list - for n in reqs { - if matches.args.contains_key(n) { - continue; - } + return Ok(ret); + } else if let Some(flag) = self.flags + .iter() + .filter(|v| v.long.is_some() && v.long.unwrap() == arg) + .next() { + // 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)); - self.required.push(n); - } - } + try!(self.parse_flag(flag, matcher)); - parse_group_reqs!(self, v); + // Handle conflicts, requirements, etc. + arg_post_processing!(self, flag, matcher); - match arg_val { - None => { - return Ok(Some(v.name)); - } - _ => { - return Ok(None); - } - } + return Ok(None); } - if let Some(v) = self.flags - .iter() - .filter(|&v| v.long.is_some() && v.long.unwrap() == arg) - .next() { - // Ensure this flag isn't on the mutually excludes list - if self.blacklist.contains(&v.name) { - matches.args.remove(v.name); - return Err(error_builder::ArgumentConflict( - v.to_string(), - self.blacklisted_from(v.name, &matches), - try!(self.create_current_usage(matches)))); - } - if self.overrides.contains(&v.name) { - debugln!("it is..."); - debugln!("checking who defined it..."); - if let Some(ref name) = self.overriden_from(v.name, matches) { - debugln!("found {}", name); - matches.args.remove(name); - remove_overriden!(self, name); - } - } - if let Some(ref or) = v.overrides { - for pa in or { - matches.args.remove(pa); - remove_overriden!(self, pa); - self.overrides.push(pa); - } - } + self.did_you_mean_error(arg, matcher).map(|_| None) + } - // Make sure this isn't one being added multiple times if it doesn't suppor it - if matches.args.contains_key(v.name) && !v.settings.is_set(&ArgSettings::Multiple) { - return Err(error_builder::UnexpectedMultipleUsage( - &*v.to_string(), - &*try!(self.create_current_usage(matches)))); - } + fn parse_short_arg(&mut self, + matcher: &mut ArgMatcher<'ar>, + full_arg: &str) + -> ClapResult> { + let arg = &*full_arg.trim_left_matches(|c| c == '-'); + for c in arg.chars() { + // Check for matching short options, and return the name if there is no trailing + // concatenated value: -oval + // Option: -o + // Value: val + if let Some(opt) = self.opts + .iter() + .filter(|&v| v.short.is_some() && v.short.unwrap() == c) + .next() { + // Check for trailing concatenated value + let val = arg.splitn(2, c).collect::>()[1]; + + // Default to "we're expecting a value later" + let ret = try!(self.parse_opt(val, opt, matcher)); + + arg_post_processing!(self, opt, matcher); - let mut done = false; - if let Some(ref mut f) = matches.args.get_mut(v.name) { - done = true; - f.occurrences = if v.settings.is_set(&ArgSettings::Multiple) { - f.occurrences + 1 - } else { - 1 - }; - } - if !done { - if let Some(ref vec) = self.groups_for_arg(v.name) { - for grp in vec { - matches.args.insert(grp, - MatchedArg { - occurrences: 1, - values: None, - }); - } - } - matches.args.insert(v.name, - MatchedArg { - occurrences: 1, - values: None, - }); + return Ok(ret); } - - // Add all of this flags "mutually excludes" list to the master list - if let Some(ref ov) = v.overrides { - for name in ov { - self.overrides.push(name); - } - } - if let Some(ref bl) = v.blacklist { - for name in bl { - self.blacklist.push(name); - vec_remove!(self.overrides, name); - vec_remove!(self.required, name); - } + try!(self.check_for_help_and_version_char(c)); + if let Some(flag) = self.flags.iter().filter(|&v| v.short.is_some() && v.short.unwrap() == c).next() { + // Only flags can be help or version + try!(self.parse_flag(flag, matcher)); + // Handle conflicts, requirements, overrides, etc. + // Must be called here due to mutablilty + arg_post_processing!(self, flag, matcher); } + } + Ok(None) + } - // Add all required args which aren't already found in matches to the master - // list - if let Some(ref reqs) = v.requires { - for n in reqs { - if matches.args.contains_key(n) { - continue; - } + fn parse_opt(&self, val: &str, opt: &OptBuilder<'ar>, matcher: &mut ArgMatcher<'ar>) -> ClapResult> { + try!(self.validate_arg(opt, matcher)); - self.required.push(n); - } - } + if matcher.contains(opt.name) && !opt.settings.is_set(&ArgSettings::Multiple) { + // Not the first time, but we don't allow multiples + return Err(error_builder::UnexpectedMultipleUsage( + &*opt.to_string(), + &*try!(self.create_current_usage(matcher)))); + } - parse_group_reqs!(self, v); + if val.len() != 0 { + if opt.is_set(ArgSettings::EmptyValues) { + try!(self.add_val_to_arg(opt, val, matcher)); + return Ok(None); + } + return Err(error_builder::EmptyValue( + &*opt.to_string(), + &*try!(self.create_current_usage(matcher)))); + } - return Ok(None); + // If it doesn't allow mutliples, (or a specific number of values), or we don't have any + // values yet, we want to return the name of this arg so the next arg is parsed as a value + // otherwise we're done getting values + if opt.settings.is_set(&ArgSettings::Multiple) || opt.num_vals.is_some() || !matcher.contains(opt.name) { + return Ok(Some(opt.name)); } + Ok(None) + } - let suffix = suggestions::did_you_mean_suffix(arg, - self.long_list.iter(), - suggestions::DidYouMeanMessageStyle::LongFlag); - if let Some(name) = suffix.1 { - if let Some(ref opt) = self.opts - .iter() - .filter_map(|o| { - if o.long.is_some() && o.long.unwrap() == name { - Some(o.name) - } else { - None - } - }) - .next() { - if let Some(ref vec) = self.groups_for_arg(opt) { - for grp in vec { - matches.args.insert(grp, - MatchedArg { - occurrences: 1, - values: None, - }); - } - } - matches.args.insert(opt, - MatchedArg { - occurrences: 0, - values: None, - }); - } else if let Some(ref flg) = self.flags - .iter() - .filter_map(|f| { - if f.long.is_some() && f.long.unwrap() == name { - Some(f.name) - } else { - None - } - }) - .next() { - if let Some(ref vec) = self.groups_for_arg(flg) { - for grp in vec { - matches.args.insert(grp, - MatchedArg { - occurrences: 1, - values: None, - }); - } - } - matches.args.insert(flg, - MatchedArg { - occurrences: 0, - values: None, - }); + fn add_val_to_arg(&self, + arg: &A, + val: &str, + matcher: &mut ArgMatcher<'ar>) + -> ClapResult> + where A: AnyArg<'ar> + Display { + matcher.add_val_to(arg.name(), val.to_owned()); + + // Increment or create the group "args" + if let Some(grps) = self.groups_for_arg(arg.name()) { + for grp in grps { + matcher.add_val_to(grp, val.to_owned()); } } - Err(error_builder::InvalidArgument(&*format!("--{}", arg), - Some(&*suffix.0), - &*try!(self.create_current_usage(matches)))) + // The validation must come AFTER inserting into 'matcher' or the usage string + // can't be built + self.validate_value(arg, val, matcher) } - fn validate_value(&self, v: &OptBuilder, av: &str, matches: &ArgMatches) -> ClapResult<()> { - if let Some(ref p_vals) = v.possible_vals { - if !p_vals.contains(&av) { + fn validate_value(&self, arg: &A, val: &str, matcher: &ArgMatcher<'ar>) -> ClapResult> + where A: AnyArg<'ar> + Display { + if let Some(ref p_vals) = arg.possible_vals() { + if !p_vals.contains(&val) { return Err( - error_builder::InvalidValue(av, + error_builder::InvalidValue(val, p_vals, - &v.to_string(), - &*try!(self.create_current_usage(matches)))); + &arg.to_string(), + &*try!(self.create_current_usage(matcher)))); } } - if !v.settings.is_set(&ArgSettings::EmptyValues) && av.is_empty() && - matches.args.contains_key(v.name) { - return Err(error_builder::EmptyValue(&*v.to_string(), - &*try!(self.create_current_usage(matches)))); + if !arg.is_set(ArgSettings::EmptyValues) && + val.is_empty() && + matcher.contains(arg.name()) { + return Err(error_builder::EmptyValue(&*arg.to_string(), + &*try!(self.create_current_usage(matcher)))); } - if let Some(ref vtor) = v.validator { - if let Err(e) = vtor(av.to_owned()) { + if let Some(ref vtor) = arg.validator() { + if let Err(e) = vtor(val.to_owned()) { return Err(error_builder::ValueValidationError(&*e)); } } - Ok(()) - } - - fn parse_short_arg(&mut self, - matches: &mut ArgMatches<'ar, 'ar>, - full_arg: &str) - -> ClapResult> { - let arg = &full_arg[..].trim_left_matches(|c| c == '-'); - for c in arg.chars() { - try!(self.check_for_help_and_version(c)); - - // Check for matching short in options, and return the name - // (only ones with shorts, of course) - if let Some(v) = self.opts - .iter() - .filter(|&v| v.short.is_some() && v.short == Some(c)) - .next() { - let mut ret = Some(v.name); - // Ensure this option isn't on the master mutually excludes list - if self.blacklist.contains(&v.name) { - matches.args.remove(v.name); - return Err(error_builder::ArgumentConflict( - v.to_string(), - self.blacklisted_from(v.name, &matches), - try!(self.create_current_usage(matches)))); - } - if self.overrides.contains(&v.name) { - if let Some(ref name) = self.overriden_from(v.name, matches) { - matches.args.remove(&*name); - remove_overriden!(self, name); - } - } - if let Some(ref or) = v.overrides { - for pa in or { - matches.args.remove(pa); - remove_overriden!(self, pa); - self.overrides.push(pa); - } - } - - if matches.args.contains_key(v.name) && !v.settings.is_set(&ArgSettings::Multiple) { - return Err(error_builder::UnexpectedMultipleUsage( - &*format!("-{}", arg), - &*try!(self.create_current_usage(matches)))); - } - - // New scope for lifetimes - let val: Vec<&str> = arg.splitn(2, c).collect(); - { - let ma = matches.args.entry(v.name).or_insert(MatchedArg { - // occurrences will be incremented on getting a value - occurrences: 0, - values: Some(BTreeMap::new()), - }); - if !val[1].is_empty() { - if !v.settings.is_set(&ArgSettings::Multiple) { - ret = None; - } - if let Some(ref mut vals) = ma.values { - let len = vals.len() as u8 + 1; - vals.insert(len, val[1].to_owned()); - } - ma.occurrences += 1; - } - } - - if let Some(ref vec) = self.groups_for_arg(v.name) { - for grp in vec { - let ma_g = matches.args.entry(grp).or_insert(MatchedArg { - occurrences: 0, - values: Some(BTreeMap::new()), - }); - if !val[1].is_empty() { - if let Some(ref mut vals) = ma_g.values { - let len = vals.len() as u8 + 1; - vals.insert(len, val[1].to_owned()); - } - ma_g.occurrences += 1; - } - } - } - - if let Some(ref bl) = v.blacklist { - for name in bl { - self.blacklist.push(name); - vec_remove!(self.overrides, name); - vec_remove!(self.required, name); - } - } - - if let Some(ref reqs) = v.requires { - // Add all required args which aren't already found in matches to the - // final required list - for n in reqs { - if matches.args.contains_key(n) { - continue; - } - - self.required.push(n); - } - } - - parse_group_reqs!(self, v); - - return Ok(ret); + let vals = matcher.get(arg.name()) + .expect(INTERNAL_ERROR_MSG) + .values.as_ref() + .expect(INTERNAL_ERROR_MSG) + .len(); + if let Some(max) = arg.max_vals() { + if (vals as u8) < max { + return Ok(Some(arg.name())); + } else { + return Ok(None); } - - match self.parse_single_short_flag(matches, c) { - Ok(b) => { - if !b { - return Err(error_builder::InvalidArgument( - &*format!("-{}", c), - None, - &*try!(self.create_current_usage(matches)))); - } + } + if let Some(..) = arg.min_vals() { + return Ok(Some(arg.name())); + } + if let Some(num) = arg.num_vals() { + if arg.is_set(ArgSettings::Multiple) { + if (vals as u8) < num { + return Ok(Some(arg.name())); + } + } else { + if (vals as u8 % num) != 0 { + return Ok(Some(arg.name())); } - Err(e) => return Err(e), } } + if arg.is_set(ArgSettings::Multiple) { + return Ok(Some(arg.name())); + } Ok(None) } - fn parse_single_short_flag(&mut self, - matches: &mut ArgMatches<'ar, 'ar>, - arg: char) - -> ClapResult { - let v = self.flags.iter().filter(|&v| v.short.is_some() && v.short.unwrap() == arg).next(); - if v.is_some() { - let flag = v.unwrap(); - try!(self.validate_arg(flag, matches)); - - if let Some(ref vec) = self.groups_for_arg(flag.name) { - for grp in vec { - if let Some(ref mut f) = matches.args.get_mut(grp) { - f.occurrences = if v.settings.is_set(&ArgSettings::Multiple) { - f.occurrences + 1 - } else { - 1 - }; - } - } - } - let mut done = false; - if let Some(ref mut f) = matches.args.get_mut(v.name) { - done = true; - f.occurrences = if v.settings.is_set(&ArgSettings::Multiple) { - f.occurrences + 1 - } else { - 1 - }; - } - if !done { - if let Some(ref vec) = self.groups_for_arg(v.name) { - for grp in vec { - matches.args.insert(grp, - MatchedArg { - occurrences: 1, - values: None, - }); - } - } - matches.args.insert(v.name, - MatchedArg { - occurrences: 1, - values: None, - }); - } - - if let Some(ref bl) = v.blacklist { - for name in bl { - self.blacklist.push(name); - vec_remove!(self.overrides, name); - vec_remove!(self.required, name); - } - } - - // Add all required args which aren't already found in matches to the master - // list - if let Some(ref reqs) = v.requires { - for n in reqs { - if matches.args.contains_key(n) { - continue; - } + fn parse_flag(&self, flag: &FlagBuilder<'ar>, matcher: &mut ArgMatcher<'ar>) -> ClapResult<()> { + // Validate that we can actually accept this arg + try!(self.validate_arg(flag, matcher)); - self.required.push(n); - } - } + // First occurrence or not? + if !matcher.contains(flag.name) { + // If this is the first, then add this flag itself + matcher.insert(flag.name); + } else if !flag.settings.is_set(&ArgSettings::Multiple) { + // Not the first time, but we don't allow multiples + return Err(error_builder::UnexpectedMultipleUsage( + &*flag.to_string(), + &*try!(self.create_current_usage(matcher)))); + } else { + matcher.inc_occurrence_of(flag.name); + } - parse_group_reqs!(self, v); + // Increment or create the group "args" + self.groups_for_arg(flag.name).and_then(|vec| Some(matcher.inc_occurrences_of(&vec))); - return Ok(true); - } - Ok(false) + Ok(()) } - fn validate_arg(&mut self, arg: &A, matches: &mut ArgMatches<'ar, 'ar>) -> ClapResult<()> + fn validate_arg(&self, arg: &A, matcher: &mut ArgMatcher<'ar>) -> ClapResult<()> where A: AnyArg<'ar> + Display { + // May not be needed since we can't get here without being an Arg of some type + // + // if arg.has_switch() && (!self.flags.iter().any(|f| f.name == arg.name()) && + // !self.opts.iter().any(|f| f.name == arg.name())) { + // return Err(error_builder::InvalidArgument( + // &*format!("{}", arg), + // None, + // &*try!(self.create_current_usage(matcher)))); + // } + // Ensure this arg isn't on the mutually excludes list if self.blacklist.contains(&arg.name()) { - matches.args.remove(arg.name()); + matcher.remove(arg.name()); return Err(error_builder::ArgumentConflict( arg.to_string(), - self.blacklisted_from(arg.name(), &matches), - try!(self.create_current_usage(matches)))); - } - if self.overrides.contains(&arg.name()) { - if let Some(ref name) = self.overriden_from(arg.name(), matches) { - matches.args.remove(name); - remove_overriden!(self, name); - } - } - if let Some(or) = arg.overrides() { - for pa in or { - matches.args.remove(pa); - remove_overriden!(self, pa); - self.overrides.push(pa); - } + self.blacklisted_from(arg.name(), &matcher), + try!(self.create_current_usage(matcher)))); } - // Make sure this isn't one being added multiple times if it doesn't suppor it - if matches.args.contains_key(arg.name()) && !arg.is_set(&ArgSettings::Multiple) { + // Make sure this isn't one being added multiple times if it doesn't support it + if matcher.contains(arg.name()) && !arg.is_set(ArgSettings::Multiple) { return Err(error_builder::UnexpectedMultipleUsage( - &*format!("-{}", arg), - &*try!(self.create_current_usage(matches)))); + &*format!("{}", arg), + &*try!(self.create_current_usage(matcher)))); } Ok(()) } - fn validate_blacklist(&self, matches: &mut ArgMatches<'ar, 'ar>) -> ClapResult<()> { + fn validate_blacklist(&self, matcher: &mut ArgMatcher<'ar>) -> ClapResult<()> { for name in self.blacklist.iter() { - if matches.args.contains_key(name) { - matches.args.remove(name); + if matcher.contains(name) { + matcher.remove(name); return Err(error_builder::ArgumentConflict( format!("{}", Format::Warning( if let Some(f) = self.flags.iter().filter(|f| &f.name == name).next() { @@ -3184,12 +2605,12 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { } } )), - self.blacklisted_from(name, &matches), - try!(self.create_current_usage(matches)))); + self.blacklisted_from(name, &matcher), + try!(self.create_current_usage(matcher)))); } else if self.groups.contains_key(name) { for n in self.arg_names_in_group(name) { - if matches.args.contains_key(n) { - matches.args.remove(n); + if matcher.contains(n) { + matcher.remove(n); return Err(error_builder::ArgumentConflict( format!("{}", Format::Warning( if let Some(f) = self.flags.iter() @@ -3210,8 +2631,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { } } )), - self.blacklisted_from(name, &matches), - try!(self.create_current_usage(matches)))); + self.blacklisted_from(name, &matcher), + try!(self.create_current_usage(matcher)))); } } } @@ -3219,8 +2640,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { Ok(()) } - fn validate_num_args(&self, matches: &mut ArgMatches<'ar, 'ar>) -> ClapResult<()> { - for (name, ma) in matches.args.iter() { + fn validate_num_args(&self, matcher: &mut ArgMatcher<'ar>) -> ClapResult<()> { + for (name, ma) in matcher.iter() { if self.groups.contains_key(name) { continue; } else if let Some(ref vals) = ma.values { @@ -3250,18 +2671,18 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { } else { "ere" }, - &*try!(self.create_current_usage(matches)))); + &*try!(self.create_current_usage(matcher)))); } } if let Some(num) = f.max_vals { if (vals.len() as u8) > num { return Err(error_builder::TooManyValues( - vals.get(vals.keys() + vals.get(&vals.keys() .last() .expect(INTERNAL_ERROR_MSG)) .expect(INTERNAL_ERROR_MSG), &f.to_string(), - &try!(self.create_current_usage(matches)))); + &try!(self.create_current_usage(matcher)))); } } if let Some(num) = f.min_vals { @@ -3270,7 +2691,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { &*f.to_string(), num, vals.len(), - &*try!(self.create_current_usage(matches)))); + &*try!(self.create_current_usage(matcher)))); } } } else if let Some(f) = self.positionals @@ -3288,18 +2709,18 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { } else { "ere" }, - &*try!(self.create_current_usage(matches)))); + &*try!(self.create_current_usage(matcher)))); } } if let Some(max) = f.max_vals { if (vals.len() as u8) > max { return Err(error_builder::TooManyValues( - vals.get(vals.keys() + vals.get(&vals.keys() .last() - .expect("error getting last key. This is a bug")) - .expect("failed to retrieve last value. This is a bug"), + .expect(INTERNAL_ERROR_MSG)) + .expect(INTERNAL_ERROR_MSG), &f.to_string(), - &try!(self.create_current_usage(matches)))); + &try!(self.create_current_usage(matcher)))); } } if let Some(min) = f.min_vals { @@ -3308,7 +2729,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { &*f.to_string(), min, vals.len(), - &*try!(self.create_current_usage(matches)))); + &*try!(self.create_current_usage(matcher)))); } } } @@ -3317,70 +2738,79 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar> { Ok(()) } - fn validate_required(&self, matches: &ArgMatches<'ar, 'ar>) -> bool { + fn validate_required(&self, matcher: &ArgMatcher<'ar>) -> ClapResult<()> { 'outer: for name in self.required.iter() { - if matches.args.contains_key(name) { + if matcher.contains(name) { continue 'outer; } - for grp in self.groups.values() { - if grp.args.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) = self.flags.iter().filter(|f| &f.name == name).next() { - if let Some(ref bl) = a.blacklist { - for n in bl.iter() { - if matches.args.contains_key(n) { - continue 'outer; - } else if self.groups.contains_key(n) { - let grp = self.groups.get(n).unwrap(); - for an in grp.args.iter() { - if matches.args.contains_key(an) { - continue 'outer; - } - } - } - } - } + if self._validate_blacklist_required(a, matcher) { continue 'outer; } } if let Some(a) = self.opts.iter().filter(|o| &o.name == name).next() { - if let Some(ref bl) = a.blacklist { - for n in bl.iter() { - if matches.args.contains_key(n) { - continue 'outer; - } else if self.groups.contains_key(n) { - let grp = self.groups.get(n).unwrap(); - for an in grp.args.iter() { - if matches.args.contains_key(an) { - continue 'outer; - } - } - } - } - } + if self._validate_blacklist_required(a, matcher) { continue 'outer; } } - // because positions use different keys, we dont use the macro - match self.positionals.values().filter(|p| &p.name == name).next() { - Some(p) => { - if let Some(ref bl) = p.blacklist { - for n in bl.iter() { - if matches.args.contains_key(n) { - continue 'outer; - } else if self.groups.contains_key(n) { - let grp = self.groups.get(n).unwrap(); - for an in grp.args.iter() { - if matches.args.contains_key(an) { - continue 'outer; - } - } - } - } - } + if let Some(a) = self.positionals.values().filter(|p| &p.name == name).next() { + if self._validate_blacklist_required(a, matcher) { continue 'outer; } + } + return Err(error_builder::MissingRequiredArgument( + &*self.get_required_from(&self.required, Some(matcher)) + .iter() + .fold(String::new(), + |acc, s| acc + &format!("\n\t{}", Format::Error(s))[..]), + &*try!(self.create_current_usage(matcher)))); + } + Ok(()) + } + + fn _validate_blacklist_required(&self, a: &A, matcher: &ArgMatcher) -> bool where A: AnyArg<'ar> { + if let Some(bl) = a.blacklist() { + for n in bl.iter() { + if matcher.contains(n) { + return true; + } else if self.groups + .get(n) + .map(|g| g.args.iter().any(|an| matcher.contains(an))) + .unwrap_or(false) { + return true; } - None => (), } - return true; } false } + + fn did_you_mean_error(&self, arg: &str, matcher: &mut ArgMatcher<'ar>) -> 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(), + 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) = self.opts + .iter() + .filter(|o| o.long.is_some() && o.long.unwrap() == name) + .next() { + self.groups_for_arg(opt.name).and_then(|grps| Some(matcher.inc_occurrences_of(&grps))); + matcher.insert(opt.name); + } else if let Some(flg) = self.flags + .iter() + .filter(|f| f.long.is_some() && f.long.unwrap() == name) + .next() { + self.groups_for_arg(flg.name).and_then(|grps| Some(matcher.inc_occurrences_of(&grps))); + matcher.insert(flg.name); + } + } + + Err(error_builder::InvalidArgument(&*format!("--{}", arg), + Some(&*suffix.0), + &*try!(self.create_current_usage(matcher)))) + } } diff --git a/src/args/any_arg.rs b/src/args/any_arg.rs index 00728419214..85fffd43f14 100644 --- a/src/args/any_arg.rs +++ b/src/args/any_arg.rs @@ -1,8 +1,19 @@ +use std::rc::Rc; + use args::settings::ArgSettings; +#[doc(hidden)] pub trait AnyArg<'n> { fn name(&self) -> &'n str; fn overrides(&self) -> Option<&[&'n str]>; - fn is_set(&self, &ArgSettings) -> bool; - fn set(&mut self, &ArgSettings); + fn requires(&self) -> Option<&[&'n str]>; + fn blacklist(&self) -> Option<&[&'n str]>; + fn is_set(&self, ArgSettings) -> bool; + fn set(&mut self, ArgSettings); + fn has_switch(&self) -> bool; + fn max_vals(&self) -> Option; + fn min_vals(&self) -> Option; + fn num_vals(&self) -> Option; + fn possible_vals(&self) -> Option<&[&'n str]>; + fn validator(&self) -> Option<&Rc Result<(), String>>>; } diff --git a/src/args/arg.rs b/src/args/arg.rs index fe016c6515f..3188b756622 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -98,6 +98,34 @@ pub struct Arg<'n, 'l, 'h, 'g, 'p, 'r> { pub hidden: bool, } +impl<'n, 'l, 'h, 'g, 'p, 'r> Default for Arg<'n, 'l, 'h, 'g, 'p, 'r> { + fn default() -> Self { + Arg { + name: "", + short: None, + long: None, + help: None, + required: false, + takes_value: false, + multiple: false, + index: None, + possible_vals: None, + blacklist: None, + requires: None, + num_vals: None, + min_vals: None, + max_vals: None, + val_names: None, + group: None, + global: false, + empty_vals: true, + validator: None, + overrides: None, + hidden: false, + } + } +} + impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> { /// Creates a new instance of `Arg` using a unique string name. /// The name will be used by the library consumer to get information about @@ -120,26 +148,7 @@ impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> { pub fn with_name(n: &'n str) -> Self { Arg { name: n, - short: None, - long: None, - help: None, - required: false, - takes_value: false, - multiple: false, - index: None, - possible_vals: None, - blacklist: None, - requires: None, - num_vals: None, - min_vals: None, - max_vals: None, - val_names: None, - group: None, - global: false, - empty_vals: true, - validator: None, - overrides: None, - hidden: false, + ..Default::default() } } @@ -370,10 +379,6 @@ impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> { required: required, takes_value: takes_value, multiple: multiple, - index: None, - possible_vals: None, - blacklist: None, - requires: None, num_vals: if num_names > 1 { Some(num_names) } else { @@ -384,14 +389,7 @@ impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> { } else { None }, - max_vals: None, - min_vals: None, - group: None, - global: false, - empty_vals: true, - validator: None, - overrides: None, - hidden: false, + ..Default::default() } } diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index 1d9dac9e41f..65569a49f74 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -2,6 +2,8 @@ use std::fmt::{Display, Formatter, Result}; use std::convert::From; use std::io; +use std::rc::Rc; +use std::result::Result as StdResult; use Arg; use args::AnyArg; @@ -170,21 +172,29 @@ impl<'n> Display for FlagBuilder<'n> { } impl<'n> AnyArg<'n> for FlagBuilder<'n> { - fn name(&self) -> &'n str { - self.name - } + fn name(&self) -> &'n str { self.name } - fn overrides(&self) -> Option<&[&'n str]> { - self.overrides.as_ref().map(|o| &o[..]) - } + fn overrides(&self) -> Option<&[&'n str]> { self.overrides.as_ref().map(|o| &o[..]) } - fn is_set(&self, s: &ArgSettings) -> bool { - self.settings.is_set(s) - } + fn requires(&self) -> Option<&[&'n str]> { self.requires.as_ref().map(|o| &o[..]) } - fn set(&mut self, s: &ArgSettings) { - self.settings.set(s) - } + fn blacklist(&self) -> Option<&[&'n str]> { self.blacklist.as_ref().map(|o| &o[..]) } + + fn is_set(&self, s: ArgSettings) -> bool { self.settings.is_set(&s) } + + fn has_switch(&self) -> bool { true } + + fn set(&mut self, s: ArgSettings) { self.settings.set(&s) } + + fn max_vals(&self) -> Option { None } + + fn num_vals(&self) -> Option { None } + + fn possible_vals(&self) -> Option<&[&'n str]> { None } + + fn validator(&self) -> Option<&Rc StdResult<(), String>>> { None } + + fn min_vals(&self) -> Option { None } } #[cfg(test)] diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index b6dba1c79c2..ce75ef27e7a 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -4,7 +4,7 @@ use std::fmt::{Display, Formatter, Result}; use std::result::Result as StdResult; use std::io; -use args::{ArgMatches, Arg}; +use args::{AnyArg, ArgMatcher, Arg}; use args::settings::{ArgFlags, ArgSettings}; use errors::{ClapResult, error_builder}; use app::App; @@ -36,10 +36,10 @@ pub struct OptBuilder<'n> { pub settings: ArgFlags, } -impl<'n> OptBuilder<'n> { - pub fn new(name: &'n str) -> Self { +impl<'n> Default for OptBuilder<'n> { + fn default() -> Self { OptBuilder { - name: name, + name: "", short: None, long: None, help: None, @@ -55,6 +55,14 @@ impl<'n> OptBuilder<'n> { settings: ArgFlags::new(), } } +} +impl<'n> OptBuilder<'n> { + pub fn new(name: &'n str) -> Self { + OptBuilder { + name: name, + ..Default::default() + } + } pub fn from_arg(a: &Arg<'n, 'n, 'n, 'n, 'n, 'n>, reqs: &mut Vec<&'n str>) -> Self { if a.short.is_none() && a.long.is_none() { @@ -67,17 +75,12 @@ impl<'n> OptBuilder<'n> { name: a.name, short: a.short, long: a.long, - blacklist: None, help: a.help, - possible_vals: None, num_vals: a.num_vals, min_vals: a.min_vals, max_vals: a.max_vals, val_names: a.val_names.clone(), - requires: None, - validator: None, - overrides: None, - settings: ArgFlags::new(), + ..Default::default() }; if a.multiple { ob.settings.set(&ArgSettings::Multiple); @@ -192,26 +195,26 @@ impl<'n> OptBuilder<'n> { pub fn validate_value(&self, val: &str, - matches: &ArgMatches, + matcher: &ArgMatcher, app: &App) -> ClapResult<()> { // Check the possible values if let Some(ref p_vals) = self.possible_vals { if !p_vals.contains(&val) { - let usage = try!(app.create_current_usage(matches)); + let usage = try!(app.create_current_usage(matcher)); return Err(error_builder::InvalidValue(val, p_vals, &self.to_string(), &usage)); } } // Check the required number of values if let Some(num) = self.num_vals { - if let Some(ref ma) = matches.args.get(self.name) { + if let Some(ref ma) = matcher.get(self.name) { if let Some(ref vals) = ma.values { if (vals.len() as u8) > num && !self.settings.is_set(&ArgSettings::Multiple) { return Err(error_builder::TooManyValues( val, &self.to_string(), - &*try!(app.create_current_usage(matches)))); + &*try!(app.create_current_usage(matcher)))); } } } @@ -219,10 +222,10 @@ impl<'n> OptBuilder<'n> { // if it's an empty value, and we don't allow that, report the error if !self.settings.is_set(&ArgSettings::EmptyValues) && - matches.args.contains_key(self.name) && + matcher.contains(self.name) && val.is_empty() { return Err(error_builder::EmptyValue(&*self.to_string(), - &*try!(app.create_current_usage(matches)))); + &*try!(app.create_current_usage(matcher)))); } if let Some(ref vtor) = self.validator { @@ -263,6 +266,54 @@ impl<'n> Display for OptBuilder<'n> { } } +impl<'n> AnyArg<'n> for OptBuilder<'n> { + fn name(&self) -> &'n str { + self.name + } + + fn overrides(&self) -> Option<&[&'n str]> { + self.overrides.as_ref().map(|o| &o[..]) + } + + fn requires(&self) -> Option<&[&'n str]> { + self.requires.as_ref().map(|o| &o[..]) + } + + fn blacklist(&self) -> Option<&[&'n str]> { + self.blacklist.as_ref().map(|o| &o[..]) + } + + fn is_set(&self, s: ArgSettings) -> bool { + self.settings.is_set(&s) + } + + fn has_switch(&self) -> bool { + true + } + + fn set(&mut self, s: ArgSettings) { + self.settings.set(&s) + } + + fn max_vals(&self) -> Option { + self.max_vals + } + fn num_vals(&self) -> Option { + self.num_vals + } + fn possible_vals(&self) -> Option<&[&'n str]> { + self.possible_vals.as_ref().map(|o| &o[..]) + } + + fn validator(&self) -> Option<&Rc StdResult<(), String>>> { + self.validator.as_ref() + } + + fn min_vals(&self) -> Option { + self.min_vals + } +} + #[cfg(test)] mod test { use super::OptBuilder; diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index 9d9896203e7..6bcc1d75a7f 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -4,6 +4,7 @@ use std::rc::Rc; use std::io; use Arg; +use args::AnyArg; use args::settings::{ArgFlags, ArgSettings}; #[allow(missing_debug_implementations)] @@ -181,6 +182,53 @@ impl<'n> Display for PosBuilder<'n> { Ok(()) } } + +impl<'n> AnyArg<'n> for PosBuilder<'n> { + fn name(&self) -> &'n str { + self.name + } + + fn overrides(&self) -> Option<&[&'n str]> { + self.overrides.as_ref().map(|o| &o[..]) + } + + fn requires(&self) -> Option<&[&'n str]> { + self.requires.as_ref().map(|o| &o[..]) + } + + fn blacklist(&self) -> Option<&[&'n str]> { + self.blacklist.as_ref().map(|o| &o[..]) + } + + fn is_set(&self, s: ArgSettings) -> bool { + self.settings.is_set(&s) + } + + fn set(&mut self, s: ArgSettings) { + self.settings.set(&s) + } + + fn has_switch(&self) -> bool { + false + } + fn max_vals(&self) -> Option { + self.max_vals + } + fn num_vals(&self) -> Option { + self.num_vals + } + fn possible_vals(&self) -> Option<&[&'n str]> { + self.possible_vals.as_ref().map(|o| &o[..]) + } + fn validator(&self) -> Option<&Rc StdResult<(), String>>> { + self.validator.as_ref() + } + + fn min_vals(&self) -> Option { + self.min_vals + } +} + #[cfg(test)] mod test { use super::PosBuilder; diff --git a/src/args/arg_matcher.rs b/src/args/arg_matcher.rs new file mode 100644 index 00000000000..20e8bf4d65c --- /dev/null +++ b/src/args/arg_matcher.rs @@ -0,0 +1,97 @@ +use vec_map::VecMap; + +use args::{ArgMatches, MatchedArg, SubCommand}; +use std::collections::hash_map::{Entry, Keys, Iter}; + +pub struct ArgMatcher<'ar>(ArgMatches<'ar, 'ar>); + +impl<'ar> ArgMatcher<'ar> { + pub fn new() -> Self { + ArgMatcher(ArgMatches::new()) + } + + pub fn get_mut(&mut self, arg: &str) -> Option<&mut MatchedArg> { + self.0.args.get_mut(arg) + } + + pub fn get(&self, arg: &str) -> Option<&MatchedArg> { + self.0.args.get(arg) + } + + pub fn remove(&mut self, arg: &str) { + self.0.args.remove(arg); + } + + pub fn insert(&mut self, name: &'ar str) { + self.0.args.insert(name, MatchedArg::new()); + } + + pub fn contains(&self, arg: &str) -> bool { + self.0.args.contains_key(arg) + } + + pub fn is_empty(&self) -> bool { + self.0.args.is_empty() + } + + pub fn values_of(&self, arg: &str) -> Option> { + self.0.values_of(arg) + } + + pub fn usage(&mut self, usage: String) { + self.0.usage = Some(usage); + } + + pub fn arg_names(&self) -> Keys<&'ar str, MatchedArg> { + self.0.args.keys() + } + + pub fn entry(&mut self, arg: &'ar str) -> Entry<&'ar str, MatchedArg> { + self.0.args.entry(arg) + } + + pub fn subcommand(&mut self, sc: SubCommand<'ar, 'ar>) { + self.0.subcommand = Some(Box::new(sc)); + } + + pub fn subcommand_name(&self) -> Option<&str> { + self.0.subcommand_name() + } + + pub fn iter(&self) -> Iter<&'ar str, MatchedArg> { + self.0.args.iter() + } + + pub fn inc_occurrence_of(&mut self, arg: &'ar str) { + if let Some(a) = self.get_mut(arg) { + a.occurrences += 1; + return; + } + self.insert(arg); + } + + pub fn inc_occurrences_of(&mut self, args: &[&'ar str]) { + for arg in args { + self.inc_occurrence_of(arg); + } + } + + pub fn add_val_to(&mut self, arg: &'ar str, val: String) { + let ma = self.entry(arg).or_insert(MatchedArg { + // occurrences will be incremented on getting a value + occurrences: 0, + values: Some(VecMap::new()), + }); + if let Some(ref mut vals) = ma.values { + let len = vals.len() + 1; + vals.insert(len, val); + ma.occurrences += 1; + } + } +} + +impl<'ar> Into> for ArgMatcher<'ar> { + fn into(self) -> ArgMatches<'ar, 'ar> { + self.0 + } +} diff --git a/src/args/matched_arg.rs b/src/args/matched_arg.rs index 807f8d75f6d..f2ba352e950 100644 --- a/src/args/matched_arg.rs +++ b/src/args/matched_arg.rs @@ -1,12 +1,19 @@ -use std::collections::BTreeMap; +use vec_map::VecMap; #[doc(hidden)] #[derive(Debug)] pub struct MatchedArg { - // #[doc(hidden)] - // pub name: String, #[doc(hidden)] pub occurrences: u8, #[doc(hidden)] - pub values: Option>, + pub values: Option>, +} + +impl MatchedArg { + pub fn new() -> Self { + MatchedArg { + occurrences: 1, + values: None + } + } } diff --git a/src/args/mod.rs b/src/args/mod.rs index b8ac2fa8918..511e8e39590 100644 --- a/src/args/mod.rs +++ b/src/args/mod.rs @@ -1,5 +1,6 @@ pub use self::arg::Arg; pub use self::arg_matches::ArgMatches; +pub use self::arg_matcher::ArgMatcher; pub use self::subcommand::SubCommand; pub use self::arg_builder::{FlagBuilder, OptBuilder, PosBuilder}; pub use self::matched_arg::MatchedArg; diff --git a/src/errors.rs b/src/errors.rs index 95a8d44de60..20f43a936df 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -281,7 +281,7 @@ pub mod error_builder { { ClapError { error: format!("{} The argument '{}' was supplied more than once, but does \ - not support multiple values\n\n\ + not support multiple occurrences\n\n\ {}\n\n\ For more information try {}", Format::Error("error:"), diff --git a/src/macros.rs b/src/macros.rs index 61a770ccedf..885c1185e90 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -44,88 +44,7 @@ macro_rules! load_yaml { ); } -macro_rules! write_spaces { - ($num:expr, $w:ident) => ({ - for _ in 0..$num { - try!(write!($w, " ")); - } - }) -} - -// convenience macro for remove an item from a vec -macro_rules! vec_remove { - ($vec:expr, $to_rem:ident) => { - { - let mut ix = None; - $vec.dedup(); - for (i, val) in $vec.iter().enumerate() { - if &val == &$to_rem { - ix = Some(i); - break; - } - } - if let Some(i) = ix { - $vec.remove(i); - } - } - } -} - -macro_rules! remove_overriden { - ($me:ident, $name:expr) => ({ - if let Some(ref o) = $me.opts.iter().filter(|o| &o.name == $name).next() { - if let Some(ref ora) = o.requires { - for a in ora { - vec_remove!($me.required, a); - } - } - if let Some(ref ora) = o.blacklist { - for a in ora { - vec_remove!($me.blacklist, a); - } - } - if let Some(ref ora) = o.overrides { - for a in ora { - vec_remove!($me.overrides, a); - } - } - } else if let Some(ref o) = $me.flags.iter().filter(|f| &f.name == $name).next() { - if let Some(ref ora) = o.requires { - for a in ora { - vec_remove!($me.required, a); - } - } - if let Some(ref ora) = o.blacklist { - for a in ora { - vec_remove!($me.blacklist, a); - } - } - if let Some(ref ora) = o.overrides { - for a in ora { - vec_remove!($me.overrides, a); - } - } - } else if let Some(p) = $me.positionals.values().filter(|p| &&p.name == &$name).next() { - if let Some(ref ora) = p.requires { - for a in ora { - vec_remove!($me.required, a); - } - } - if let Some(ref ora) = p.blacklist { - for a in ora { - vec_remove!($me.blacklist, a); - } - } - if let Some(ref ora) = p.overrides { - for a in ora { - vec_remove!($me.overrides, a); - } - } - } - }) -} - -// De-duplication macro used in src/app.rs +// used in src/args/arg_builder/option.rs macro_rules! print_opt_help { ($opt:ident, $spc:expr, $w:ident) => { if let Some(h) = $opt.help { @@ -153,38 +72,35 @@ macro_rules! print_opt_help { }; } -// De-duplication macro used in src/app.rs -macro_rules! parse_group_reqs { - ($me:ident, $arg:ident) => { - for ag in $me.groups.values() { - let mut found = false; - for name in ag.args.iter() { - if name == &$arg.name { - vec_remove!($me.required, name); - if let Some(ref reqs) = ag.requires { - for r in reqs { - $me.required.push(r); - } - } - if let Some(ref bl) = ag.conflicts { - for b in bl { - $me.blacklist.push(b); - } - } - found = true; +// Helper/deduplication macro for printing the correct number of spaces in help messages +// used in: +// src/args/arg_builder/*.rs +// src/app/mod.rs +macro_rules! write_spaces { + ($num:expr, $w:ident) => ({ + for _ in 0..$num { + try!(write!($w, " ")); + } + }) +} + +// convenience macro for remove an item from a vec +macro_rules! vec_remove { + ($vec:expr, $to_rem:ident) => { + { + let mut ix = None; + $vec.dedup(); + for (i, val) in $vec.iter().enumerate() { + if &val == &$to_rem { + ix = Some(i); break; } } - if found { - for name in ag.args.iter() { - if name == &$arg.name { continue } - vec_remove!($me.required, name); - - $me.blacklist.push(name); - } + if let Some(i) = ix { + $vec.remove(i); } } - }; + } } // Thanks to bluss and flan3002 in #rust IRC diff --git a/tests/app_settings.rs b/tests/app_settings.rs index f16ff6a1acf..41679c9a97d 100644 --- a/tests/app_settings.rs +++ b/tests/app_settings.rs @@ -43,12 +43,11 @@ fn arg_required_else_help() { let result = App::new("arg_required") .setting(AppSettings::ArgRequiredElseHelp) .arg(Arg::with_name("test") - .required(true) .index(1)) .get_matches_from_safe(vec![""]); assert!(result.is_err()); let err = result.err().unwrap(); - assert_eq!(err.error_type, ClapErrorType::MissingRequiredArgument); + assert_eq!(err.error_type, ClapErrorType::MissingArgumentOrSubcommand); } #[test] @@ -109,4 +108,4 @@ fn app_settings_fromstr() { assert_eq!("subcommandrequiredelsehelp".parse::().ok().unwrap(), AppSettings::SubcommandRequiredElseHelp); assert_eq!("hidden".parse::().ok().unwrap(), AppSettings::Hidden); assert!("hahahaha".parse::().is_err()); -} \ No newline at end of file +} diff --git a/tests/groups.rs b/tests/groups.rs index b20d4197a12..bc9c19a9245 100644 --- a/tests/groups.rs +++ b/tests/groups.rs @@ -26,6 +26,10 @@ fn group_single_value() { .get_matches_from(vec!["", "-c", "blue"]); assert!(m.is_present("grp")); assert_eq!(m.value_of("grp").unwrap(), "blue"); +} + +#[test] +fn group_single_flag() { let m = App::new("group") .args_from_usage("-f, --flag 'some flag' -c, --color [color] 'some option'") @@ -34,6 +38,10 @@ fn group_single_value() { .get_matches_from(vec!["", "-f"]); assert!(m.is_present("grp")); assert!(m.value_of("grp").is_none()); +} + +#[test] +fn group_empty() { let m = App::new("group") .args_from_usage("-f, --flag 'some flag' -c, --color [color] 'some option'") @@ -44,6 +52,20 @@ fn group_single_value() { assert!(m.value_of("grp").is_none()); } +#[test] +fn group_reqired_flags_empty() { + let result = App::new("group") + .args_from_usage("-f, --flag 'some flag' + -c, --color 'some option'") + .arg_group(ArgGroup::with_name("grp") + .required(true) + .add_all(&["flag", "color"])) + .get_matches_from_safe(vec![""]); + assert!(result.is_err()); + let err = result.err().unwrap(); + assert_eq!(err.error_type, ClapErrorType::MissingRequiredArgument); +} + #[test] fn group_multi_value_single_arg() { let m = App::new("group") diff --git a/tests/posix_compatible.rs b/tests/posix_compatible.rs index 0e3524414cc..0f297d387d2 100644 --- a/tests/posix_compatible.rs +++ b/tests/posix_compatible.rs @@ -150,15 +150,15 @@ fn conflict_overriden_4() { } #[test] -fn require_overriden() { +fn pos_required_overridden_by_flag() { let result = App::new("require_overriden") - .arg(Arg::with_name("flag") + .arg(Arg::with_name("pos") .index(1) .required(true)) - .arg(Arg::from_usage("-c, --color 'other flag'") - .mutually_overrides_with("flag")) - .get_matches_from_safe(vec!["", "flag", "-c"]); - assert!(result.is_ok()); + .arg(Arg::from_usage("-c, --color 'some flag'") + .mutually_overrides_with("pos")) + .get_matches_from_safe(vec!["", "test", "-c"]); + assert!(result.is_ok(), "{:?}", result.unwrap_err()); } #[test] @@ -200,4 +200,4 @@ fn require_overriden_4() { assert!(result.is_err()); let err = result.err().unwrap(); assert_eq!(err.error_type, ClapErrorType::MissingRequiredArgument); -} \ No newline at end of file +}