diff --git a/src/parse/matches/arg_matches.rs b/src/parse/matches/arg_matches.rs index 9fbc06c3..bae62f0d 100644 --- a/src/parse/matches/arg_matches.rs +++ b/src/parse/matches/arg_matches.rs @@ -242,8 +242,21 @@ impl ArgMatches { } /// Placeholder documentation. - pub fn grouped_values_of(&self, id: T) -> Option>> { - self.args.get(&Id::from(id)).map(|arg| arg.vals()) + pub fn grouped_values_of(&self, id: T) -> Option { + #[allow(clippy::type_complexity)] + let arg_values: for<'a> fn( + &'a MatchedArg, + ) -> Map< + std::slice::Iter<'a, Vec>, + fn(&Vec) -> Vec<&str>, + > = |arg| { + arg.vals() + .map(|g| g.iter().map(|x| x.to_str().expect(INVALID_UTF8)).collect()) + }; + self.args + .get(&Id::from(id)) + .map(arg_values) + .map(|iter| GroupedValues { iter }) } /// Gets the lossy values of a specific argument. If the option wasn't present at runtime @@ -991,7 +1004,7 @@ impl ArgMatches { #[allow(missing_debug_implementations)] pub struct Values<'a> { #[allow(clippy::type_complexity)] - iter: Map>>, for<'r> fn(&'r OsString) -> &'r str>, + iter: Map>>, for<'r> fn(&'r OsString) -> &'r str>, } impl<'a> Iterator for Values<'a> { @@ -1017,12 +1030,44 @@ impl<'a> ExactSizeIterator for Values<'a> {} impl<'a> Default for Values<'a> { fn default() -> Self { static EMPTY: [Vec; 0] = []; - // This is never called because the iterator is empty: - fn to_str_slice(_: &OsString) -> &str { - unreachable!() - } Values { - iter: EMPTY[..].iter().flatten().map(to_str_slice), + iter: EMPTY[..].iter().flatten().map(|_| unreachable!()), + } + } +} + +#[derive(Clone)] +#[allow(missing_debug_implementations)] +pub struct GroupedValues<'a> { + #[allow(clippy::type_complexity)] + iter: Map>, fn(&Vec) -> Vec<&str>>, +} + +impl<'a> Iterator for GroupedValues<'a> { + type Item = Vec<&'a str>; + + fn next(&mut self) -> Option { + self.iter.next() + } + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a> DoubleEndedIterator for GroupedValues<'a> { + fn next_back(&mut self) -> Option { + self.iter.next_back() + } +} + +impl<'a> ExactSizeIterator for GroupedValues<'a> {} + +/// Creates an empty iterator. Used for `unwrap_or_default()`. +impl<'a> Default for GroupedValues<'a> { + fn default() -> Self { + static EMPTY: [Vec; 0] = []; + GroupedValues { + iter: EMPTY[..].iter().map(|_| unreachable!()), } } } @@ -1052,7 +1097,7 @@ impl<'a> Default for Values<'a> { #[allow(missing_debug_implementations)] pub struct OsValues<'a> { #[allow(clippy::type_complexity)] - iter: Map>>, fn(&OsString) -> &OsStr>, + iter: Map>>, fn(&OsString) -> &OsStr>, } impl<'a> Iterator for OsValues<'a> { @@ -1078,12 +1123,8 @@ impl<'a> ExactSizeIterator for OsValues<'a> {} impl Default for OsValues<'_> { fn default() -> Self { static EMPTY: [Vec; 0] = []; - // This is never called because the iterator is empty: - fn to_str_slice(_: &OsString) -> &OsStr { - unreachable!() - } OsValues { - iter: EMPTY[..].iter().flatten().map(to_str_slice), + iter: EMPTY[..].iter().flatten().map(|_| unreachable!()), } } } diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 0562e41b..34c50c32 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -1251,7 +1251,7 @@ impl<'help, 'app> Parser<'help, 'app> { } vals } else { - arg_split.into_iter().collect() + arg_split.collect() }; let vals = vals.into_iter().map(|x| x.to_os_string()).collect(); self.add_multiple_vals_to_arg(arg, vals, matcher, ty, append); diff --git a/tests/grouped_values.rs b/tests/grouped_values.rs new file mode 100644 index 00000000..04c2da0a --- /dev/null +++ b/tests/grouped_values.rs @@ -0,0 +1,147 @@ +mod utils; + +use clap::{App, Arg}; + +#[test] +fn value_sets_works() { + let m = App::new("cli") + .arg(Arg::new("option").long("option").multiple(true)) + .get_matches_from(&[ + "cli", + "--option", + "fr_FR:mon option 1", + "en_US:my option 1", + "--option", + "fr_FR:mon option 2", + "en_US:my option 2", + ]); + let grouped_vals: Vec<_> = m.grouped_values_of("option").unwrap().collect(); + assert_eq!( + grouped_vals, + vec![ + vec!["fr_FR:mon option 1", "en_US:my option 1",], + vec!["fr_FR:mon option 2", "en_US:my option 2",], + ] + ); +} + +#[test] +fn issue_1026() { + let m = App::new("cli") + .arg(Arg::new("server").short('s').takes_value(true)) + .arg(Arg::new("user").short('u').takes_value(true)) + .arg(Arg::new("target").long("target").multiple(true)) + .get_matches_from(&[ + "backup", "-s", "server", "-u", "user", "--target", "target1", "file1", "file2", + "file3", "--target", "target2", "file4", "file5", "file6", "file7", "--target", + "target3", "file8", + ]); + let grouped_vals: Vec<_> = m.grouped_values_of("target").unwrap().collect(); + assert_eq!( + grouped_vals, + vec![ + vec!["target1", "file1", "file2", "file3"], + vec!["target2", "file4", "file5", "file6", "file7",], + vec!["target3", "file8"] + ] + ); +} + +#[test] +fn value_sets_long_flag_delimiter() { + let m = App::new("myapp") + .arg( + Arg::new("option") + .long("option") + .takes_value(true) + .use_delimiter(true) + .multiple(true), + ) + .get_matches_from(vec![ + "myapp", + "--option=hmm", + "--option=val1,val2,val3", + "--option", + "alice,bob", + ]); + let grouped_vals: Vec<_> = m.grouped_values_of("option").unwrap().collect(); + assert_eq!( + grouped_vals, + vec![ + vec!["hmm"], + vec!["val1", "val2", "val3"], + vec!["alice", "bob"] + ] + ); +} + +#[test] +fn value_sets_short_flag_delimiter() { + let m = App::new("myapp") + .arg( + Arg::new("option") + .short('o') + .takes_value(true) + .use_delimiter(true) + .multiple(true), + ) + .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 value_sets_positional_arg() { + let m = App::new("multiple_values") + .arg(Arg::new("pos").about("multiple positionals").multiple(true)) + .get_matches_from(vec![ + "myprog", "val1", "val2", "val3", "val4", "val5", "val6", + ]); + let grouped_vals: Vec<_> = m.grouped_values_of("pos").unwrap().collect(); + assert_eq!( + grouped_vals, + vec![vec!["val1", "val2", "val3", "val4", "val5", "val6"]] + ); +} + +#[test] +fn value_sets_multiple_positional_arg() { + let m = App::new("multiple_values") + .arg(Arg::new("pos1").about("multiple positionals")) + .arg( + Arg::new("pos2") + .about("multiple positionals") + .multiple(true), + ) + .get_matches_from(vec![ + "myprog", "val1", "val2", "val3", "val4", "val5", "val6", + ]); + let grouped_vals: Vec<_> = m.grouped_values_of("pos2").unwrap().collect(); + assert_eq!( + grouped_vals, + vec![vec!["val2", "val3", "val4", "val5", "val6"]] + ); +} + +#[test] +fn value_sets_multiple_positional_arg_last_multiple() { + let m = App::new("multiple_values") + .arg(Arg::new("pos1").about("multiple positionals")) + .arg( + Arg::new("pos2") + .about("multiple positionals") + .multiple(true) + .last(true), + ) + .get_matches_from(vec![ + "myprog", "val1", "--", "val2", "val3", "val4", "val5", "val6", + ]); + let grouped_vals: Vec<_> = m.grouped_values_of("pos2").unwrap().collect(); + assert_eq!( + grouped_vals, + vec![vec!["val2", "val3", "val4", "val5", "val6"]] + ); +}