mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 14:22:34 +00:00
Merge pull request #4600 from tmccombs/groups-derive
Add support for deriving grouped options for Vec<Vec<T>>
This commit is contained in:
commit
084991f70a
5 changed files with 139 additions and 8 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
82
tests/derive/occurrences.rs
Normal file
82
tests/derive/occurrences.rs
Normal 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()
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue