Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(derive): Expose control over Actions #3794

Merged
merged 2 commits into from Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
168 changes: 157 additions & 11 deletions clap_derive/src/attrs.rs
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
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
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
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