Merge pull request #4600 from tmccombs/groups-derive

Add support for deriving grouped options for Vec<Vec<T>>
This commit is contained in:
Ed Page 2023-01-10 08:43:36 -06:00 committed by GitHub
commit 084991f70a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 139 additions and 8 deletions

View file

@ -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
@ -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",
@ -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",
@ -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));
@ -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)

View file

@ -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
}

View file

@ -11,9 +11,11 @@ use syn::{
pub enum Ty {
Unit,
Vec,
VecVec,
Option,
OptionOption,
OptionVec,
OptionVecVec,
Other,
}
@ -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)
}
@ -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...",
}
}
@ -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,
}
}
@ -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)
}

View file

@ -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;

View file

@ -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()
);
}