diff --git a/src/app.rs b/src/app.rs index 559bfa53daa..ce498b50767 100644 --- a/src/app.rs +++ b/src/app.rs @@ -300,6 +300,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ requires: None, possible_vals: None, num_vals: a.num_vals, + min_vals: a.min_vals, + max_vals: a.max_vals, help: a.help, }; if pb.num_vals.unwrap_or(0) > 1 && !pb.multiple { @@ -342,6 +344,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ 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, requires: None, required: a.required, @@ -1138,7 +1142,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ if let Some(ref mut o) = matches.args.get_mut(opt.name) { // Options have values, so we can unwrap() if let Some(ref mut vals) = o.values { - vals.push(arg.clone()); + let len = vals.len() as u8 + 1; + vals.insert(len, arg.clone()); } // if it's multiple the occurrences are increased when originall found @@ -1227,7 +1232,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ done = true; pos.occurrences += 1; if let Some(ref mut vals) = pos.values { - vals.push(arg.clone()); + let len = (vals.len() + 1) as u8; + vals.insert(len, arg.clone()); } } } else { @@ -1236,9 +1242,11 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } // Was an update made, or is this the first occurrence? if !done { + let mut bm = BTreeMap::new(); + bm.insert(1, arg.clone()); matches.args.insert(p.name, MatchedArg{ occurrences: 1, - values: Some(vec![arg.clone()]), + values: Some(bm), }); } @@ -1438,15 +1446,21 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ if let Some(ref mut o) = matches.args.get_mut(v.name) { o.occurrences += 1; if let Some(ref mut vals) = o.values { - vals.push(arg_val.clone().unwrap()); + let len = (vals.len() + 1) as u8; + vals.insert(len, arg_val.clone().unwrap()); } } } } else { matches.args.insert(v.name, MatchedArg{ - // name: v.name.to_owned(), occurrences: if arg_val.is_some() { 1 } else { 0 }, - values: if arg_val.is_some() { Some(vec![arg_val.clone().unwrap()])} else { Some(vec![]) } + values: if arg_val.is_some() { + let mut bm = BTreeMap::new(); + bm.insert(1, arg_val.clone().unwrap()); + Some(bm) + } else { + Some(BTreeMap::new()) + } }); } @@ -1584,7 +1598,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ // name: v.name.to_owned(), // occurrences will be incremented on getting a value occurrences: 0, - values: Some(vec![]) + values: Some(BTreeMap::new()) }); } if let Some(ref bl) = v.blacklist { @@ -1727,6 +1741,18 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ true, true, Some(matches.args.keys().map(|k| *k).collect::>())); } } + if let Some(num) = f.max_vals { + if num > vals.len() as u8 { + self.report_error(format!("The argument {} requires no more than {} values, but {} w{} provided", f, num, vals.len(), if vals.len() == 1 {"as"}else{"ere"}), + true, true, Some(matches.args.keys().map(|k| *k).collect::>())); + } + } + if let Some(num) = f.min_vals { + if num < vals.len() as u8 { + self.report_error(format!("The argument {} requires at least {} values, but {} w{} provided", f, num, vals.len(), if vals.len() == 1 {"as"}else{"ere"}), + true, true, Some(matches.args.keys().map(|k| *k).collect::>())); + } + } } else if let Some(f) = self.positionals_idx.get(self.positionals_name.get(name).unwrap()) { if let Some(num) = f.num_vals { if num != vals.len() as u8 { @@ -1734,6 +1760,18 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ true, true, Some(matches.args.keys().map(|k| *k).collect::>())); } } + if let Some(num) = f.max_vals { + if num > vals.len() as u8 { + self.report_error(format!("The argument {} requires no more than {} values, but {} w{} provided", f, num, vals.len(), if vals.len() == 1 {"as"}else{"ere"}), + true, true, Some(matches.args.keys().map(|k| *k).collect::>())); + } + } + if let Some(num) = f.min_vals { + if num < vals.len() as u8 { + self.report_error(format!("The argument {} requires at least {} values, but {} w{} provided", f, num, vals.len(), if vals.len() == 1 {"as"}else{"ere"}), + true, true, Some(matches.args.keys().map(|k| *k).collect::>())); + } + } } } } diff --git a/src/args/arg.rs b/src/args/arg.rs index d69b287dd48..806a3a4daa0 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -82,6 +82,10 @@ pub struct Arg<'n, 'l, 'h, 'g, 'p, 'r> { pub val_names: Option>, #[doc(hidden)] pub num_vals: Option, + #[doc(hidden)] + pub max_vals: Option, + #[doc(hidden)] + pub min_vals: Option, } impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> { @@ -121,7 +125,9 @@ impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> { requires: None, group: None, num_vals: None, - val_names: None + val_names: None, + max_vals: None, + min_vals: None, } } @@ -157,6 +163,8 @@ impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> { blacklist: None, requires: None, num_vals: None, + min_vals: None, + max_vals: None, val_names: None, group: None, } @@ -275,6 +283,8 @@ impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> { requires: None, num_vals: None, val_names: None, + max_vals: None, + min_vals: None, group: None, } } @@ -638,11 +648,85 @@ impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> { self } + /// Specifies how many values are required to satisfy this argument. For example, if you had a + /// `-f ` argument where you wanted exactly 3 'files' you would set + /// `.number_of_values(3)`, and this argument wouldn't be satisfied unless the user provided + /// 3 and only 3 values. + /// + /// **NOTE:** The argument *must* have `.multiple(true)` or `...` to use this setting. + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// # let matches = App::new("myprog") + /// # .arg( + /// # Arg::with_name("debug").index(1) + /// .number_of_values(3) + /// # ).get_matches(); pub fn number_of_values(mut self, qty: u8) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { self.num_vals = Some(qty); self } + /// Specifies the *maximum* number of values are for this argument. For example, if you had a + /// `-f ` argument where you wanted up to 3 'files' you would set + /// `.max_values(3)`, and this argument would be satisfied if the user provided, 1, 2, or 3 + /// values. + /// + /// **NOTE:** The argument *must* have `.multiple(true)` or `...` to use this setting. + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// # let matches = App::new("myprog") + /// # .arg( + /// # Arg::with_name("debug").index(1) + /// .max_values(3) + /// # ).get_matches(); + pub fn max_values(mut self, qty: u8) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { + self.max_vals = Some(qty); + self + } + + /// Specifies the *minimum* number of values are for this argument. For example, if you had a + /// `-f ` argument where you wanted at least 2 'files' you would set + /// `.min_values(2)`, and this argument would be satisfied if the user provided, 2 or more + /// values. + /// + /// **NOTE:** The argument *must* have `.multiple(true)` or `...` to use this setting. + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// # let matches = App::new("myprog") + /// # .arg( + /// # Arg::with_name("debug").index(1) + /// .min_values(2) + /// # ).get_matches(); + pub fn min_values(mut self, qty: u8) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { + self.min_vals = Some(qty); + self + } + + /// Specifies names for values of option arguments. These names are cosmetic only, used for + /// help and usage strings only. The names are **not** used to access arguments. THe values of + /// the arguments are accessed in numeric order (i.e. if you specify two names `one` and `two` + /// `one` will be the first matched value, `two` will be the second). + /// + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// let val_names = ["one", "two"]; + /// # let matches = App::new("myprog") + /// # .arg( + /// # Arg::with_name("debug").index(1) + /// .value_names(&val_names) + /// # ).get_matches(); pub fn value_names(mut self, names: I) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> where T: AsRef + 'p, diff --git a/src/args/argbuilder/option.rs b/src/args/argbuilder/option.rs index 970fa7e65d3..e8742858bee 100644 --- a/src/args/argbuilder/option.rs +++ b/src/args/argbuilder/option.rs @@ -26,6 +26,8 @@ pub struct OptBuilder<'n> { /// this flag is used pub requires: Option>, pub num_vals: Option, + pub min_vals: Option, + pub max_vals: Option, pub val_names: Option> } diff --git a/src/args/argbuilder/positional.rs b/src/args/argbuilder/positional.rs index 60e7849ea45..cefc0d12190 100644 --- a/src/args/argbuilder/positional.rs +++ b/src/args/argbuilder/positional.rs @@ -24,6 +24,8 @@ pub struct PosBuilder<'n> { /// The index of the argument pub index: u8, pub num_vals: Option, + pub max_vals: Option, + pub min_vals: Option, } impl<'n> Display for PosBuilder<'n> { diff --git a/src/args/argmatches.rs b/src/args/argmatches.rs index 0a23c3c61f2..ce2562378f6 100644 --- a/src/args/argmatches.rs +++ b/src/args/argmatches.rs @@ -99,7 +99,7 @@ impl<'n, 'a> ArgMatches<'n, 'a> { pub fn value_of<'na>(&self, name: &'na str) -> Option<&str> { if let Some(ref arg) = self.args.get(name) { if let Some(ref vals) = arg.values { - if let Some(ref val) = vals.iter().nth(0) { + if let Some(ref val) = vals.values().nth(0) { return Some(&val[..]); } } @@ -128,7 +128,7 @@ impl<'n, 'a> ArgMatches<'n, 'a> { pub fn values_of<'na>(&'a self, name: &'na str) -> Option> { if let Some(ref arg) = self.args.get(name) { if let Some(ref vals) = arg.values { - return Some(vals.iter().map(|s| &s[..]).collect::>()); + return Some(vals.values().map(|s| &s[..]).collect::>()); } } None diff --git a/src/args/matchedarg.rs b/src/args/matchedarg.rs index 275fb7c6d47..2915b5ad710 100644 --- a/src/args/matchedarg.rs +++ b/src/args/matchedarg.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + #[doc(hidden)] pub struct MatchedArg { // #[doc(hidden)] @@ -5,5 +7,5 @@ pub struct MatchedArg { #[doc(hidden)] pub occurrences: u8, #[doc(hidden)] - pub values: Option> + pub values: Option> } \ No newline at end of file