diff --git a/clap_derive/src/derives/args.rs b/clap_derive/src/derives/args.rs index ad2a6463f0c..1c3ce8b29fd 100644 --- a/clap_derive/src/derives/args.rs +++ b/clap_derive/src/derives/args.rs @@ -288,6 +288,14 @@ pub fn gen_augment( } } + Ty::VecVec | Ty::OptionVecVec => { + quote_spanned! { ty.span() => + .value_name(#value_name) + #value_parser + #action + } + } + Ty::Other => { let required = item.find_default_method().is_none() && !override_required; // `ArgAction::takes_values` is assuming `ArgAction::default_value` will be @@ -431,7 +439,9 @@ pub fn gen_constructor(fields: &[(&Field, Item)]) -> TokenStream { Ty::Unit | Ty::Vec | Ty::OptionOption | - Ty::OptionVec => { + Ty::OptionVec | + Ty::VecVec | + Ty::OptionVecVec => { abort!( ty.span(), "{} types are not supported for subcommand", @@ -470,7 +480,9 @@ pub fn gen_constructor(fields: &[(&Field, Item)]) -> TokenStream { Ty::Unit | Ty::Vec | Ty::OptionOption | - Ty::OptionVec => { + Ty::OptionVec | + Ty::VecVec | + Ty::OptionVecVec => { abort!( ty.span(), "{} types are not supported for flatten", @@ -609,6 +621,7 @@ fn gen_parsers( let id = item.id(); let get_one = quote_spanned!(span=> remove_one::<#convert_type>); let get_many = quote_spanned!(span=> remove_many::<#convert_type>); + let get_occurrences = quote_spanned!(span=> remove_occurrences::<#convert_type>); let deref = quote!(|s| s); let parse = quote_spanned!(span=> |s| ::std::result::Result::Ok::<_, clap::Error>(s)); @@ -665,6 +678,17 @@ fn gen_parsers( } } + Ty::VecVec => quote_spanned! { ty.span()=> + #arg_matches.#get_occurrences(#id) + .map(|g| g.map(::std::iter::Iterator::collect).collect::>>()) + .unwrap_or_else(Vec::new) + }, + + Ty::OptionVecVec => quote_spanned! { ty.span()=> + #arg_matches.#get_occurrences(#id) + .map(|g| g.map(::std::iter::Iterator::collect).collect::>>()) + }, + Ty::Other => { quote_spanned! { ty.span()=> #arg_matches.#get_one(#id) diff --git a/clap_derive/src/item.rs b/clap_derive/src/item.rs index a068e36011a..5e8272ac0c1 100644 --- a/clap_derive/src/item.rs +++ b/clap_derive/src/item.rs @@ -1121,7 +1121,7 @@ impl Action { fn default_action(field_type: &Type, span: Span) -> Method { let ty = Ty::from_syn_ty(field_type); let args = match *ty { - Ty::Vec | Ty::OptionVec => { + Ty::Vec | Ty::OptionVec | Ty::VecVec | Ty::OptionVecVec => { quote_spanned! { span=> clap::ArgAction::Append } diff --git a/clap_derive/src/utils/ty.rs b/clap_derive/src/utils/ty.rs index 1cf051441f1..32bc2c4fc98 100644 --- a/clap_derive/src/utils/ty.rs +++ b/clap_derive/src/utils/ty.rs @@ -11,9 +11,11 @@ use syn::{ pub enum Ty { Unit, Vec, + VecVec, Option, OptionOption, OptionVec, + OptionVecVec, Other, } @@ -24,13 +26,21 @@ impl Ty { if is_unit_ty(ty) { t(Unit) - } else if is_generic_ty(ty, "Vec") { - t(Vec) + } else if let Some(subty) = subty_if_name(ty, "Vec") { + if is_generic_ty(subty, "Vec") { + t(VecVec) + } else { + t(Vec) + } } else if let Some(subty) = subty_if_name(ty, "Option") { if is_generic_ty(subty, "Option") { t(OptionOption) - } else if is_generic_ty(subty, "Vec") { - t(OptionVec) + } else if let Some(subty) = subty_if_name(subty, "Vec") { + if is_generic_ty(subty, "Vec") { + t(OptionVecVec) + } else { + t(OptionVec) + } } else { t(Option) } @@ -46,6 +56,8 @@ impl Ty { Self::Option => "Option", Self::OptionOption => "Option>", Self::OptionVec => "Option>", + Self::VecVec => "Vec>", + Self::OptionVecVec => "Option>>", Self::Other => "...other...", } } @@ -55,9 +67,13 @@ pub fn inner_type(field_ty: &syn::Type) -> &syn::Type { let ty = Ty::from_syn_ty(field_ty); match *ty { Ty::Vec | Ty::Option => sub_type(field_ty).unwrap_or(field_ty), - Ty::OptionOption | Ty::OptionVec => { + Ty::OptionOption | Ty::OptionVec | Ty::VecVec => { sub_type(field_ty).and_then(sub_type).unwrap_or(field_ty) } + Ty::OptionVecVec => sub_type(field_ty) + .and_then(sub_type) + .and_then(sub_type) + .unwrap_or(field_ty), _ => field_ty, } } diff --git a/tests/derive/grouped_values.rs b/tests/derive/grouped_values.rs new file mode 100644 index 00000000000..8b2d8f2bc19 --- /dev/null +++ b/tests/derive/grouped_values.rs @@ -0,0 +1,62 @@ +#![cfg(feature = "unstable-grouped")] +use clap::Parser; + +#[test] +fn test_vec_of_vec() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[arg(short = 'p', num_args = 2)] + points: Vec>, + } + + assert_eq!( + Opt { + points: vec![vec![1, 2], vec![0, 0]] + }, + Opt::try_parse_from(&["test", "-p", "1", "2", "-p", "0", "0"]).unwrap() + ); +} + +#[test] +fn test_vec_vec_empty() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[arg(short = 'p', num_args = 2)] + points: Vec>, + } + + assert_eq!( + Opt { points: vec![] }, + Opt::try_parse_from(&["test"]).unwrap() + ); +} + +#[test] +fn test_option_vec_vec() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[arg(short = 'p', num_args = 2)] + points: Option>>, + } + + assert_eq!( + Opt { + points: Some(vec![vec![1, 2], vec![3, 4]]) + }, + Opt::try_parse_from(&["test", "-p", "1", "2", "-p", "3", "4"]).unwrap() + ); +} + +#[test] +fn test_option_vec_vec_empty() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[arg(short = 'p', num_args = 2)] + points: Option>>, + } + + assert_eq!( + Opt { points: None }, + Opt::try_parse_from(&["test"]).unwrap() + ); +} diff --git a/tests/derive/main.rs b/tests/derive/main.rs index 0c6ea7bd75b..5c809d6470c 100644 --- a/tests/derive/main.rs +++ b/tests/derive/main.rs @@ -15,6 +15,7 @@ mod explicit_name_no_renaming; mod flags; mod flatten; mod generic; +mod grouped_values; mod groups; mod help; mod issues;