From 912602aa6ef30d7652597d501339e360c5f3d063 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Thu, 28 Jan 2021 13:54:20 +0000 Subject: [PATCH] Add number control of grouped value number. --- src/build/arg/mod.rs | 24 +++++++++ src/parse/matches/matched_arg.rs | 8 +++ src/parse/validator.rs | 86 +++++++++++++++++++++++++----- tests/app_settings.rs | 2 +- tests/grouped_values.rs | 90 ++++++++++++++++++++++++++++++++ tests/multiple_values.rs | 2 +- tests/posix_compatible.rs | 2 +- 7 files changed, 199 insertions(+), 15 deletions(-) diff --git a/src/build/arg/mod.rs b/src/build/arg/mod.rs index 63303f58f3cf..c84637d662b2 100644 --- a/src/build/arg/mod.rs +++ b/src/build/arg/mod.rs @@ -89,6 +89,9 @@ pub struct Arg<'help> { pub(crate) num_vals: Option, pub(crate) max_vals: Option, pub(crate) min_vals: Option, + pub(crate) grouped_num_vals: Option, + pub(crate) grouped_max_vals: Option, + pub(crate) grouped_min_vals: Option, pub(crate) validator: Option>>>, pub(crate) validator_os: Option>>>, pub(crate) val_delim: Option, @@ -1851,6 +1854,13 @@ impl<'help> Arg<'help> { self.takes_value(true) } + /// Placeholder documentation + #[inline] + pub fn grouped_number_of_values(mut self, qty: u64) -> Self { + self.grouped_num_vals = Some(qty); + self.takes_value(true) + } + /// Allows one to perform a custom validation on the argument value. You provide a closure /// which accepts a [`String`] value, and return a [`Result`] where the [`Err(String)`] is a /// message displayed to the user. @@ -2073,6 +2083,13 @@ impl<'help> Arg<'help> { self.takes_value(true).multiple_values(true) } + /// placeholder documentation + #[inline] + pub fn grouped_max_values(mut self, qty: u64) -> Self { + self.grouped_max_vals = Some(qty); + self.takes_value(true).multiple_values(true) + } + /// Specifies the *minimum* number of values 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 @@ -2136,6 +2153,13 @@ impl<'help> Arg<'help> { self.takes_value(true) } + /// placeholder documentation + #[inline] + pub fn grouped_min_values(mut self, qty: u64) -> Self { + self.grouped_min_vals = Some(qty); + self.takes_value(true) + } + /// Specifies the separator to use when values are clumped together, defaults to `,` (comma). /// /// **NOTE:** implicitly sets [`Arg::use_delimiter(true)`] diff --git a/src/parse/matches/matched_arg.rs b/src/parse/matches/matched_arg.rs index 87d4e043f13b..93a573f4de98 100644 --- a/src/parse/matches/matched_arg.rs +++ b/src/parse/matches/matched_arg.rs @@ -85,6 +85,14 @@ impl MatchedArg { self.vals.last().map(|x| x.len()).unwrap_or(0) } + pub(crate) fn min_num_vals_group(&self) -> usize { + self.vals.iter().map(|x| x.len()).min().unwrap_or(0) + } + + pub(crate) fn max_num_vals_group(&self) -> usize { + self.vals.iter().map(|x| x.len()).max().unwrap_or(0) + } + pub(crate) fn no_val(&self) -> bool { self.vals.iter().flatten().count() == 0 } diff --git a/src/parse/validator.rs b/src/parse/validator.rs index f9daddbc7f0b..fbcd770bbaa1 100644 --- a/src/parse/validator.rs +++ b/src/parse/validator.rs @@ -442,30 +442,74 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> { fn validate_arg_num_vals(&self, a: &Arg, ma: &MatchedArg) -> ClapResult<()> { debug!("Validator::validate_arg_num_vals"); if let Some(num) = a.num_vals { + let num_vals = ma.num_vals() as u64; debug!("Validator::validate_arg_num_vals: num_vals set...{}", num); - let should_err = if a.is_set(ArgSettings::MultipleValues) { - ((ma.num_vals() as u64) % num) != 0 + let incorrect_num = if num_vals != num { + Some(num_vals) } else { - num != (ma.num_vals() as u64) + None }; - if should_err { + if let Some(incorrect_num) = incorrect_num { debug!("Validator::validate_arg_num_vals: Sending error WrongNumberOfValues"); return Err(Error::wrong_number_of_values( a, num, - if a.is_set(ArgSettings::MultipleValues) { - ma.num_vals() % num as usize - } else { - ma.num_vals() - }, + incorrect_num as usize, + Usage::new(self.p).create_usage_with_title(&[]), + self.p.app.color(), + )); + } + } + if let Some(num) = a.grouped_num_vals { + let max_num_vals = ma.max_num_vals_group() as u64; + let min_num_vals = ma.min_num_vals_group() as u64; + debug!( + "Validator::validate_arg_num_vals: grouped_num_vals set...{}", + num + ); + let incorrect_num = if max_num_vals != num { + Some(max_num_vals) + } else if min_num_vals != num { + Some(min_num_vals) + } else { + None + }; + if let Some(incorrect_num) = incorrect_num { + debug!("Validator::validate_arg_num_vals: Sending error WrongNumberOfValues"); + return Err(Error::wrong_number_of_values( + a, + num, + incorrect_num as usize, Usage::new(self.p).create_usage_with_title(&[]), self.p.app.color(), )); } } if let Some(num) = a.max_vals { + let num_vals = ma.num_vals() as u64; debug!("Validator::validate_arg_num_vals: max_vals set...{}", num); - if (ma.num_vals() as u64) > num { + if num_vals > num { + debug!("Validator::validate_arg_num_vals: Sending error TooManyValues"); + return Err(Error::too_many_values( + ma.vals_flatten() + .last() + .expect(INTERNAL_ERROR_MSG) + .to_str() + .expect(INVALID_UTF8) + .to_string(), + a, + Usage::new(self.p).create_usage_with_title(&[]), + self.p.app.color(), + )); + } + } + if let Some(num) = a.grouped_max_vals { + let max_num_vals = ma.max_num_vals_group() as u64; + debug!( + "Validator::validate_arg_num_vals: grouped_max_vals set...{}", + num + ); + if max_num_vals > num { debug!("Validator::validate_arg_num_vals: Sending error TooManyValues"); return Err(Error::too_many_values( ma.vals_flatten() @@ -481,13 +525,14 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> { } } let min_vals_zero = if let Some(num) = a.min_vals { + let num_vals = ma.num_vals() as u64; debug!("Validator::validate_arg_num_vals: min_vals set: {}", num); - if (ma.num_vals() as u64) < num && num != 0 { + if num_vals < num && num != 0 { debug!("Validator::validate_arg_num_vals: Sending error TooFewValues"); return Err(Error::too_few_values( a, num, - ma.num_vals(), + num_vals as usize, Usage::new(self.p).create_usage_with_title(&[]), self.p.app.color(), )); @@ -496,6 +541,23 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> { } else { false }; + if let Some(num) = a.grouped_min_vals { + let min_num_vals = ma.min_num_vals_group() as u64; + debug!( + "Validator::validate_arg_num_vals: grouped_min_vals set: {}", + num + ); + if min_num_vals < num { + debug!("Validator::validate_arg_num_vals: Sending error TooFewValues"); + return Err(Error::too_few_values( + a, + num, + min_num_vals as usize, + Usage::new(self.p).create_usage_with_title(&[]), + self.p.app.color(), + )); + } + } // Issue 665 (https://github.com/kbknapp/clap-rs/issues/665) // Issue 1105 (https://github.com/kbknapp/clap-rs/issues/1105) if a.is_set(ArgSettings::TakesValue) && !min_vals_zero && ma.no_val() { diff --git a/tests/app_settings.rs b/tests/app_settings.rs index b971ad19acd9..7edc54370ebb 100644 --- a/tests/app_settings.rs +++ b/tests/app_settings.rs @@ -944,7 +944,7 @@ fn aaos_opts_mult() { .setting(AppSettings::AllArgsOverrideSelf) .arg( Arg::from("--opt [val]... 'some option'") - .number_of_values(1) + .grouped_min_values(1) .require_delimiter(true), ) .try_get_matches_from(vec!["", "--opt=some", "--opt=other", "--opt=one,two"]); diff --git a/tests/grouped_values.rs b/tests/grouped_values.rs index d6a5ca06a9a7..6e0d8df93017 100644 --- a/tests/grouped_values.rs +++ b/tests/grouped_values.rs @@ -188,3 +188,93 @@ fn issue_2171() { let _ = schema.clone().try_get_matches_from(argv).unwrap(); } } + +#[test] +fn grouped_min_values_test() { + let m = App::new("myapp") + .arg( + Arg::new("option") + .short('o') + .takes_value(true) + .use_delimiter(true) + .multiple(true) + .grouped_min_values(1), + ) + .get_matches_from(vec!["myapp", "-o=foo", "-o=val1,val2,val3", "-o=bar"]); + let grouped_vals: Vec<_> = m.grouped_values_of("option").unwrap().collect(); + assert_eq!( + grouped_vals, + vec![vec!["foo"], vec!["val1", "val2", "val3"], vec!["bar"]] + ); +} + +#[test] +fn grouped_max_values_test() { + let m = App::new("myapp") + .arg( + Arg::new("option") + .short('o') + .takes_value(true) + .use_delimiter(true) + .multiple(true) + .grouped_max_values(3), + ) + .get_matches_from(vec!["myapp", "-o=foo", "-o=val1,val2,val3", "-o=bar"]); + let grouped_vals: Vec<_> = m.grouped_values_of("option").unwrap().collect(); + assert_eq!( + grouped_vals, + vec![vec!["foo"], vec!["val1", "val2", "val3"], vec!["bar"]] + ); +} + +#[test] +fn grouped_min_values_failed() { + static MIN_VALS_FAILED: &str = + "error: The argument '-o