feat(derive): Expose control over Actions

This is the derive support for #3774 (see also #3775, #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 #3792.

Prep for this included
- #3782
- #3783
- #3786
- #3789
- #3793
This commit is contained in:
Ed Page 2022-06-01 10:31:23 -05:00
parent 4489f09f10
commit 647896d929
25 changed files with 293 additions and 111 deletions

View file

@ -43,6 +43,7 @@ pub struct Attrs {
doc_comment: Vec<Method>, doc_comment: Vec<Method>,
methods: Vec<Method>, methods: Vec<Method>,
value_parser: Option<ValueParser>, value_parser: Option<ValueParser>,
action: Option<Action>,
parser: Option<Sp<Parser>>, parser: Option<Sp<Parser>>,
verbatim_doc_comment: Option<Ident>, verbatim_doc_comment: Option<Ident>,
next_display_order: Option<Method>, next_display_order: Option<Method>,
@ -70,6 +71,12 @@ impl Attrs {
"`value_parser` attribute is only allowed on fields" "`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() { if let Some(parser) = res.parser.as_ref() {
abort!(parser.span(), "`parse` attribute is only allowed on fields"); abort!(parser.span(), "`parse` attribute is only allowed on fields");
} }
@ -110,6 +117,12 @@ impl Attrs {
"`value_parser` attribute is not allowed for flattened entry" "`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() { if let Some(parser) = res.parser.as_ref() {
abort!( abort!(
parser.span(), parser.span(),
@ -136,6 +149,12 @@ impl Attrs {
"`value_parser` attribute is not allowed for subcommand" "`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() { if let Some(parser) = res.parser.as_ref() {
abort!( abort!(
parser.span(), parser.span(),
@ -210,6 +229,12 @@ impl Attrs {
"`value_parser` attribute is only allowed on fields" "`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() { if let Some(parser) = res.parser.as_ref() {
abort!(parser.span(), "`parse` attribute is only allowed on fields"); abort!(parser.span(), "`parse` attribute is only allowed on fields");
} }
@ -250,6 +275,12 @@ impl Attrs {
"`value_parser` attribute is not allowed for flattened entry" "`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() { if let Some(parser) = res.parser.as_ref() {
abort!( abort!(
parser.span(), parser.span(),
@ -280,6 +311,12 @@ impl Attrs {
"`value_parser` attribute is not allowed for subcommand" "`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() { if let Some(parser) = res.parser.as_ref() {
abort!( abort!(
parser.span(), parser.span(),
@ -333,6 +370,12 @@ impl Attrs {
"`value_parser` attribute conflicts with `parse` attribute" "`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 { match *ty {
Ty::Option | Ty::Vec | Ty::OptionVec => (), Ty::Option | Ty::Vec | Ty::OptionVec => (),
_ => ty = Sp::new(Ty::Other, ty.span()), _ => ty = Sp::new(Ty::Other, ty.span()),
@ -386,6 +429,7 @@ impl Attrs {
doc_comment: vec![], doc_comment: vec![],
methods: vec![], methods: vec![],
value_parser: None, value_parser: None,
action: None,
parser: None, parser: None,
verbatim_doc_comment: None, verbatim_doc_comment: None,
next_display_order: None, next_display_order: None,
@ -401,6 +445,8 @@ impl Attrs {
self.name = Name::Assigned(quote!(#arg)); self.name = Name::Assigned(quote!(#arg));
} else if name == "value_parser" { } else if name == "value_parser" {
self.value_parser = Some(ValueParser::Explicit(Method::new(name, quote!(#arg)))); 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 { } else {
self.methods.push(Method::new(name, quote!(#arg))); self.methods.push(Method::new(name, quote!(#arg)));
} }
@ -426,6 +472,11 @@ impl Attrs {
self.value_parser = Some(ValueParser::Implicit(ident)); self.value_parser = Some(ValueParser::Implicit(ident));
} }
Action(ident) => {
use crate::attrs::Action;
self.action = Some(Action::Implicit(ident));
}
Env(ident) => { Env(ident) => {
self.push_method(ident, self.name.clone().translate(*self.env_casing)); self.push_method(ident, self.name.clone().translate(*self.env_casing));
} }
@ -718,11 +769,31 @@ impl Attrs {
let inner_type = inner_type(field_type); let inner_type = inner_type(field_type);
p.resolve(inner_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 { 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> { pub fn parser(&self, field_type: &Type) -> Sp<Parser> {
@ -780,15 +851,7 @@ impl ValueParser {
fn resolve(self, inner_type: &Type) -> Method { fn resolve(self, inner_type: &Type) -> Method {
match self { match self {
Self::Explicit(method) => method, Self::Explicit(method) => method,
Self::Implicit(ident) => { Self::Implicit(ident) => default_value_parser(inner_type, ident.span()),
let func = Ident::new("value_parser", ident.span());
Method::new(
func,
quote_spanned! { ident.span()=>
clap::value_parser!(#inner_type)
},
)
}
} }
} }
@ -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)] #[allow(clippy::large_enum_variant)]
#[derive(Clone)] #[derive(Clone)]
pub enum Kind { pub enum Kind {
@ -846,6 +971,10 @@ impl Method {
Some(Method::new(ident, quote!(#lit))) Some(Method::new(ident, quote!(#lit)))
} }
pub(crate) fn args(&self) -> &TokenStream {
&self.args
}
} }
impl ToTokens for Method { impl ToTokens for Method {
@ -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)] #[derive(Debug, PartialEq, Clone, Copy)]

View file

@ -246,6 +246,7 @@ pub fn gen_augment(
let parser = attrs.parser(&field.ty); let parser = attrs.parser(&field.ty);
let value_parser = attrs.value_parser(&field.ty); let value_parser = attrs.value_parser(&field.ty);
let action = attrs.action(&field.ty);
let func = &parser.func; let func = &parser.func;
let mut occurrences = false; let mut occurrences = false;
@ -287,6 +288,7 @@ pub fn gen_augment(
#possible_values #possible_values
#validator #validator
#value_parser #value_parser
#action
} }
} }
@ -299,6 +301,7 @@ pub fn gen_augment(
#possible_values #possible_values
#validator #validator
#value_parser #value_parser
#action
}, },
Ty::OptionVec => quote_spanned! { ty.span()=> Ty::OptionVec => quote_spanned! { ty.span()=>
@ -308,6 +311,7 @@ pub fn gen_augment(
#possible_values #possible_values
#validator #validator
#value_parser #value_parser
#action
}, },
Ty::Vec => { Ty::Vec => {
@ -318,6 +322,7 @@ pub fn gen_augment(
#possible_values #possible_values
#validator #validator
#value_parser #value_parser
#action
} }
} }
@ -331,13 +336,18 @@ pub fn gen_augment(
Ty::Other => { Ty::Other => {
let required = attrs.find_default_method().is_none() && !override_required; 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()=> quote_spanned! { ty.span()=>
.takes_value(true) .takes_value(true)
.value_name(#value_name) .value_name(#value_name)
.required(#required) .required(#required && #action_value.takes_values())
#possible_values #possible_values
#validator #validator
#value_parser #value_parser
#action
} }
} }
}; };

View file

@ -27,6 +27,7 @@ pub enum ClapAttr {
Short(Ident), Short(Ident),
Long(Ident), Long(Ident),
ValueParser(Ident), ValueParser(Ident),
Action(Ident),
Env(Ident), Env(Ident),
Flatten(Ident), Flatten(Ident),
ArgEnum(Ident), ArgEnum(Ident),
@ -184,6 +185,7 @@ impl Parse for ClapAttr {
"long" => Ok(Long(name)), "long" => Ok(Long(name)),
"short" => Ok(Short(name)), "short" => Ok(Short(name)),
"value_parser" => Ok(ValueParser(name)), "value_parser" => Ok(ValueParser(name)),
"action" => Ok(Action(name)),
"env" => Ok(Env(name)), "env" => Ok(Env(name)),
"flatten" => Ok(Flatten(name)), "flatten" => Ok(Flatten(name)),
"arg_enum" => Ok(ArgEnum(name)), "arg_enum" => Ok(ArgEnum(name)),

View file

@ -179,6 +179,10 @@ These correspond to a `clap::Arg`.
- `value_parser [= <expr>]`: `clap::Arg::value_parser` - `value_parser [= <expr>]`: `clap::Arg::value_parser`
- When not present: will auto-select an implementation based on the field type - When not present: will auto-select an implementation based on the field type
- To register a custom type's `ValueParser`, implement `ValueParserFactory` - 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` - `help = <expr>`: `clap::Arg::help`
- When not present: [Doc comment summary](#doc-comments) - When not present: [Doc comment summary](#doc-comments)
- `long_help = <expr>`: `clap::Arg::long_help` - `long_help = <expr>`: `clap::Arg::long_help`
@ -346,7 +350,7 @@ struct Robo {
/// I am artificial superintelligence. I won't rest /// I am artificial superintelligence. I won't rest
/// until I'll have destroyed humanity. Enjoy your /// until I'll have destroyed humanity. Enjoy your
/// pathetic existence, you mere mortals. /// pathetic existence, you mere mortals.
#[clap(long)] #[clap(long, action)]
kill_all_humans: bool, kill_all_humans: bool,
} }
``` ```

View file

@ -2,20 +2,23 @@ use clap::{arg, Args as _, Command, FromArgMatches as _, Parser};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct DerivedArgs { struct DerivedArgs {
#[clap(short, long)] #[clap(short, long, action)]
derived: bool, derived: bool,
} }
fn main() { fn main() {
let cli = Command::new("CLI").arg(arg!(-b - -built)); let cli = Command::new("CLI").arg(arg!(-b - -built).action(clap::ArgAction::SetTrue));
// Augment built args with derived args // Augment built args with derived args
let cli = DerivedArgs::augment_args(cli); let cli = DerivedArgs::augment_args(cli);
let matches = cli.get_matches(); let matches = cli.get_matches();
println!("Value of built: {:?}", matches.is_present("built")); println!(
"Value of built: {:?}",
*matches.get_one::<bool>("built").unwrap()
);
println!( println!(
"Value of derived via ArgMatches: {:?}", "Value of derived via ArgMatches: {:?}",
matches.is_present("derived") *matches.get_one::<bool>("derived").unwrap()
); );
// Since DerivedArgs implements FromArgMatches, we can extract it from the unstructured ArgMatches. // Since DerivedArgs implements FromArgMatches, we can extract it from the unstructured ArgMatches.

View file

@ -3,7 +3,7 @@ use clap::{Command, FromArgMatches as _, Parser, Subcommand as _};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
enum Subcommands { enum Subcommands {
Derived { Derived {
#[clap(short, long)] #[clap(short, long, action)]
derived_flag: bool, derived_flag: bool,
}, },
} }

View file

@ -11,11 +11,11 @@ USAGE:
custom-bool[EXE] [OPTIONS] --foo <FOO> <BOOM> custom-bool[EXE] [OPTIONS] --foo <FOO> <BOOM>
ARGS: ARGS:
<BOOM> <BOOM> [possible values: true, false]
OPTIONS: OPTIONS:
--bar <BAR> [default: false] --bar <BAR> [default: false]
--foo <FOO> --foo <FOO> [possible values: true, false]
-h, --help Print help information -h, --help Print help information
-V, --version Print version information -V, --version Print version information
@ -31,14 +31,14 @@ USAGE:
For more information try --help For more information try --help
$ custom-bool --foo true false $ custom-bool --foo true false
[examples/derive_ref/custom-bool.rs:32] opt = Opt { [examples/derive_ref/custom-bool.rs:31] opt = Opt {
foo: true, foo: true,
bar: false, bar: false,
boom: false, boom: false,
} }
$ custom-bool --foo true --bar true false $ custom-bool --foo true --bar true false
[examples/derive_ref/custom-bool.rs:32] opt = Opt { [examples/derive_ref/custom-bool.rs:31] opt = Opt {
foo: true, foo: true,
bar: true, bar: true,
boom: false, boom: false,

View file

@ -1,21 +1,20 @@
#![allow(deprecated)] // Can't opt-out of implicit flags until #3405
use clap::Parser; use clap::Parser;
#[derive(Parser, Debug, PartialEq)] #[derive(Parser, Debug, PartialEq)]
#[clap(author, version, about, long_about = None)] #[clap(author, version, about, long_about = None)]
struct Opt { struct Opt {
// Default parser for `try_from_str` is FromStr::from_str. // Default parser for `Set` is FromStr::from_str.
// `impl FromStr for bool` parses `true` or `false` so this // `impl FromStr for bool` parses `true` or `false` so this
// works as expected. // works as expected.
#[clap(long, parse(try_from_str))] #[clap(long, action = clap::ArgAction::Set)]
foo: bool, foo: bool,
// Of course, this could be done with an explicit parser function. // Of course, this could be done with an explicit parser function.
#[clap(long, parse(try_from_str = true_or_false), default_value_t)] #[clap(long, action = clap::ArgAction::Set, value_parser = true_or_false, default_value_t)]
bar: bool, bar: bool,
// `bool` can be positional only with explicit `parse(...)` annotation // `bool` can be positional only with explicit `action` annotation
#[clap(parse(try_from_str))] #[clap(action = clap::ArgAction::Set)]
boom: bool, boom: bool,
} }

View file

@ -8,7 +8,7 @@ struct AddArgs {
} }
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct RemoveArgs { struct RemoveArgs {
#[clap(short, long)] #[clap(short, long, action)]
force: bool, force: bool,
#[clap(value_parser)] #[clap(value_parser)]
name: Vec<String>, name: Vec<String>,
@ -69,7 +69,7 @@ impl Subcommand for CliSub {
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct Cli { struct Cli {
#[clap(short, long)] #[clap(short, long, action)]
top_level: bool, top_level: bool,
#[clap(subcommand)] #[clap(subcommand)]
subcommand: CliSub, subcommand: CliSub,

View file

@ -5,7 +5,7 @@ use clap::Parser;
#[derive(Parser)] #[derive(Parser)]
#[clap(author, version, about, long_about = None)] #[clap(author, version, about, long_about = None)]
struct Cli { struct Cli {
#[clap(short = 'f')] #[clap(short = 'f', action)]
eff: bool, eff: bool,
#[clap(short = 'p', value_name = "PEAR", value_parser)] #[clap(short = 'p', value_name = "PEAR", value_parser)]

View file

@ -14,8 +14,8 @@ struct Cli {
config: Option<PathBuf>, config: Option<PathBuf>,
/// Turn debugging information on /// Turn debugging information on
#[clap(short, long, parse(from_occurrences))] #[clap(short, long, action = clap::ArgAction::Count)]
debug: usize, debug: u64,
#[clap(subcommand)] #[clap(subcommand)]
command: Option<Commands>, command: Option<Commands>,
@ -26,7 +26,7 @@ enum Commands {
/// does testing things /// does testing things
Test { Test {
/// lists test values /// lists test values
#[clap(short, long)] #[clap(short, long, action)]
list: bool, list: bool,
}, },
} }

View file

@ -3,7 +3,7 @@ use clap::Parser;
#[derive(Parser)] #[derive(Parser)]
#[clap(author, version, about, long_about = None)] #[clap(author, version, about, long_about = None)]
struct Cli { struct Cli {
#[clap(short, long)] #[clap(short, long, action)]
verbose: bool, verbose: bool,
} }

View file

@ -3,8 +3,8 @@ use clap::Parser;
#[derive(Parser)] #[derive(Parser)]
#[clap(author, version, about, long_about = None)] #[clap(author, version, about, long_about = None)]
struct Cli { struct Cli {
#[clap(short, long, parse(from_occurrences))] #[clap(short, long, action = clap::ArgAction::Count)]
verbose: usize, verbose: u64,
} }
fn main() { fn main() {

View file

@ -13,15 +13,15 @@ struct Cli {
set_ver: Option<String>, set_ver: Option<String>,
/// auto inc major /// auto inc major
#[clap(long)] #[clap(long, action)]
major: bool, major: bool,
/// auto inc minor /// auto inc minor
#[clap(long)] #[clap(long, action)]
minor: bool, minor: bool,
/// auto inc patch /// auto inc patch
#[clap(long)] #[clap(long, action)]
patch: bool, patch: bool,
/// some regular input /// some regular input

View file

@ -8,15 +8,15 @@ struct Cli {
set_ver: Option<String>, set_ver: Option<String>,
/// auto inc major /// auto inc major
#[clap(long)] #[clap(long, action)]
major: bool, major: bool,
/// auto inc minor /// auto inc minor
#[clap(long)] #[clap(long, action)]
minor: bool, minor: bool,
/// auto inc patch /// auto inc patch
#[clap(long)] #[clap(long, action)]
patch: bool, patch: bool,
/// some regular input /// some regular input

View file

@ -236,17 +236,11 @@ $ 03_01_flag_bool_derive --verbose
verbose: true verbose: true
$ 03_01_flag_bool_derive --verbose --verbose $ 03_01_flag_bool_derive --verbose --verbose
? failed verbose: true
error: The argument '--verbose' was provided more than once, but cannot be used multiple times
USAGE:
03_01_flag_bool_derive[EXE] [OPTIONS]
For more information try --help
``` ```
Or counted with `#[clap(parse(from_occurrences))]`: Or counted with `#[clap(action = clap::ArgAction::Count)]`:
[Example:](03_01_flag_count.rs) [Example:](03_01_flag_count.rs)
```console ```console

View file

@ -23,7 +23,7 @@ fn doc_comments() {
struct LoremIpsum { struct LoremIpsum {
/// Fooify a bar /// Fooify a bar
/// and a baz /// and a baz
#[clap(short, long)] #[clap(short, long, action)]
foo: bool, foo: bool,
} }
@ -39,7 +39,12 @@ fn help_is_better_than_comments() {
#[clap(name = "lorem-ipsum", about = "Dolor sit amet")] #[clap(name = "lorem-ipsum", about = "Dolor sit amet")]
struct LoremIpsum { struct LoremIpsum {
/// Fooify a bar /// Fooify a bar
#[clap(short, long, help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")] #[clap(
short,
long,
help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES",
action
)]
foo: bool, foo: bool,
} }
@ -71,11 +76,11 @@ fn field_long_doc_comment_both_help_long_help() {
/// Dot is removed from multiline comments. /// Dot is removed from multiline comments.
/// ///
/// Long help /// Long help
#[clap(long)] #[clap(long, action)]
foo: bool, foo: bool,
/// Dot is removed from one short comment. /// Dot is removed from one short comment.
#[clap(long)] #[clap(long, action)]
bar: bool, bar: bool,
} }
@ -141,7 +146,7 @@ fn verbatim_doc_comment() {
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[clap(verbatim_doc_comment)] #[clap(verbatim_doc_comment)]
struct SeeFigure1 { struct SeeFigure1 {
#[clap(long)] #[clap(long, action)]
foo: bool, foo: bool,
} }
@ -171,10 +176,10 @@ fn verbatim_doc_comment_field() {
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct Command { struct Command {
/// This help ends in a period. /// This help ends in a period.
#[clap(long, verbatim_doc_comment)] #[clap(long, verbatim_doc_comment, action)]
foo: bool, foo: bool,
/// This help does not end in a period. /// This help does not end in a period.
#[clap(long)] #[clap(long, action)]
bar: bool, bar: bool,
} }

View file

@ -20,7 +20,7 @@ use clap::Parser;
fn bool_type_is_flag() { fn bool_type_is_flag() {
#[derive(Parser, PartialEq, Debug)] #[derive(Parser, PartialEq, Debug)]
struct Opt { struct Opt {
#[clap(short, long)] #[clap(short, long, action)]
alice: bool, alice: bool,
} }
@ -32,24 +32,26 @@ fn bool_type_is_flag() {
Opt { alice: true }, Opt { alice: true },
Opt::try_parse_from(&["test", "-a"]).unwrap() Opt::try_parse_from(&["test", "-a"]).unwrap()
); );
assert_eq!(
Opt { alice: true },
Opt::try_parse_from(&["test", "-a", "-a"]).unwrap()
);
assert_eq!( assert_eq!(
Opt { alice: true }, Opt { alice: true },
Opt::try_parse_from(&["test", "--alice"]).unwrap() Opt::try_parse_from(&["test", "--alice"]).unwrap()
); );
assert!(Opt::try_parse_from(&["test", "-i"]).is_err()); assert!(Opt::try_parse_from(&["test", "-i"]).is_err());
assert!(Opt::try_parse_from(&["test", "-a", "foo"]).is_err()); assert!(Opt::try_parse_from(&["test", "-a", "foo"]).is_err());
assert!(Opt::try_parse_from(&["test", "-a", "-a"]).is_err());
assert!(Opt::try_parse_from(&["test", "-a", "--alice"]).is_err());
} }
#[test] #[test]
fn from_occurrences() { fn count() {
#[derive(Parser, PartialEq, Debug)] #[derive(Parser, PartialEq, Debug)]
struct Opt { struct Opt {
#[clap(short, long, parse(from_occurrences))] #[clap(short, long, action = clap::ArgAction::Count)]
alice: u64, alice: u64,
#[clap(short, long, parse(from_occurrences))] #[clap(short, long, action = clap::ArgAction::Count)]
bob: u8, bob: u64,
} }
assert_eq!( assert_eq!(
@ -111,9 +113,9 @@ fn non_bool_type_flag() {
fn mixed_type_flags() { fn mixed_type_flags() {
#[derive(Parser, PartialEq, Debug)] #[derive(Parser, PartialEq, Debug)]
struct Opt { struct Opt {
#[clap(short, long)] #[clap(short, long, action)]
alice: bool, alice: bool,
#[clap(short, long, parse(from_occurrences))] #[clap(short, long, action = clap::ArgAction::Count)]
bob: u64, bob: u64,
} }
@ -191,10 +193,10 @@ fn ignore_qualified_bool_type() {
} }
#[test] #[test]
fn override_implicit_from_flag() { fn override_implicit_action() {
#[derive(Parser, PartialEq, Debug)] #[derive(Parser, PartialEq, Debug)]
struct Opt { struct Opt {
#[clap(long, parse(try_from_str))] #[clap(long, action = clap::ArgAction::Set)]
arg: bool, arg: bool,
} }
@ -213,7 +215,7 @@ fn override_implicit_from_flag() {
fn override_implicit_from_flag_positional() { fn override_implicit_from_flag_positional() {
#[derive(Parser, PartialEq, Debug)] #[derive(Parser, PartialEq, Debug)]
struct Opt { struct Opt {
#[clap(parse(try_from_str))] #[clap(action = clap::ArgAction::Set)]
arg: bool, arg: bool,
} }

View file

@ -70,7 +70,7 @@ fn flatten_in_subcommand() {
#[derive(Args, PartialEq, Debug)] #[derive(Args, PartialEq, Debug)]
struct Add { struct Add {
#[clap(short)] #[clap(short, action)]
interactive: bool, interactive: bool,
#[clap(flatten)] #[clap(flatten)]
common: Common, common: Common,
@ -79,7 +79,7 @@ fn flatten_in_subcommand() {
#[derive(Parser, PartialEq, Debug)] #[derive(Parser, PartialEq, Debug)]
enum Opt { enum Opt {
Fetch { Fetch {
#[clap(short)] #[clap(short, action)]
all: bool, all: bool,
#[clap(flatten)] #[clap(flatten)]
common: Common, common: Common,
@ -227,7 +227,7 @@ fn docstrings_ordering_with_multiple_clap() {
/// This is the docstring for Flattened /// This is the docstring for Flattened
#[derive(Args)] #[derive(Args)]
struct Flattened { struct Flattened {
#[clap(long)] #[clap(long, action)]
foo: bool, foo: bool,
} }
@ -248,7 +248,7 @@ fn docstrings_ordering_with_multiple_clap_partial() {
/// This is the docstring for Flattened /// This is the docstring for Flattened
#[derive(Args)] #[derive(Args)]
struct Flattened { struct Flattened {
#[clap(long)] #[clap(long, action)]
foo: bool, foo: bool,
} }

View file

@ -274,8 +274,8 @@ fn derive_generated_error_has_full_context() {
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
enum SubCommands { enum SubCommands {
Sub { Sub {
#[clap(short, long, parse(from_occurrences))] #[clap(short, long, action = clap::ArgAction::Count)]
verbose: u8, verbose: u64,
}, },
} }
@ -339,7 +339,7 @@ OPTIONS:
#[clap(next_display_order = 10000)] #[clap(next_display_order = 10000)]
struct A { struct A {
/// second flag /// second flag
#[clap(long)] #[clap(long, action)]
flag_a: bool, flag_a: bool,
/// second option /// second option
#[clap(long, value_parser)] #[clap(long, value_parser)]
@ -350,7 +350,7 @@ OPTIONS:
#[clap(next_display_order = 10)] #[clap(next_display_order = 10)]
struct B { struct B {
/// first flag /// first flag
#[clap(long)] #[clap(long, action)]
flag_b: bool, flag_b: bool,
/// first option /// first option
#[clap(long, value_parser)] #[clap(long, value_parser)]
@ -397,7 +397,7 @@ OPTIONS:
#[derive(Args, Debug)] #[derive(Args, Debug)]
struct A { struct A {
/// second flag /// second flag
#[clap(long)] #[clap(long, action)]
flag_a: bool, flag_a: bool,
/// second option /// second option
#[clap(long, value_parser)] #[clap(long, value_parser)]
@ -407,7 +407,7 @@ OPTIONS:
#[derive(Args, Debug)] #[derive(Args, Debug)]
struct B { struct B {
/// first flag /// first flag
#[clap(long)] #[clap(long, action)]
flag_b: bool, flag_b: bool,
/// first option /// first option
#[clap(long, value_parser)] #[clap(long, value_parser)]
@ -453,7 +453,7 @@ OPTIONS:
#[derive(Args, Debug)] #[derive(Args, Debug)]
struct A { struct A {
/// first flag /// first flag
#[clap(long)] #[clap(long, action)]
flag_a: bool, flag_a: bool,
/// first option /// first option
#[clap(long, value_parser)] #[clap(long, value_parser)]
@ -463,7 +463,7 @@ OPTIONS:
#[derive(Args, Debug)] #[derive(Args, Debug)]
struct B { struct B {
/// second flag /// second flag
#[clap(long)] #[clap(long, action)]
flag_b: bool, flag_b: bool,
/// second option /// second option
#[clap(long, value_parser)] #[clap(long, value_parser)]

View file

@ -5,14 +5,14 @@ use crate::utils;
use clap::{ArgGroup, Args, Parser, Subcommand}; use clap::{ArgGroup, Args, Parser, Subcommand};
#[test] #[test]
fn issue_151() { fn issue_151_groups_within_subcommands() {
#[derive(Args, Debug)] #[derive(Args, Debug)]
#[clap(group = ArgGroup::new("verb").required(true).multiple(true))] #[clap(group = ArgGroup::new("verb").required(true).multiple(true))]
struct Opt { struct Opt {
#[clap(long, group = "verb")] #[clap(long, group = "verb", value_parser)]
foo: bool, foo: Option<String>,
#[clap(long, group = "verb")] #[clap(long, group = "verb", value_parser)]
bar: bool, bar: Option<String>,
} }
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
@ -22,10 +22,10 @@ fn issue_151() {
} }
assert!(Cli::try_parse_from(&["test"]).is_err()); assert!(Cli::try_parse_from(&["test"]).is_err());
assert!(Cli::try_parse_from(&["test", "--foo"]).is_ok()); assert!(Cli::try_parse_from(&["test", "--foo=v1"]).is_ok());
assert!(Cli::try_parse_from(&["test", "--bar"]).is_ok()); assert!(Cli::try_parse_from(&["test", "--bar=v2"]).is_ok());
assert!(Cli::try_parse_from(&["test", "--zebra"]).is_err()); assert!(Cli::try_parse_from(&["test", "--zebra=v3"]).is_err());
assert!(Cli::try_parse_from(&["test", "--foo", "--bar"]).is_ok()); assert!(Cli::try_parse_from(&["test", "--foo=v1", "--bar=v2"]).is_ok());
} }
#[test] #[test]

View file

@ -5,7 +5,7 @@ fn test_standalone_long_generates_kebab_case() {
#[derive(Parser, Debug, PartialEq)] #[derive(Parser, Debug, PartialEq)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
struct Opt { struct Opt {
#[clap(long)] #[clap(long, action)]
FOO_OPTION: bool, FOO_OPTION: bool,
} }
@ -19,7 +19,7 @@ fn test_standalone_long_generates_kebab_case() {
fn test_custom_long_overwrites_default_name() { fn test_custom_long_overwrites_default_name() {
#[derive(Parser, Debug, PartialEq)] #[derive(Parser, Debug, PartialEq)]
struct Opt { struct Opt {
#[clap(long = "foo")] #[clap(long = "foo", action)]
foo_option: bool, foo_option: bool,
} }

View file

@ -16,9 +16,9 @@ use clap::{Parser, Subcommand};
#[derive(Parser, PartialEq, Debug)] #[derive(Parser, PartialEq, Debug)]
struct Opt { struct Opt {
#[clap(short, long)] #[clap(short, long, action)]
force: bool, force: bool,
#[clap(short, long, parse(from_occurrences))] #[clap(short, long, action = clap::ArgAction::Count)]
verbose: u64, verbose: u64,
#[clap(subcommand)] #[clap(subcommand)]
cmd: Sub, cmd: Sub,
@ -32,9 +32,9 @@ enum Sub {
#[derive(Parser, PartialEq, Debug)] #[derive(Parser, PartialEq, Debug)]
struct Opt2 { struct Opt2 {
#[clap(short, long)] #[clap(short, long, action)]
force: bool, force: bool,
#[clap(short, long, parse(from_occurrences))] #[clap(short, long, action = clap::ArgAction::Count)]
verbose: u64, verbose: u64,
#[clap(subcommand)] #[clap(subcommand)]
cmd: Option<Sub>, cmd: Option<Sub>,
@ -109,7 +109,7 @@ fn test_badinput() {
#[derive(Parser, PartialEq, Debug)] #[derive(Parser, PartialEq, Debug)]
struct Opt3 { struct Opt3 {
#[clap(short, long)] #[clap(short, long, action)]
all: bool, all: bool,
#[clap(subcommand)] #[clap(subcommand)]
cmd: Sub2, cmd: Sub2,

View file

@ -37,8 +37,11 @@ fn required_option() {
Opt { arg: 42 }, Opt { arg: 42 },
Opt::try_parse_from(&["test", "--arg", "42"]).unwrap() Opt::try_parse_from(&["test", "--arg", "42"]).unwrap()
); );
assert_eq!(
Opt { arg: 42 },
Opt::try_parse_from(&["test", "--arg", "24", "--arg", "42"]).unwrap()
);
assert!(Opt::try_parse_from(&["test"]).is_err()); assert!(Opt::try_parse_from(&["test"]).is_err());
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
} }
#[test] #[test]
@ -52,8 +55,11 @@ fn option_with_default() {
Opt { arg: 24 }, Opt { arg: 24 },
Opt::try_parse_from(&["test", "-a24"]).unwrap() Opt::try_parse_from(&["test", "-a24"]).unwrap()
); );
assert_eq!(
Opt { arg: 42 },
Opt::try_parse_from(&["test", "-a", "24", "-a", "42"]).unwrap()
);
assert_eq!(Opt { arg: 42 }, Opt::try_parse_from(&["test"]).unwrap()); assert_eq!(Opt { arg: 42 }, Opt::try_parse_from(&["test"]).unwrap());
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
} }
#[test] #[test]
@ -67,8 +73,11 @@ fn option_with_raw_default() {
Opt { arg: 24 }, Opt { arg: 24 },
Opt::try_parse_from(&["test", "-a24"]).unwrap() Opt::try_parse_from(&["test", "-a24"]).unwrap()
); );
assert_eq!(
Opt { arg: 42 },
Opt::try_parse_from(&["test", "-a", "24", "-a", "42"]).unwrap()
);
assert_eq!(Opt { arg: 42 }, Opt::try_parse_from(&["test"]).unwrap()); assert_eq!(Opt { arg: 42 }, Opt::try_parse_from(&["test"]).unwrap());
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
} }
#[test] #[test]
@ -157,8 +166,11 @@ fn option_type_is_optional() {
Opt { arg: Some(42) }, Opt { arg: Some(42) },
Opt::try_parse_from(&["test", "-a42"]).unwrap() Opt::try_parse_from(&["test", "-a42"]).unwrap()
); );
assert_eq!(
Opt { arg: Some(42) },
Opt::try_parse_from(&["test", "-a", "24", "-a", "42"]).unwrap()
);
assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap()); assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap());
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
} }
#[test] #[test]
@ -176,8 +188,8 @@ fn required_with_option_type() {
#[derive(Debug, PartialEq, Eq, Subcommand)] #[derive(Debug, PartialEq, Eq, Subcommand)]
enum SubCommands { enum SubCommands {
ExSub { ExSub {
#[clap(short, long, parse(from_occurrences))] #[clap(short, long, action = clap::ArgAction::Count)]
verbose: u8, verbose: u64,
}, },
} }
@ -238,8 +250,13 @@ fn option_option_type_is_optional_value() {
Opt { arg: Some(None) }, Opt { arg: Some(None) },
Opt::try_parse_from(&["test", "-a"]).unwrap() Opt::try_parse_from(&["test", "-a"]).unwrap()
); );
assert_eq!(
Opt {
arg: Some(Some(42))
},
Opt::try_parse_from(&["test", "-a", "24", "-a", "42"]).unwrap()
);
assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap()); assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap());
assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
} }
#[test] #[test]

View file

@ -20,19 +20,19 @@ use clap::{Args, Parser, Subcommand};
enum Opt { enum Opt {
/// Fetch stuff from GitHub /// Fetch stuff from GitHub
Fetch { Fetch {
#[clap(long)] #[clap(long, action)]
all: bool, all: bool,
#[clap(short, long)]
/// Overwrite local branches. /// Overwrite local branches.
#[clap(short, long, action)]
force: bool, force: bool,
#[clap(value_parser)] #[clap(value_parser)]
repo: String, repo: String,
}, },
Add { Add {
#[clap(short, long)] #[clap(short, long, action)]
interactive: bool, interactive: bool,
#[clap(short, long)] #[clap(short, long, action)]
verbose: bool, verbose: bool,
}, },
} }
@ -173,7 +173,7 @@ fn test_tuple_commands() {
fn global_passed_down() { fn global_passed_down() {
#[derive(Debug, PartialEq, Parser)] #[derive(Debug, PartialEq, Parser)]
struct Opt { struct Opt {
#[clap(global = true, long)] #[clap(global = true, long, action)]
other: bool, other: bool,
#[clap(subcommand)] #[clap(subcommand)]
sub: Subcommands, sub: Subcommands,
@ -187,7 +187,7 @@ fn global_passed_down() {
#[derive(Debug, PartialEq, Args)] #[derive(Debug, PartialEq, Args)]
struct GlobalCmd { struct GlobalCmd {
#[clap(from_global)] #[clap(from_global, action)]
other: bool, other: bool,
} }