Skip to content

Commit

Permalink
feat(derive): Support arrays as number_of_values
Browse files Browse the repository at this point in the history
Add `ErrorKind::Internal` for erroring
  • Loading branch information
ldm0 committed May 28, 2022
1 parent 54a1530 commit 4f8545d
Show file tree
Hide file tree
Showing 12 changed files with 351 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
55 changes: 54 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
#value_parser
}
}

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
#value_parser
}
}

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

Ty::OptionArray => quote_spanned! { ty.span()=>
if #arg_matches.is_present(#id) {
Some(
std::convert::TryInto::try_into(
#arg_matches.#get_many(#id)
.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)
)
.map_err(|arr| clap::Error::raw(clap::ErrorKind::Internal, format!("Get value array with wrong length: {}", arr.len())))?
)
} else {
None
}
},

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

Ty::Array => {
quote_spanned! { ty.span()=>
std::convert::TryInto::try_into(
#arg_matches.#get_many(#id)
.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)
)
.map_err(|arr| clap::Error::raw(clap::ErrorKind::Internal, format!("Get value array with wrong length: {}", arr.len())))?
}
}

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
6 changes: 6 additions & 0 deletions src/error/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,11 @@ pub enum ErrorKind {
/// [`Display`]: std::fmt::Display
/// [Format error]: std::fmt::Error
Format,

/// Represents Clap's internal error.
///
/// Please file a bug report at https://github.com/clap-rs/clap/issues.
Internal,
}

impl ErrorKind {
Expand Down Expand Up @@ -429,6 +434,7 @@ impl ErrorKind {
Self::ArgumentNotFound => Some("An argument wasn't found"),
Self::Io => None,
Self::Format => None,
Self::Internal => Some(crate::INTERNAL_ERROR_MSG),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,7 @@ impl Error {
ErrorKind::DisplayHelp
| ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
| ErrorKind::DisplayVersion
| ErrorKind::Internal
| ErrorKind::Io
| ErrorKind::Format => false,
}
Expand Down

0 comments on commit 4f8545d

Please sign in to comment.