Skip to content

Commit 276e61d

Browse files
committed
Add support for deriving grouped options for Vec<Vec<T>>
Relates-To: clap-rs#2924
1 parent 6abc2cf commit 276e61d

File tree

5 files changed

+111
-8
lines changed

5 files changed

+111
-8
lines changed

clap_derive/src/derives/args.rs

+26-2
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,14 @@ pub fn gen_augment(
288288
}
289289
}
290290

291+
Ty::VecVec | Ty::OptionVecVec => {
292+
quote_spanned! { ty.span() =>
293+
.value_name(#value_name)
294+
#value_parser
295+
#action
296+
}
297+
}
298+
291299
Ty::Other => {
292300
let required = item.find_default_method().is_none() && !override_required;
293301
// `ArgAction::takes_values` is assuming `ArgAction::default_value` will be
@@ -431,7 +439,9 @@ pub fn gen_constructor(fields: &[(&Field, Item)]) -> TokenStream {
431439
Ty::Unit |
432440
Ty::Vec |
433441
Ty::OptionOption |
434-
Ty::OptionVec => {
442+
Ty::OptionVec |
443+
Ty::VecVec |
444+
Ty::OptionVecVec => {
435445
abort!(
436446
ty.span(),
437447
"{} types are not supported for subcommand",
@@ -470,7 +480,9 @@ pub fn gen_constructor(fields: &[(&Field, Item)]) -> TokenStream {
470480
Ty::Unit |
471481
Ty::Vec |
472482
Ty::OptionOption |
473-
Ty::OptionVec => {
483+
Ty::OptionVec |
484+
Ty::VecVec |
485+
Ty::OptionVecVec => {
474486
abort!(
475487
ty.span(),
476488
"{} types are not supported for flatten",
@@ -609,6 +621,7 @@ fn gen_parsers(
609621
let id = item.id();
610622
let get_one = quote_spanned!(span=> remove_one::<#convert_type>);
611623
let get_many = quote_spanned!(span=> remove_many::<#convert_type>);
624+
let get_occurrences = quote_spanned!(span=> remove_occurrences::<#convert_type>);
612625
let deref = quote!(|s| s);
613626
let parse = quote_spanned!(span=> |s| ::std::result::Result::Ok::<_, clap::Error>(s));
614627

@@ -665,6 +678,17 @@ fn gen_parsers(
665678
}
666679
}
667680

681+
Ty::VecVec => quote_spanned! { ty.span()=>
682+
#arg_matches.#get_occurrences(#id)
683+
.map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>())
684+
.unwrap_or_else(Vec::new)
685+
},
686+
687+
Ty::OptionVecVec => quote_spanned! { ty.span()=>
688+
#arg_matches.#get_occurrences(#id)
689+
.map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>())
690+
},
691+
668692
Ty::Other => {
669693
quote_spanned! { ty.span()=>
670694
#arg_matches.#get_one(#id)

clap_derive/src/item.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,7 @@ impl Action {
11211121
fn default_action(field_type: &Type, span: Span) -> Method {
11221122
let ty = Ty::from_syn_ty(field_type);
11231123
let args = match *ty {
1124-
Ty::Vec | Ty::OptionVec => {
1124+
Ty::Vec | Ty::OptionVec | Ty::VecVec | Ty::OptionVecVec => {
11251125
quote_spanned! { span=>
11261126
clap::ArgAction::Append
11271127
}

clap_derive/src/utils/ty.rs

+21-5
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ use syn::{
1111
pub enum Ty {
1212
Unit,
1313
Vec,
14+
VecVec,
1415
Option,
1516
OptionOption,
1617
OptionVec,
18+
OptionVecVec,
1719
Other,
1820
}
1921

@@ -24,13 +26,21 @@ impl Ty {
2426

2527
if is_unit_ty(ty) {
2628
t(Unit)
27-
} else if is_generic_ty(ty, "Vec") {
28-
t(Vec)
29+
} else if let Some(subty) = subty_if_name(ty, "Vec") {
30+
if is_generic_ty(subty, "Vec") {
31+
t(VecVec)
32+
} else {
33+
t(Vec)
34+
}
2935
} else if let Some(subty) = subty_if_name(ty, "Option") {
3036
if is_generic_ty(subty, "Option") {
3137
t(OptionOption)
32-
} else if is_generic_ty(subty, "Vec") {
33-
t(OptionVec)
38+
} else if let Some(subty) = subty_if_name(subty, "Vec") {
39+
if is_generic_ty(subty, "Vec") {
40+
t(OptionVecVec)
41+
} else {
42+
t(OptionVec)
43+
}
3444
} else {
3545
t(Option)
3646
}
@@ -46,6 +56,8 @@ impl Ty {
4656
Self::Option => "Option<T>",
4757
Self::OptionOption => "Option<Option<T>>",
4858
Self::OptionVec => "Option<Vec<T>>",
59+
Self::VecVec => "Vec<Vec<T>>",
60+
Self::OptionVecVec => "Option<Vec<Vec<T>>>",
4961
Self::Other => "...other...",
5062
}
5163
}
@@ -55,9 +67,13 @@ pub fn inner_type(field_ty: &syn::Type) -> &syn::Type {
5567
let ty = Ty::from_syn_ty(field_ty);
5668
match *ty {
5769
Ty::Vec | Ty::Option => sub_type(field_ty).unwrap_or(field_ty),
58-
Ty::OptionOption | Ty::OptionVec => {
70+
Ty::OptionOption | Ty::OptionVec | Ty::VecVec => {
5971
sub_type(field_ty).and_then(sub_type).unwrap_or(field_ty)
6072
}
73+
Ty::OptionVecVec => sub_type(field_ty)
74+
.and_then(sub_type)
75+
.and_then(sub_type)
76+
.unwrap_or(field_ty),
6177
_ => field_ty,
6278
}
6379
}

tests/derive/grouped_values.rs

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#![cfg(feature = "unstable-grouped")]
2+
use clap::Parser;
3+
4+
#[test]
5+
fn test_vec_of_vec() {
6+
#[derive(Parser, Debug, PartialEq)]
7+
struct Opt {
8+
#[arg(short = 'p', num_args = 2)]
9+
points: Vec<Vec<i32>>,
10+
}
11+
12+
assert_eq!(
13+
Opt {
14+
points: vec![vec![1, 2], vec![0, 0]]
15+
},
16+
Opt::try_parse_from(&["test", "-p", "1", "2", "-p", "0", "0"]).unwrap()
17+
);
18+
}
19+
20+
#[test]
21+
fn test_vec_vec_empty() {
22+
#[derive(Parser, Debug, PartialEq)]
23+
struct Opt {
24+
#[arg(short = 'p', num_args = 2)]
25+
points: Vec<Vec<i32>>,
26+
}
27+
28+
assert_eq!(
29+
Opt { points: vec![] },
30+
Opt::try_parse_from(&["test"]).unwrap()
31+
);
32+
}
33+
34+
#[test]
35+
fn test_option_vec_vec() {
36+
#[derive(Parser, Debug, PartialEq)]
37+
struct Opt {
38+
#[arg(short = 'p', num_args = 2)]
39+
points: Option<Vec<Vec<i32>>>,
40+
}
41+
42+
assert_eq!(
43+
Opt {
44+
points: Some(vec![vec![1, 2], vec![3, 4]])
45+
},
46+
Opt::try_parse_from(&["test", "-p", "1", "2", "-p", "3", "4"]).unwrap()
47+
);
48+
}
49+
50+
#[test]
51+
fn test_option_vec_vec_empty() {
52+
#[derive(Parser, Debug, PartialEq)]
53+
struct Opt {
54+
#[arg(short = 'p', num_args = 2)]
55+
points: Option<Vec<Vec<i32>>>,
56+
}
57+
58+
assert_eq!(
59+
Opt { points: None },
60+
Opt::try_parse_from(&["test"]).unwrap()
61+
);
62+
}

tests/derive/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod explicit_name_no_renaming;
1515
mod flags;
1616
mod flatten;
1717
mod generic;
18+
mod grouped_values;
1819
mod groups;
1920
mod help;
2021
mod issues;

0 commit comments

Comments
 (0)