Skip to content

Commit

Permalink
Add support for deriving grouped options for Vec<Vec<T>>
Browse files Browse the repository at this point in the history
Relates-To: clap-rs#2924
  • Loading branch information
tmccombs committed Dec 9, 2022
1 parent 4bc7fd6 commit c9dc781
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 8 deletions.
28 changes: 26 additions & 2 deletions clap_derive/src/derives/args.rs
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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_groups = quote_spanned!(span=> remove_groups::<#convert_type>);
let deref = quote!(|s| s);
let parse = quote_spanned!(span=> |s| ::std::result::Result::Ok::<_, clap::Error>(s));

Expand Down Expand Up @@ -665,6 +678,17 @@ fn gen_parsers(
}
}

Ty::VecVec => quote_spanned! { ty.span()=>
#arg_matches.#get_groups(#id)
.map(|g| g.collect::<Vec<_>>())
.unwrap_or_else(Vec::new)
},

Ty::OptionVecVec => quote_spanned! { ty.span()=>
#arg_matches.#get_groups(#id)
.map(|g| g.collect::<Vec<_>>())
},

Ty::Other => {
quote_spanned! { ty.span()=>
#arg_matches.#get_one(#id)
Expand Down
2 changes: 1 addition & 1 deletion clap_derive/src/item.rs
Expand Up @@ -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
}
Expand Down
26 changes: 21 additions & 5 deletions clap_derive/src/utils/ty.rs
Expand Up @@ -11,9 +11,11 @@ use syn::{
pub enum Ty {
Unit,
Vec,
VecVec,
Option,
OptionOption,
OptionVec,
OptionVecVec,
Other,
}

Expand All @@ -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)
}
Expand All @@ -46,6 +56,8 @@ impl Ty {
Self::Option => "Option<T>",
Self::OptionOption => "Option<Option<T>>",
Self::OptionVec => "Option<Vec<T>>",
Self::VecVec => "Vec<Vec<T>>",
Self::OptionVecVec => "Option<Vec<Vec<T>>>",
Self::Other => "...other...",
}
}
Expand All @@ -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,
}
}
Expand Down
1 change: 1 addition & 0 deletions src/parser/matches/arg_matches.rs
Expand Up @@ -1153,6 +1153,7 @@ impl ArgMatches {
}

/// Non-panicking version of [`ArgMatches::remove_groups`]
#[cfg(feature = "unstable-grouped")]
pub fn try_remove_groups<T: Any + Clone + Send + Sync + 'static>(
&mut self,
id: &str,
Expand Down
62 changes: 62 additions & 0 deletions 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<Vec<i32>>,
}

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<Vec<i32>>,
}

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<Vec<Vec<i32>>>,
}

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<Vec<Vec<i32>>>,
}

assert_eq!(
Opt { points: None },
Opt::try_parse_from(&["test"]).unwrap()
);
}
1 change: 1 addition & 0 deletions tests/derive/main.rs
Expand Up @@ -15,6 +15,7 @@ mod explicit_name_no_renaming;
mod flags;
mod flatten;
mod generic;
mod grouped_values;
mod groups;
mod help;
mod issues;
Expand Down

0 comments on commit c9dc781

Please sign in to comment.