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 Apr 30, 2022
1 parent 3ca1b77 commit e5344d3
Show file tree
Hide file tree
Showing 18 changed files with 410 additions and 22 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ jobs:
ui:
name: UI Tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
features: [default, next]
steps:
- name: Checkout repository
uses: actions/checkout@v2
Expand All @@ -123,7 +127,7 @@ jobs:
override: true
- uses: Swatinem/rust-cache@v1
- name: UI Tests
run: make test-ui
run: make test-ui-${{ matrix.features }}
docs:
name: Docs
runs-on: ubuntu-latest
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ unicode = ["textwrap/unicode-width", "unicase"] # Support for unicode character
unstable-replace = []
unstable-multicall = []
unstable-grouped = []
unstable-v4 = []
# note: this will always enable clap_derive, change this to `clap_derive?/unstable-v4` when MSRV is bigger than 1.60
unstable-v4 = ["clap_derive/unstable-v4"]

[lib]
bench = false
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ test-%:
clippy-%:
cargo clippy ${_FEATURES_${@:clippy-%=%}} ${ARGS} --all-targets -- -D warnings -A deprecated

test-ui:
cargo +${MSRV} test --test derive_ui --features derive
test-ui-%:
cargo +${MSRV} test --test derive_ui --features derive ${_FEATURES_${@:test-ui-%=%}}
2 changes: 2 additions & 0 deletions clap_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ proc-macro-error = "1"
[features]
default = []
debug = []
unstable-v4 = ["unstable-array"]
unstable-array = []

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
23 changes: 21 additions & 2 deletions clap_derive/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,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 @@ -277,6 +283,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 All @@ -298,7 +310,7 @@ impl Attrs {
let mut ty = Ty::from_syn_ty(&field.ty);
if res.has_custom_parser {
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 @@ -345,7 +357,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 @@ -310,6 +310,18 @@ pub fn gen_augment(
#allow_invalid_utf8
},

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 @@ -321,6 +333,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 @@ -612,6 +637,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.#values_of(#name)
Expand All @@ -621,6 +661,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.#value_of(#name))
},
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
32 changes: 20 additions & 12 deletions examples/derive_ref/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
# Derive Reference

1. [Overview](#overview)
2. [Attributes](#attributes)
1. [Terminology](#terminology)
2. [Command Attributes](#command-attributes)
3. [Arg Attributes](#arg-attributes)
4. [Arg Enum Attributes](#arg-enum-attributes)
5. [Possible Value Attributes](#possible-value-attributes)
3. [Arg Types](#arg-types)
4. [Doc Comments](#doc-comments)
5. [Tips](#tips)
6. [Mixing Builder and Derive APIS](#mixing-builder-and-derive-apis)
- [Derive Reference](#derive-reference)
- [Overview](#overview)
- [Attributes](#attributes)
- [Terminology](#terminology)
- [Command Attributes](#command-attributes)
- [Arg Attributes](#arg-attributes)
- [Arg Enum Attributes](#arg-enum-attributes)
- [Possible Value Attributes](#possible-value-attributes)
- [Arg Types](#arg-types)
- [Doc Comments](#doc-comments)
- [Pre-processing](#pre-processing)
- [Tips](#tips)
- [Mixing Builder and Derive APIs](#mixing-builder-and-derive-apis)
- [Using derived arguments in a builder application](#using-derived-arguments-in-a-builder-application)
- [Using derived subcommands in a builder application](#using-derived-subcommands-in-a-builder-application)
- [Adding hand-implemented subcommands to a derived application](#adding-hand-implemented-subcommands-to-a-derived-application)
- [Flattening hand-implemented args into a derived application](#flattening-hand-implemented-args-into-a-derived-application)

## Overview

Expand Down Expand Up @@ -240,12 +246,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

0 comments on commit e5344d3

Please sign in to comment.