Skip to content

Commit

Permalink
feat(derive): Expose control over Actions
Browse files Browse the repository at this point in the history
This is the derive support for clap-rs#3774 (see also clap-rs#3775, clap-rs#3777)

This combined with `value_parser` replaces `parser`.  The main
frustration with this is that `ArgAction::Count` (the replacement for
`parse(from_occurrences)` must be a `u64`.  We could come up with a
magic attribute that is meant to be the value parser's parsed type.  We
could then use `TryFrom` to convert the parsed type to the user's type
to allow more.  That is an exercise for the future.  Alternatively, we
have clap-rs#3792.

Prep for this included
- clap-rs#3782
- clap-rs#3783
- clap-rs#3786
- clap-rs#3789
- clap-rs#3793
  • Loading branch information
epage committed Jun 6, 2022
1 parent 4489f09 commit 647896d
Show file tree
Hide file tree
Showing 25 changed files with 293 additions and 111 deletions.
168 changes: 157 additions & 11 deletions clap_derive/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub struct Attrs {
doc_comment: Vec<Method>,
methods: Vec<Method>,
value_parser: Option<ValueParser>,
action: Option<Action>,
parser: Option<Sp<Parser>>,
verbatim_doc_comment: Option<Ident>,
next_display_order: Option<Method>,
Expand Down Expand Up @@ -70,6 +71,12 @@ impl Attrs {
"`value_parser` attribute is only allowed on fields"
);
}
if let Some(action) = res.action.as_ref() {
abort!(
action.span(),
"`action` attribute is only allowed on fields"
);
}
if let Some(parser) = res.parser.as_ref() {
abort!(parser.span(), "`parse` attribute is only allowed on fields");
}
Expand Down Expand Up @@ -110,6 +117,12 @@ impl Attrs {
"`value_parser` attribute is not allowed for flattened entry"
);
}
if let Some(action) = res.action.as_ref() {
abort!(
action.span(),
"`action` attribute is not allowed for flattened entry"
);
}
if let Some(parser) = res.parser.as_ref() {
abort!(
parser.span(),
Expand All @@ -136,6 +149,12 @@ impl Attrs {
"`value_parser` attribute is not allowed for subcommand"
);
}
if let Some(action) = res.action.as_ref() {
abort!(
action.span(),
"`action` attribute is not allowed for subcommand"
);
}
if let Some(parser) = res.parser.as_ref() {
abort!(
parser.span(),
Expand Down Expand Up @@ -210,6 +229,12 @@ impl Attrs {
"`value_parser` attribute is only allowed on fields"
);
}
if let Some(action) = res.action.as_ref() {
abort!(
action.span(),
"`action` attribute is only allowed on fields"
);
}
if let Some(parser) = res.parser.as_ref() {
abort!(parser.span(), "`parse` attribute is only allowed on fields");
}
Expand Down Expand Up @@ -250,6 +275,12 @@ impl Attrs {
"`value_parser` attribute is not allowed for flattened entry"
);
}
if let Some(action) = res.action.as_ref() {
abort!(
action.span(),
"`action` attribute is not allowed for flattened entry"
);
}
if let Some(parser) = res.parser.as_ref() {
abort!(
parser.span(),
Expand Down Expand Up @@ -280,6 +311,12 @@ impl Attrs {
"`value_parser` attribute is not allowed for subcommand"
);
}
if let Some(action) = res.action.as_ref() {
abort!(
action.span(),
"`action` attribute is not allowed for subcommand"
);
}
if let Some(parser) = res.parser.as_ref() {
abort!(
parser.span(),
Expand Down Expand Up @@ -333,6 +370,12 @@ impl Attrs {
"`value_parser` attribute conflicts with `parse` attribute"
);
}
if let Some(action) = res.action.as_ref() {
abort!(
action.span(),
"`action` attribute conflicts with `parse` attribute"
);
}
match *ty {
Ty::Option | Ty::Vec | Ty::OptionVec => (),
_ => ty = Sp::new(Ty::Other, ty.span()),
Expand Down Expand Up @@ -386,6 +429,7 @@ impl Attrs {
doc_comment: vec![],
methods: vec![],
value_parser: None,
action: None,
parser: None,
verbatim_doc_comment: None,
next_display_order: None,
Expand All @@ -401,6 +445,8 @@ impl Attrs {
self.name = Name::Assigned(quote!(#arg));
} else if name == "value_parser" {
self.value_parser = Some(ValueParser::Explicit(Method::new(name, quote!(#arg))));
} else if name == "action" {
self.action = Some(Action::Explicit(Method::new(name, quote!(#arg))));
} else {
self.methods.push(Method::new(name, quote!(#arg)));
}
Expand All @@ -426,6 +472,11 @@ impl Attrs {
self.value_parser = Some(ValueParser::Implicit(ident));
}

Action(ident) => {
use crate::attrs::Action;
self.action = Some(Action::Implicit(ident));
}

Env(ident) => {
self.push_method(ident, self.name.clone().translate(*self.env_casing));
}
Expand Down Expand Up @@ -718,11 +769,31 @@ impl Attrs {
let inner_type = inner_type(field_type);
p.resolve(inner_type)
})
.unwrap_or_else(|| self.parser(field_type).value_parser())
.unwrap_or_else(|| {
if let Some(action) = self.action.as_ref() {
let inner_type = inner_type(field_type);
default_value_parser(inner_type, action.span())
} else {
self.parser(field_type).value_parser()
}
})
}

pub fn action(&self, field_type: &Type) -> Method {
self.action
.clone()
.map(|p| p.resolve(field_type))
.unwrap_or_else(|| {
if let Some(value_parser) = self.value_parser.as_ref() {
default_action(field_type, value_parser.span())
} else {
self.parser(field_type).action()
}
})
}

pub fn ignore_parser(&self) -> bool {
self.value_parser.is_some()
self.value_parser.is_some() || self.action.is_some()
}

pub fn parser(&self, field_type: &Type) -> Sp<Parser> {
Expand Down Expand Up @@ -780,15 +851,7 @@ impl ValueParser {
fn resolve(self, inner_type: &Type) -> Method {
match self {
Self::Explicit(method) => method,
Self::Implicit(ident) => {
let func = Ident::new("value_parser", ident.span());
Method::new(
func,
quote_spanned! { ident.span()=>
clap::value_parser!(#inner_type)
},
)
}
Self::Implicit(ident) => default_value_parser(inner_type, ident.span()),
}
}

Expand All @@ -800,6 +863,68 @@ impl ValueParser {
}
}

fn default_value_parser(inner_type: &Type, span: Span) -> Method {
let func = Ident::new("value_parser", span);
Method::new(
func,
quote_spanned! { span=>
clap::value_parser!(#inner_type)
},
)
}

#[derive(Clone)]
pub enum Action {
Explicit(Method),
Implicit(Ident),
}

impl Action {
pub fn resolve(self, field_type: &Type) -> Method {
match self {
Self::Explicit(method) => method,
Self::Implicit(ident) => default_action(field_type, ident.span()),
}
}

pub fn span(&self) -> Span {
match self {
Self::Explicit(method) => method.name.span(),
Self::Implicit(ident) => ident.span(),
}
}
}

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 => {
quote_spanned! { span=>
clap::ArgAction::Append
}
}
Ty::Option | Ty::OptionOption => {
quote_spanned! { span=>
clap::ArgAction::Set
}
}
_ => {
if is_simple_ty(field_type, "bool") {
quote_spanned! { span=>
clap::ArgAction::SetTrue
}
} else {
quote_spanned! { span=>
clap::ArgAction::Set
}
}
}
};

let func = Ident::new("action", span);
Method::new(func, args)
}

#[allow(clippy::large_enum_variant)]
#[derive(Clone)]
pub enum Kind {
Expand Down Expand Up @@ -846,6 +971,10 @@ impl Method {

Some(Method::new(ident, quote!(#lit)))
}

pub(crate) fn args(&self) -> &TokenStream {
&self.args
}
}

impl ToTokens for Method {
Expand Down Expand Up @@ -962,6 +1091,23 @@ impl Parser {
),
}
}

fn action(&self) -> Method {
let func = Ident::new("action", self.kind.span());
match *self.kind {
ParserKind::FromStr
| ParserKind::TryFromStr
| ParserKind::FromOsStr
| ParserKind::TryFromOsStr => Method::new(
func,
quote_spanned! { self.kind.span()=> clap::ArgAction::StoreValue},
),
ParserKind::FromOccurrences | ParserKind::FromFlag => Method::new(
func,
quote_spanned! { self.kind.span()=> clap::ArgAction::IncOccurrence},
),
}
}
}

#[derive(Debug, PartialEq, Clone, Copy)]
Expand Down
12 changes: 11 additions & 1 deletion clap_derive/src/derives/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ pub fn gen_augment(
let parser = attrs.parser(&field.ty);

let value_parser = attrs.value_parser(&field.ty);
let action = attrs.action(&field.ty);
let func = &parser.func;

let mut occurrences = false;
Expand Down Expand Up @@ -287,6 +288,7 @@ pub fn gen_augment(
#possible_values
#validator
#value_parser
#action
}
}

Expand All @@ -299,6 +301,7 @@ pub fn gen_augment(
#possible_values
#validator
#value_parser
#action
},

Ty::OptionVec => quote_spanned! { ty.span()=>
Expand All @@ -308,6 +311,7 @@ pub fn gen_augment(
#possible_values
#validator
#value_parser
#action
},

Ty::Vec => {
Expand All @@ -318,6 +322,7 @@ pub fn gen_augment(
#possible_values
#validator
#value_parser
#action
}
}

Expand All @@ -331,13 +336,18 @@ pub fn gen_augment(

Ty::Other => {
let required = attrs.find_default_method().is_none() && !override_required;
// `ArgAction::takes_values` is assuming `ArgAction::default_value` will be
// set though that won't always be true but this should be good enough,
// otherwise we'll report an "arg required" error when unwrapping.
let action_value = action.args();
quote_spanned! { ty.span()=>
.takes_value(true)
.value_name(#value_name)
.required(#required)
.required(#required && #action_value.takes_values())
#possible_values
#validator
#value_parser
#action
}
}
};
Expand Down
2 changes: 2 additions & 0 deletions clap_derive/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub enum ClapAttr {
Short(Ident),
Long(Ident),
ValueParser(Ident),
Action(Ident),
Env(Ident),
Flatten(Ident),
ArgEnum(Ident),
Expand Down Expand Up @@ -184,6 +185,7 @@ impl Parse for ClapAttr {
"long" => Ok(Long(name)),
"short" => Ok(Short(name)),
"value_parser" => Ok(ValueParser(name)),
"action" => Ok(Action(name)),
"env" => Ok(Env(name)),
"flatten" => Ok(Flatten(name)),
"arg_enum" => Ok(ArgEnum(name)),
Expand Down
6 changes: 5 additions & 1 deletion examples/derive_ref/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ These correspond to a `clap::Arg`.
- `value_parser [= <expr>]`: `clap::Arg::value_parser`
- When not present: will auto-select an implementation based on the field type
- To register a custom type's `ValueParser`, implement `ValueParserFactory`
- When present, implies `#[clap(action)]`
- `action [= <expr>]`: `clap::Arg::action`
- When not present: will auto-select an action based on the field type
- When present, implies `#[clap(value_parser)]`
- `help = <expr>`: `clap::Arg::help`
- When not present: [Doc comment summary](#doc-comments)
- `long_help = <expr>`: `clap::Arg::long_help`
Expand Down Expand Up @@ -346,7 +350,7 @@ struct Robo {
/// I am artificial superintelligence. I won't rest
/// until I'll have destroyed humanity. Enjoy your
/// pathetic existence, you mere mortals.
#[clap(long)]
#[clap(long, action)]
kill_all_humans: bool,
}
```
Expand Down
Loading

0 comments on commit 647896d

Please sign in to comment.