diff --git a/src/app.rs b/src/app.rs index 60ca01ca..4f2e9ae2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, BTreeSet, HashSet, HashMap, VecDeque}; +use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque}; use std::env; use std::io::{self, BufRead, Write}; use std::path::Path; @@ -53,7 +53,7 @@ enum DidYouMeanMessageStyle { EnumValue, } -/// Some application options +/// Application level settings, which affect how `App` operates pub enum AppSettings { /// Allows subcommands to override all requirements of the parent (this command). For example /// if you had a subcommand or even top level application which had a required arguments that @@ -245,10 +245,10 @@ pub struct App<'a, 'v, 'ab, 'u, 'h, 'ar> { subcommands: BTreeMap>, help_short: Option, version_short: Option, - required: HashSet<&'ar str>, - short_list: HashSet, - long_list: HashSet<&'ar str>, - blacklist: HashSet<&'ar str>, + required: Vec<&'ar str>, + short_list: Vec, + long_list: Vec<&'ar str>, + blacklist: Vec<&'ar str>, usage_str: Option<&'u str>, bin_name: Option, usage: Option, @@ -267,7 +267,7 @@ pub struct App<'a, 'v, 'ab, 'u, 'h, 'ar> { // None = not set, Some(true) set for all children, Some(false) = disable version versionless_scs: Option, unified_help: bool, - overrides: HashSet<&'ar str>, + overrides: Vec<&'ar str>, } impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ @@ -301,12 +301,12 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ needs_subcmd_help: true, help_short: None, version_short: None, - required: HashSet::new(), - short_list: HashSet::new(), - long_list: HashSet::new(), + required: vec![], + short_list: vec![], + long_list: vec![], usage_str: None, usage: None, - blacklist: HashSet::new(), + blacklist: vec![], bin_name: None, groups: HashMap::new(), subcmds_neg_reqs: false, @@ -319,7 +319,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ global_ver: false, versionless_scs: None, unified_help: false, - overrides: HashSet::new() + overrides: vec![] } } @@ -801,25 +801,20 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ if let Some(grp) = a.group { let ag = self.groups.entry(grp).or_insert(ArgGroup::with_name(grp)); ag.args.insert(a.name); - // Leaving this commented out for now...I'm not sure if having a required argument in - // a in required group is bad...It has it's uses - // assert!(!a.required, - // format!("Arguments may not be required AND part of a required group\n\n\t{} is \ - // required and also part of the {} group\n\n\tEither remove the requirement \ - // from the group, or the argument.", a.name, grp)); } if let Some(s) = a.short { if self.short_list.contains(&s) { panic!("Argument short must be unique\n\n\t-{} is already in use", s); } else { - self.short_list.insert(s); + self.short_list.push(s); } } if let Some(l) = a.long { - if self.long_list.contains(l) { + self.long_list.dedup(); + if self.long_list.contains(&l) { panic!("Argument long must be unique\n\n\t--{} is already in use", l); } else { - self.long_list.insert(l); + self.long_list.push(l); } if l == "help" { self.needs_long_help = false; @@ -828,7 +823,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } } if a.required { - self.required.insert(a.name); + self.required.push(a.name); } if a.index.is_some() || (a.short.is_none() && a.long.is_none()) { let i = if a.index.is_none() { @@ -888,21 +883,30 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ // Check if there is anything in the blacklist (mutually excludes list) and add any // values if let Some(ref bl) = a.blacklist { - let mut bhs = HashSet::new(); + let mut bhs = vec![]; // without derefing n = &&str - for n in bl { bhs.insert(*n); } + for n in bl { bhs.push(*n); } + bhs.dedup(); pb.blacklist = Some(bhs); } + if let Some(ref or) = a.overrides { + let mut bhs = vec![]; + // without derefing n = &&str + for n in or { bhs.push(*n); } + bhs.dedup(); + pb.overrides = Some(bhs); + } // Check if there is anything in the requires list and add any values if let Some(ref r) = a.requires { - let mut rhs = HashSet::new(); + let mut rhs = vec![]; // without derefing n = &&str for n in r { - rhs.insert(*n); + rhs.push(*n); if pb.required { - self.required.insert(*n); + self.required.push(*n); } } + rhs.dedup(); pb.requires = Some(rhs); } // Check if there is anything in the possible values and add those as well @@ -955,9 +959,10 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ // Check if there is anything in the blacklist (mutually excludes list) and add any // values if let Some(ref bl) = a.blacklist { - let mut bhs = HashSet::new(); + let mut bhs = vec![]; // without derefing n = &&str - for n in bl { bhs.insert(*n); } + for n in bl { bhs.push(*n); } + bhs.dedup(); ob.blacklist = Some(bhs); } if let Some(ref p) = a.validator { @@ -965,16 +970,24 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } // Check if there is anything in the requires list and add any values if let Some(ref r) = a.requires { - let mut rhs = HashSet::new(); + let mut rhs = vec![]; // without derefing n = &&str for n in r { - rhs.insert(*n); + rhs.push(*n); if ob.required { - self.required.insert(*n); + self.required.push(*n); } } + rhs.dedup(); ob.requires = Some(rhs); } + if let Some(ref or) = a.overrides { + let mut bhs = vec![]; + // without derefing n = &&str + for n in or { bhs.push(*n); } + bhs.dedup(); + ob.overrides = Some(bhs); + } // Check if there is anything in the possible values and add those as well if let Some(ref p) = a.possible_vals { let mut phs = BTreeSet::new(); @@ -1017,18 +1030,27 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ // Check if there is anything in the blacklist (mutually excludes list) and add any // values if let Some(ref bl) = a.blacklist { - let mut bhs = HashSet::new(); + let mut bhs = vec![]; // without derefing n = &&str - for n in bl { bhs.insert(*n); } + for n in bl { bhs.push(*n); } + bhs.dedup(); fb.blacklist = Some(bhs); } // Check if there is anything in the requires list and add any values if let Some(ref r) = a.requires { - let mut rhs = HashSet::new(); + let mut rhs = vec![]; // without derefing n = &&str - for n in r { rhs.insert(*n); } + for n in r { rhs.push(*n); } + rhs.dedup(); fb.requires = Some(rhs); } + if let Some(ref or) = a.overrides { + let mut bhs = vec![]; + // without derefing n = &&str + for n in or { bhs.push(*n); } + bhs.dedup(); + fb.overrides = Some(bhs); + } self.flags.insert(a.name, fb); } if a.global { @@ -1144,15 +1166,15 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ /// # ; pub fn arg_group(mut self, group: ArgGroup<'ar, 'ar>) -> Self { if group.required { - self.required.insert(group.name); + self.required.push(group.name); if let Some(ref reqs) = group.requires { for r in reqs { - self.required.insert(r); + self.required.push(r); } } if let Some(ref bl) = group.conflicts { for b in bl { - self.blacklist.insert(b); + self.blacklist.push(b); } } } @@ -1800,9 +1822,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ /// .get_matches(); /// ``` pub fn get_matches(self) -> ArgMatches<'ar, 'ar> { - let args: Vec<_> = env::args().collect(); - - self.get_matches_from(args) + // Start the parsing + self.get_matches_from(env::args()) } /// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls @@ -1829,12 +1850,20 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ -> ArgMatches<'ar, 'ar> where I: IntoIterator, T: AsRef { + // Verify all positional assertions pass self.verify_positionals(); + // If there are global arguments, we need to propgate them down to subcommands before + // parsing incase we run into a subcommand self.propogate_globals(); let mut matches = ArgMatches::new(); let mut it = itr.into_iter(); + // Get the name of the program (argument 1 of env::args()) and determine the actual file + // that was used to execute the program. This is because a program called + // ./target/release/my_prog -a + // will have two arguments, './target/release/my_prog', '-a' but we don't want to display + // the full path when displaying help messages and such if let Some(name) = it.next() { let p = Path::new(name.as_ref()); if let Some(f) = p.file_name() { @@ -1845,6 +1874,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } } } + + // do the real parsing self.get_matches_with(&mut matches, &mut it); matches @@ -1880,7 +1911,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ for (_, p) in self.positionals_idx.iter_mut().rev() { if found { p.required = true; - self.required.insert(p.name); + self.required.push(p.name); continue; } if p.required { @@ -1891,6 +1922,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ fn propogate_globals(&mut self) { for (_,sc) in self.subcommands.iter_mut() { + // We have to create a new scope in order to tell rustc the borrow of `sc` is done and + // to recursively call this method { for a in self.global_args.iter() { sc.add_arg(a.into()); @@ -1926,6 +1959,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ fn get_matches_with(&mut self, matches: &mut ArgMatches<'ar, 'ar>, it: &mut I) where I: Iterator, T: AsRef { + + // First we create the `--help` and `--version` arguments and add them if necessary self.create_help_and_version(); let mut pos_only = false; @@ -1936,14 +1971,26 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ while let Some(arg) = it.next() { let arg_slice = arg.as_ref(); 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 }; + + // 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.contains_key(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.get(nvo) { + // Check the possible values if let Some(ref p_vals) = opt.possible_vals { if !p_vals.is_empty() { if !p_vals.contains(arg_slice) { @@ -1952,6 +1999,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } } } + // Check the required number of values if let Some(num) = opt.num_vals { if let Some(ref ma) = matches.args.get(opt.name) { if let Some(ref vals) = ma.values { @@ -1971,6 +2019,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } } + // if it's an empty value, and we don't allow that, report the error if !opt.empty_vals && matches.args.contains_key(opt.name) && arg_slice.is_empty() { @@ -1980,8 +2029,18 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ Some(matches.args.keys() .map(|k| *k).collect())); } + + // save the value to matched option if let Some(ref mut o) = matches.args.get_mut(opt.name) { - // Options have values, so we can unwrap() + // if it's multiple; the occurrences are increased when originally found + o.occurrences = if opt.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 { if let Some(ref vtor) = opt.validator { if let Err(e) = vtor(arg_slice.to_owned()) { @@ -1990,19 +2049,21 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ Some(vec![opt.name])); } } + + // 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()); - } - // if it's multiple the occurrences are increased when originall found - o.occurrences = if opt.multiple { - o.occurrences + 1 - } else { - skip = true; - 1 - }; - if let Some(ref vals) = o.values { - let len = vals.len() as u8; + // 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 { @@ -2014,9 +2075,11 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ 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 } } @@ -2025,10 +2088,13 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } } } + + // 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.get(name) { if !o.multiple { self.report_error( @@ -2040,17 +2106,23 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } } + 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; } - // Single flag, or option long version + + // This arg is either an option or flag using a long (i.e. '--something') needs_val_of = self.parse_long_arg(matches, arg_slice); } 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') needs_val_of = self.parse_short_arg(matches, arg_slice); } else { // Positional or Subcommand + // // If the user pased `--` we don't check for subcommands, because the argument they // may be trying to pass might match a subcommand name if !pos_only { @@ -2060,9 +2132,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } subcmd_name = Some(arg_slice.to_owned()); break; - } - - if let Some(candidate_subcommand) = did_you_mean(arg_slice, + } else if let Some(candidate_subcommand) = did_you_mean(arg_slice, self.subcommands.keys()) { self.report_error( format!("The subcommand '{}' isn't valid\n\tDid you mean '{}' ?\n\n\ @@ -2078,6 +2148,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } } + // Did the developer even define any valid positionals? Since we reached this far, + // it's not a subcommand if self.positionals_idx.is_empty() { self.report_error( format!("Found argument '{}', but {} wasn't expecting any", @@ -2085,13 +2157,14 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ self.bin_name.clone().unwrap_or(self.name.clone())), true, Some(matches.args.keys().map(|k| *k).collect())); - } - // If we find that an argument requires a positiona, we need to update all the - // previous positionals too. This will denote where to start - // let mut req_pos_from_name = None; - if let Some(p) = self.positionals_idx.get(&pos_counter) { - if self.blacklist.contains(p.name) { - matches.args.remove(p.name); + } else if let Some(p) = self.positionals_idx.get(&pos_counter) { + // Make sure this one doesn't conflict with anything + self.blacklist.dedup(); + if self.blacklist.contains(&p.name) { + // we shouldn't need to remove this arg...since it should be matched yet + // anyways + // matches.args.remove(p.name); + self.report_error(format!("The argument '{}' cannot be used with {}", Format::Warning(p.to_string()), match self.blacklisted_from(p.name, &matches) { @@ -2103,6 +2176,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ Some(matches.args.keys().map(|k| *k).collect())); } + if let Some(ref p_vals) = p.possible_vals { if !p_vals.is_empty() { if !p_vals.contains(arg_slice) { @@ -2111,6 +2185,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } } } + // Have we made the update yet? let mut done = false; if p.multiple { @@ -2146,12 +2221,27 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ vals.insert(len, arg_slice.to_owned()); } } + } 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 { + self.overrides.dedup(); + if self.overrides.contains(&p.name) { + if let Some(name) = self.overriden_from(p.name, matches) { + matches.args.remove(&*name); + remove_override!(self, &*name); + } + } + if let Some(ref or) = p.overrides { + for pa in or { + matches.args.remove(pa); + remove_override!(self, pa); + self.overrides.push(pa); + } + } let mut bm = BTreeMap::new(); if !p.empty_vals && arg_slice.is_empty() { self.report_error(format!("The argument '{}' does not allow empty \ @@ -2173,27 +2263,29 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ occurrences: 1, values: Some(bm), }); - } - if let Some(ref bl) = p.blacklist { - for name in bl { - self.blacklist.insert(name); - self.required.remove(name); + if let Some(ref bl) = p.blacklist { + for name in bl { + self.blacklist.push(name); + vec_remove!(self.required, name); + } } - } - self.required.remove(p.name); - 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;} + // Because of the macro call, we have to create a temp variable + let pname = &p.name; + vec_remove!(self.required, pname); + 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.insert(n); + self.required.push(n); + } } - } - parse_group_reqs!(self, p); + parse_group_reqs!(self, p); + } } else { self.report_error(format!("The argument '{}' was found, but '{}' wasn't \ @@ -2251,7 +2343,6 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ self.validate_blacklist(matches); self.validate_num_args(matches); - matches.usage = Some(self.create_usage(None)); if let Some(sc_name) = subcmd_name { @@ -2325,14 +2416,14 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ for k in matches.args.keys() { if let Some(f) = self.flags.get(k) { if let Some(ref bl) = f.blacklist { - if bl.contains(name) { + if bl.contains(&name) { return Some(format!("{}", f)) } } } if let Some(o) = self.opts.get(k) { if let Some(ref bl) = o.blacklist { - if bl.contains(name) { + if bl.contains(&name) { return Some(format!("{}", o)) } } @@ -2340,7 +2431,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ if let Some(idx) = self.positionals_name.get(k) { if let Some(pos) = self.positionals_idx.get(idx) { if let Some(ref bl) = pos.blacklist { - if bl.contains(name) { + if bl.contains(&name) { return Some(format!("{}", pos)) } } @@ -2350,6 +2441,35 @@ 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() { + if let Some(f) = self.flags.get(k) { + if let Some(ref bl) = f.overrides { + if bl.contains(&name) { + return Some(f.name) + } + } + } + if let Some(o) = self.opts.get(k) { + if let Some(ref bl) = o.overrides { + if bl.contains(&name) { + return Some(o.name) + } + } + } + if let Some(idx) = self.positionals_name.get(k) { + if let Some(pos) = self.positionals_idx.get(idx) { + if let Some(ref bl) = pos.overrides { + if bl.contains(&name) { + return Some(pos.name) + } + } + } + } + } + None + } + fn create_help_and_version(&mut self) { // name is "hclap_help" because flags are sorted by name if self.needs_long_help { @@ -2367,7 +2487,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ requires: None, overrides: None }; - self.long_list.insert("help"); + self.long_list.push("help"); self.flags.insert("hclap_help", arg); } if self.needs_long_version @@ -2388,7 +2508,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ requires: None, overrides: None }; - self.long_list.insert("version"); + self.long_list.push("version"); self.flags.insert("vclap_version", arg); } if self.needs_subcmd_help && !self.subcommands.is_empty() { @@ -2443,15 +2563,19 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ .filter(|&v| v.long.is_some()) .filter(|&v| v.long.unwrap() == arg).nth(0) { // Ensure this option isn't on the master mutually excludes list - if self.blacklist.contains(v.name) { + if self.blacklist.contains(&v.name) { matches.args.remove(v.name); self.report_error(format!("The argument '{}' cannot be used with one or more of \ the other specified arguments", Format::Warning(format!("--{}", arg))), true, Some(matches.args.keys().map(|k| *k).collect())); } - if self.overrides.contains(v.name) { - matches.args.remove(v.name); + if let Some(ref or) = v.overrides { + for pa in or { + matches.args.remove(pa); + remove_override!(self, pa); + self.overrides.push(pa); + } } if matches.args.contains_key(v.name) { @@ -2522,21 +2646,16 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } }); } - if let Some(ref ov) = v.overrides { - for name in ov { - self.overrides.insert(name); - self.required.remove(name); - } - } if let Some(ref bl) = v.blacklist { for name in bl { - self.blacklist.insert(name); - self.overrides.remove(name); - self.required.remove(name); + self.blacklist.push(name); + vec_remove!(self.overrides, name); + vec_remove!(self.required, name); } } - self.required.remove(v.name); + let vname = &v.name; + vec_remove!(self.required, vname); if let Some(ref reqs) = v.requires { // Add all required args which aren't already found in matches to the @@ -2544,7 +2663,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ for n in reqs { if matches.args.contains_key(n) { continue; } - self.required.insert(n); + self.required.push(n); } } @@ -2560,7 +2679,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ .filter(|&v| v.long.is_some()) .filter(|&v| v.long.unwrap() == arg).nth(0) { // Ensure this flag isn't on the mutually excludes list - if self.blacklist.contains(v.name) { + self.blacklist.dedup(); + if self.blacklist.contains(&v.name) { matches.args.remove(v.name); self.report_error(format!("The argument '{}' cannot be used with {}", Format::Warning(v.to_string()), @@ -2571,8 +2691,12 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ true, Some(matches.args.keys().map(|k| *k).collect())); } - if self.overrides.contains(v.name) { - matches.args.remove(v.name); + if let Some(ref or) = v.overrides { + for pa in or { + matches.args.remove(pa); + remove_override!(self, pa); + self.overrides.push(pa); + } } // Make sure this isn't one being added multiple times if it doesn't suppor it @@ -2599,20 +2723,21 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ // If this flag was requierd, remove it // .. even though Flags shouldn't be required - self.required.remove(v.name); + let vname = &v.name; + vec_remove!(self.required, vname); // 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.insert(name); - self.required.remove(name); + self.overrides.push(name); + vec_remove!(self.required, name); } } if let Some(ref bl) = v.blacklist { for name in bl { - self.blacklist.insert(name); - self.overrides.remove(name); - self.required.remove(name); + self.blacklist.push(name); + vec_remove!(self.overrides, name); + vec_remove!(self.required, name); } } @@ -2621,7 +2746,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ for n in reqs { if matches.args.contains_key(n) { continue; } - self.required.insert(n); + self.required.push(n); } } @@ -2703,7 +2828,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ .filter(|&v| v.short.is_some()) .filter(|&v| v.short.unwrap() == arg_c).nth(0) { // Ensure this option isn't on the master mutually excludes list - if self.blacklist.contains(v.name) { + self.blacklist.dedup(); + if self.blacklist.contains(&v.name) { matches.args.remove(v.name); self.report_error(format!("The argument '{}' cannot be used with {}", Format::Warning(format!("-{}", arg)), @@ -2714,8 +2840,19 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ true, Some(matches.args.keys().map(|k| *k).collect())); } - if self.overrides.contains(v.name) { - matches.args.remove(v.name); + self.overrides.dedup(); + if self.overrides.contains(&v.name) { + if let Some(name) = self.overriden_from(v.name, matches) { + matches.args.remove(&*name); + remove_override!(self, &*name); + } + } + if let Some(ref or) = v.overrides { + for pa in or { + matches.args.remove(pa); + remove_override!(self, pa); + self.overrides.push(pa); + } } if matches.args.contains_key(v.name) { @@ -2733,21 +2870,16 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ values: Some(BTreeMap::new()) }); } - if let Some(ref ov) = v.overrides { - for name in ov { - self.overrides.insert(name); - self.required.remove(name); - } - } if let Some(ref bl) = v.blacklist { for name in bl { - self.blacklist.insert(name); - self.overrides.remove(name); - self.required.remove(name); + self.blacklist.push(name); + vec_remove!(self.overrides, name); + vec_remove!(self.required, name); } } - self.required.remove(v.name); + let vname = &v.name; + vec_remove!(self.required, vname); if let Some(ref reqs) = v.requires { // Add all required args which aren't already found in matches to the @@ -2755,7 +2887,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ for n in reqs { if matches.args.contains_key(n) { continue; } - self.required.insert(n); + self.required.push(n); } } @@ -2774,11 +2906,12 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } fn parse_single_short_flag(&mut self, matches: &mut ArgMatches<'ar, 'ar>, arg: char) -> bool { - for v in self.flags.values() + if let Some(v) = self.flags.values() .filter(|&v| v.short.is_some()) - .filter(|&v| v.short.unwrap() == arg) { + .filter(|&v| v.short.unwrap() == arg).nth(0) { // Ensure this flag isn't on the mutually excludes list - if self.blacklist.contains(v.name) { + self.blacklist.dedup(); + if self.blacklist.contains(&v.name) { matches.args.remove(v.name); self.report_error(format!("The argument '{}' cannot be used {}", Format::Warning(format!("-{}", arg)), @@ -2790,8 +2923,23 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ true, Some(matches.args.keys().map(|k| *k).collect())); } - if self.overrides.contains(v.name) { - matches.args.remove(v.name); + self.overrides.dedup(); + debugln!("checking if {} is in overrides", v.name); + if self.overrides.contains(&v.name) { + debugln!("it is..."); + debugln!("checking who defined it..."); + if let Some(name) = self.overriden_from(v.name, matches) { + debugln!("found {}", name); + matches.args.remove(name); + remove_override!(self, name); + } + } + if let Some(ref or) = v.overrides { + for pa in or { + matches.args.remove(pa); + remove_override!(self, pa); + self.overrides.push(pa); + } } // Make sure this isn't one being added multiple times if it doesn't suppor it @@ -2818,20 +2966,14 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ // If this flag was requierd, remove it // .. even though Flags shouldn't be required - self.required.remove(v.name); + let vname = &v.name; + vec_remove!(self.required, vname); - // 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.insert(name); - self.required.remove(name); - } - } if let Some(ref bl) = v.blacklist { for name in bl { - self.blacklist.insert(name); - self.overrides.remove(name); - self.required.remove(name); + self.blacklist.push(name); + vec_remove!(self.overrides, name); + vec_remove!(self.required, name); } } @@ -2840,7 +2982,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ for n in reqs { if matches.args.contains_key(n) { continue; } - self.required.insert(n); + self.required.push(n); } } @@ -2994,7 +3136,6 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ fn validate_required(&self, matches: &ArgMatches<'ar, 'ar>) -> bool{ for name in self.required.iter() { validate_reqs!(self, flags, matches, name); - validate_reqs!(self, opts, matches, name); // because positions use different keys, we dont use the macro @@ -3044,4 +3185,5 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ None => (String::new(), None), } } + } \ No newline at end of file diff --git a/src/args/argbuilder/flag.rs b/src/args/argbuilder/flag.rs index 8510c998..37f473a8 100644 --- a/src/args/argbuilder/flag.rs +++ b/src/args/argbuilder/flag.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +// use std::collections::HashSet; use std::fmt::{ Display, Formatter, Result }; pub struct FlagBuilder<'n> { @@ -16,11 +16,11 @@ pub struct FlagBuilder<'n> { pub multiple: bool, /// A list of names for other arguments that /// *may not* be used with this flag - pub blacklist: Option>, + pub blacklist: Option>, /// A list of names of other arguments that /// are *required* to be used when this /// flag is used - pub requires: Option>, + pub requires: Option>, /// The short version (i.e. single character) /// of the argument, no preceding `-` pub short: Option, diff --git a/src/args/argbuilder/option.rs b/src/args/argbuilder/option.rs index 880918e7..dedcbb0a 100644 --- a/src/args/argbuilder/option.rs +++ b/src/args/argbuilder/option.rs @@ -1,5 +1,4 @@ use std::rc::Rc; -use std::collections::HashSet; use std::collections::BTreeSet; use std::fmt::{ Display, Formatter, Result }; use std::result::Result as StdResult; @@ -16,7 +15,7 @@ pub struct OptBuilder<'n> { /// Allow multiple occurrences of an option argument such as "-c some -c other" pub multiple: bool, /// A list of names for other arguments that *may not* be used with this flag - pub blacklist: Option>, + pub blacklist: Option>, /// If this is a required by default when using the command line program /// i.e. a configuration file that's required for the program to function /// **NOTE:** required by default means, it is required *until* mutually @@ -26,7 +25,7 @@ pub struct OptBuilder<'n> { pub possible_vals: Option>, /// A list of names of other arguments that are *required* to be used when /// this flag is used - pub requires: Option>, + pub requires: Option>, pub num_vals: Option, pub min_vals: Option, pub max_vals: Option, diff --git a/src/args/argbuilder/positional.rs b/src/args/argbuilder/positional.rs index 34369861..a93652d6 100644 --- a/src/args/argbuilder/positional.rs +++ b/src/args/argbuilder/positional.rs @@ -1,4 +1,3 @@ -use std::collections::HashSet; use std::collections::BTreeSet; use std::fmt::{ Display, Formatter, Result }; use std::result::Result as StdResult; @@ -18,9 +17,9 @@ pub struct PosBuilder<'n> { pub multiple: bool, /// A list of names of other arguments that are *required* to be used when /// this flag is used - pub requires: Option>, + pub requires: Option>, /// A list of names for other arguments that *may not* be used with this flag - pub blacklist: Option>, + pub blacklist: Option>, /// A list of possible values for this argument pub possible_vals: Option>, /// The index of the argument @@ -32,7 +31,7 @@ pub struct PosBuilder<'n> { pub global: bool, pub validator: Option StdResult<(), String>>>, /// A list of names for other arguments that *mutually override* this flag - pub overrides: Option>, + pub overrides: Option>, } impl<'n> Display for PosBuilder<'n> { diff --git a/src/macros.rs b/src/macros.rs index 5512023c..a90b2cd5 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -36,6 +36,80 @@ macro_rules! debug { ($fmt:expr, $($arg:tt)*) => (); } +// convienience macro for remove an item from a vec +macro_rules! vec_remove { + ($vec:expr, $to_rem:ident) => { + { + let mut ix = None; + 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_override { + ($me:ident, $name:expr) => ({ + if let Some(ref o) = $me.opts.get($name) { + 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.get($name) { + 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_name.get($name) { + if let Some(ref o) = $me.positionals_idx.get(p) { + 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); + } + } + } + } + }) +} + // De-duplication macro used in src/app.rs macro_rules! print_opt_help { ($me:ident, $opt:ident, $spc:expr) => { @@ -72,15 +146,24 @@ macro_rules! parse_group_reqs { let mut found = false; for name in ag.args.iter() { if name == &$arg.name { - $me.required.remove(ag.name); + let mut ix = None; + for (i, val) in $me.required.iter().enumerate() { + if val == &ag.name { + ix = Some(i); + break; + } + } + if let Some(i) = ix { + $me.required.remove(i); + } if let Some(ref reqs) = ag.requires { for r in reqs { - $me.required.insert(r); + $me.required.push(r); } } if let Some(ref bl) = ag.conflicts { for b in bl { - $me.blacklist.insert(b); + $me.blacklist.push(b); } } found = true; @@ -90,8 +173,18 @@ macro_rules! parse_group_reqs { if found { for name in ag.args.iter() { if name == &$arg.name { continue } - $me.required.remove(name); - $me.blacklist.insert(name); + let mut ix = None; + for (i, val) in $me.required.iter().enumerate() { + if val == name { + ix = Some(i); + break; + } + } + if let Some(i) = ix { + $me.required.remove(i); + } + + $me.blacklist.push(name); } } }