Skip to content

Commit

Permalink
feat(MultipleValues): add support for minimum and maximum number of v…
Browse files Browse the repository at this point in the history
…alues
  • Loading branch information
kbknapp committed Apr 30, 2015
1 parent ae09f05 commit 53f6b8c
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 11 deletions.
52 changes: 45 additions & 7 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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),
});
}

Expand Down Expand Up @@ -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())
}
});
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -1727,13 +1741,37 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
true, true, Some(matches.args.keys().map(|k| *k).collect::<Vec<_>>()));
}
}
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::<Vec<_>>()));
}
}
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::<Vec<_>>()));
}
}
} 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 {
self.report_error(format!("The argument {} requires {} 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::<Vec<_>>()));
}
}
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::<Vec<_>>()));
}
}
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::<Vec<_>>()));
}
}
}
}
}
Expand Down
86 changes: 85 additions & 1 deletion src/args/arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ pub struct Arg<'n, 'l, 'h, 'g, 'p, 'r> {
pub val_names: Option<Vec<&'p str>>,
#[doc(hidden)]
pub num_vals: Option<u8>,
#[doc(hidden)]
pub max_vals: Option<u8>,
#[doc(hidden)]
pub min_vals: Option<u8>,
}

impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> {
Expand Down Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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,
}
}
Expand Down Expand Up @@ -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 <file>` 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 <file>` 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 <file>` 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<T, I>(mut self, names: I)
-> Arg<'n, 'l, 'h, 'g, 'p, 'r>
where T: AsRef<str> + 'p,
Expand Down
2 changes: 2 additions & 0 deletions src/args/argbuilder/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub struct OptBuilder<'n> {
/// this flag is used
pub requires: Option<HashSet<&'n str>>,
pub num_vals: Option<u8>,
pub min_vals: Option<u8>,
pub max_vals: Option<u8>,
pub val_names: Option<Vec<&'n str>>
}

Expand Down
2 changes: 2 additions & 0 deletions src/args/argbuilder/positional.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub struct PosBuilder<'n> {
/// The index of the argument
pub index: u8,
pub num_vals: Option<u8>,
pub max_vals: Option<u8>,
pub min_vals: Option<u8>,
}

impl<'n> Display for PosBuilder<'n> {
Expand Down
4 changes: 2 additions & 2 deletions src/args/argmatches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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[..]);
}
}
Expand Down Expand Up @@ -128,7 +128,7 @@ impl<'n, 'a> ArgMatches<'n, 'a> {
pub fn values_of<'na>(&'a self, name: &'na str) -> Option<Vec<&'a str>> {
if let Some(ref arg) = self.args.get(name) {
if let Some(ref vals) = arg.values {
return Some(vals.iter().map(|s| &s[..]).collect::<Vec<_>>());
return Some(vals.values().map(|s| &s[..]).collect::<Vec<_>>());
}
}
None
Expand Down
4 changes: 3 additions & 1 deletion src/args/matchedarg.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::collections::BTreeMap;

#[doc(hidden)]
pub struct MatchedArg {
// #[doc(hidden)]
// pub name: String,
#[doc(hidden)]
pub occurrences: u8,
#[doc(hidden)]
pub values: Option<Vec<String>>
pub values: Option<BTreeMap<u8, String>>
}

0 comments on commit 53f6b8c

Please sign in to comment.