Skip to content

Commit

Permalink
Merge pull request #4600 from tmccombs/groups-derive
Browse files Browse the repository at this point in the history
Add support for deriving grouped options for Vec<Vec<T>>
  • Loading branch information
epage committed Jan 10, 2023
2 parents d0adade + 73df3d1 commit 084991f
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 8 deletions.
28 changes: 26 additions & 2 deletions clap_derive/src/derives/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,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();
// `ArgAction::takes_values` is assuming `ArgAction::default_value` will be
Expand Down Expand Up @@ -452,7 +460,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 @@ -491,7 +501,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 @@ -630,6 +642,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));

Expand Down Expand Up @@ -686,6 +699,17 @@ fn gen_parsers(
}
}

Ty::VecVec => quote_spanned! { ty.span()=>
#arg_matches.#get_occurrences(#id)
.map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>())
.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::<Vec<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
Original file line number Diff line number Diff line change
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
34 changes: 29 additions & 5 deletions clap_derive/src/utils/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ use syn::{
pub enum Ty {
Unit,
Vec,
VecVec,
Option,
OptionOption,
OptionVec,
OptionVecVec,
Other,
}

Expand All @@ -24,13 +26,13 @@ impl Ty {

if is_unit_ty(ty) {
t(Unit)
} else if is_generic_ty(ty, "Vec") {
t(Vec)
} else if let Some(vt) = get_vec_ty(ty, Vec, VecVec) {
t(vt)
} 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(vt) = get_vec_ty(subty, OptionVec, OptionVecVec) {
t(vt)
} else {
t(Option)
}
Expand All @@ -46,6 +48,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 +59,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 Expand Up @@ -139,3 +147,19 @@ where
{
iter.next().filter(|_| iter.next().is_none())
}

#[cfg(feature = "unstable-v5")]
fn get_vec_ty(ty: &Type, vec_ty: Ty, vecvec_ty: Ty) -> Option<Ty> {
subty_if_name(ty, "Vec").map(|subty| {
if is_generic_ty(subty, "Vec") {
vecvec_ty
} else {
vec_ty
}
})
}

#[cfg(not(feature = "unstable-v5"))]
fn get_vec_ty(ty: &Type, vec_ty: Ty, _vecvec_ty: Ty) -> Option<Ty> {
is_generic_ty(ty, "Vec").then(|| vec_ty)
}
1 change: 1 addition & 0 deletions tests/derive/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ mod macros;
mod naming;
mod nested_subcommands;
mod non_literal_attributes;
mod occurrences;
mod options;
mod privacy;
mod raw_bool_literal;
Expand Down
82 changes: 82 additions & 0 deletions tests/derive/occurrences.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#![cfg(all(feature = "unstable-grouped", feature = "unstable-v5"))]
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_of_vec_opt_out() {
fn parser(s: &str) -> Result<Vec<String>, std::convert::Infallible> {
Ok(s.split(',').map(str::to_owned).collect())
}

#[derive(Parser, PartialEq, Debug)]
struct Opt {
#[arg(value_parser = parser, short = 'p')]
arg: Vec<::std::vec::Vec<String>>,
}

assert_eq!(
Opt {
arg: vec![vec!["1".into(), "2".into()], vec!["a".into(), "b".into()]],
},
Opt::try_parse_from(["test", "-p", "1,2", "-p", "a,b"]).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()
);
}

0 comments on commit 084991f

Please sign in to comment.