Skip to content

Commit

Permalink
feat(derive): Support arrays as number_of_values
Browse files Browse the repository at this point in the history
clean up

Support optional fixed array

Remove `number_of_occurrences` currently

Add tests for deriving fixed array

Add tests for deriving positional fixed array arguments

Add ui test

Fix clippy

Put fixed array support under feature flag

Fix makefile

Fix doc
  • Loading branch information
ldm0 committed May 28, 2022
1 parent 54a1530 commit 7a882e5
Show file tree
Hide file tree
Showing 10 changed files with 342 additions and 8 deletions.
3 changes: 2 additions & 1 deletion clap_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,5 @@ proc-macro-error = "1"
[features]
default = []
debug = []
unstable-v4 = []
unstable-v4 = ["unstable-array"]
unstable-array = []
23 changes: 21 additions & 2 deletions clap_derive/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ impl Attrs {
"Option<Vec<T>> type is not allowed for subcommand"
);
}
Ty::OptionArray => {
abort!(
field_ty,
"Option<[T; N]> type is not allowed for subcommand"
);
}
_ => (),
}

Expand Down Expand Up @@ -314,6 +320,12 @@ impl Attrs {
"Option<Vec<T>> type is not allowed for subcommand"
);
}
Ty::OptionArray => {
abort!(
field.ty,
"Option<[T; N]> type is not allowed for subcommand"
);
}
_ => (),
}

Expand Down Expand Up @@ -341,7 +353,7 @@ impl Attrs {
);
}
match *ty {
Ty::Option | Ty::Vec | Ty::OptionVec => (),
Ty::Option | Ty::Vec | Ty::Array | Ty::OptionVec | Ty::OptionArray => (),
_ => ty = Sp::new(Ty::Other, ty.span()),
}
}
Expand Down Expand Up @@ -388,7 +400,14 @@ impl Attrs {
)
}
}

Ty::OptionArray => {
if res.is_positional() {
abort!(
field.ty,
"Option<[T; N]> type is meaningless for positional argument"
)
}
}
_ => (),
}
res.kind = Sp::new(Kind::Arg(ty), orig_ty.span());
Expand Down
53 changes: 52 additions & 1 deletion clap_derive/src/derives/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
use crate::{
attrs::{Attrs, Kind, Name, ParserKind, DEFAULT_CASING, DEFAULT_ENV_CASING},
dummies,
utils::{inner_type, sub_type, Sp, Ty},
utils::{array_ty_len, inner_type, sub_type, Sp, Ty},
};

use proc_macro2::{Ident, Span, TokenStream};
Expand Down Expand Up @@ -307,6 +307,18 @@ pub fn gen_augment(
#value_parser
},

Ty::OptionArray => {
let len = array_ty_len(sub_type(&field.ty).unwrap()).unwrap();
quote_spanned! { ty.span()=>
.takes_value(true)
.value_name(#value_name)
.number_of_values(#len)
#possible_values
#validator
#allow_invalid_utf8
}
}

Ty::Vec => {
quote_spanned! { ty.span()=>
.takes_value(true)
Expand All @@ -318,6 +330,19 @@ pub fn gen_augment(
}
}

Ty::Array => {
let len = array_ty_len(&field.ty).unwrap();
quote_spanned! { ty.span()=>
.takes_value(true)
.value_name(#value_name)
.required(true)
.number_of_values(#len)
#possible_values
#validator
#allow_invalid_utf8
}
}

Ty::Other if occurrences => quote_spanned! { ty.span()=>
.multiple_occurrences(true)
},
Expand Down Expand Up @@ -630,6 +655,21 @@ fn gen_parsers(
}
},

Ty::OptionArray => quote_spanned! { ty.span()=>
if #arg_matches.is_present(#name) {
Some(
std::convert::TryInto::try_into(
#arg_matches.#values_of(#name)
.map(|v| v.map::<::std::result::Result<#convert_type, clap::Error>, _>(#parse).collect::<::std::result::Result<Vec<_>, clap::Error>>())
.transpose()?
.unwrap_or_else(Vec::new)
).unwrap()
)
} else {
None
}
},

Ty::Vec => {
quote_spanned! { ty.span()=>
#arg_matches.#get_many(#id)
Expand All @@ -639,6 +679,17 @@ fn gen_parsers(
}
}

Ty::Array => {
quote_spanned! { ty.span()=>
std::convert::TryInto::try_into(
#arg_matches.#values_of(#name)
.map(|v| v.map::<::std::result::Result<#convert_type, clap::Error>, _>(#parse).collect::<::std::result::Result<Vec<_>, clap::Error>>())
.transpose()?
.unwrap_or_else(Vec::new)
).unwrap()
}
}

Ty::Other if occurrences => quote_spanned! { ty.span()=>
#parse(
#arg_matches.#get_one(#id)
Expand Down
2 changes: 1 addition & 1 deletion clap_derive/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ mod ty;
pub use self::{
doc_comments::process_doc_comment,
spanned::Sp,
ty::{inner_type, is_simple_ty, sub_type, subty_if_name, Ty},
ty::{array_ty_len, inner_type, is_simple_ty, sub_type, subty_if_name, Ty},
};
33 changes: 31 additions & 2 deletions clap_derive/src/utils/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
use super::spanned::Sp;

use syn::{
spanned::Spanned, GenericArgument, Path, PathArguments, PathArguments::AngleBracketed,
PathSegment, Type, TypePath,
spanned::Spanned, Expr, GenericArgument, Path, PathArguments, PathArguments::AngleBracketed,
PathSegment, Type, TypeArray, TypePath,
};

#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Ty {
Bool,
Vec,
Array,
Option,
OptionOption,
OptionVec,
OptionArray,
Other,
}

Expand All @@ -26,11 +28,15 @@ impl Ty {
t(Bool)
} else if is_generic_ty(ty, "Vec") {
t(Vec)
} else if cfg!(feature = "unstable-array") && is_array_ty(ty) {
t(Array)
} 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 cfg!(feature = "unstable-array") && is_array_ty(subty) {
t(OptionArray)
} else {
t(Option)
}
Expand All @@ -43,9 +49,13 @@ impl Ty {
pub fn inner_type(ty: Ty, field_ty: &syn::Type) -> &syn::Type {
match ty {
Ty::Vec | Ty::Option => sub_type(field_ty).unwrap_or(field_ty),
Ty::Array => array_ty_type(field_ty).unwrap_or(field_ty),
Ty::OptionOption | Ty::OptionVec => {
sub_type(field_ty).and_then(sub_type).unwrap_or(field_ty)
}
Ty::OptionArray => sub_type(field_ty)
.and_then(array_ty_type)
.unwrap_or(field_ty),
_ => field_ty,
}
}
Expand Down Expand Up @@ -113,6 +123,25 @@ fn is_generic_ty(ty: &syn::Type, name: &str) -> bool {
subty_if_name(ty, name).is_some()
}

fn is_array_ty(ty: &Type) -> bool {
matches!(ty, Type::Array(TypeArray { .. }))
}

fn array_ty_type(ty: &syn::Type) -> Option<&syn::Type> {
if let Type::Array(TypeArray { elem, .. }) = ty {
Some(&*elem)
} else {
None
}
}

pub fn array_ty_len(ty: &Type) -> Option<&Expr> {
match ty {
Type::Array(TypeArray { len, .. }) => Some(len),
_ => None,
}
}

fn only_one<I, T>(mut iter: I) -> Option<T>
where
I: Iterator<Item = T>,
Expand Down
4 changes: 3 additions & 1 deletion examples/derive_ref/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,14 @@ These correspond to a `clap::PossibleValue`.

| Type | Effect | Implies |
|---------------------|--------------------------------------|------------------------------------------------------------------|
| `bool` | flag | `#[clap(parse(from_flag))]` |
| `bool` | flag | `#[clap(parse(from_flag))]` |
| `Option<T>` | optional argument | `.takes_value(true).required(false)` |
| `Option<Option<T>>` | optional value for optional argument | `.takes_value(true).required(false).min_values(0).max_values(1)` |
| `T` | required argument | `.takes_value(true).required(!has_default)` |
| `Vec<T>` | `0..` occurrences of argument | `.takes_value(true).required(false).multiple_occurrences(true)` |
| `[T; N]` | `N` occurrences of argument | `.takes_value(true).required(true).number_of_values(N)` |
| `Option<Vec<T>>` | `0..` occurrences of argument | `.takes_value(true).required(false).multiple_occurrences(true)` |
| `Option<[T; N]>` | `0` or `N` occurrences of argument | `.takes_value(true).required(false).number_of_values(N)` |

Notes:
- For custom type behavior, you can override the implied attributes/settings and/or set additional ones
Expand Down
Loading

0 comments on commit 7a882e5

Please sign in to comment.