From 647896d929e01c9b1247c7b8b5e970559a1bacc4 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 1 Jun 2022 10:31:23 -0500 Subject: [PATCH] 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 --- clap_derive/src/attrs.rs | 168 +++++++++++++++++-- clap_derive/src/derives/args.rs | 12 +- clap_derive/src/parse.rs | 2 + examples/derive_ref/README.md | 6 +- examples/derive_ref/augment_args.rs | 11 +- examples/derive_ref/augment_subcommands.rs | 2 +- examples/derive_ref/custom-bool.md | 8 +- examples/derive_ref/custom-bool.rs | 11 +- examples/derive_ref/hand_subcommand.rs | 4 +- examples/escaped-positional-derive.rs | 2 +- examples/tutorial_derive/01_quick.rs | 6 +- examples/tutorial_derive/03_01_flag_bool.rs | 2 +- examples/tutorial_derive/03_01_flag_count.rs | 4 +- examples/tutorial_derive/04_03_relations.rs | 6 +- examples/tutorial_derive/04_04_custom.rs | 6 +- examples/tutorial_derive/README.md | 10 +- tests/derive/doc_comments_help.rs | 19 ++- tests/derive/flags.rs | 26 +-- tests/derive/flatten.rs | 8 +- tests/derive/help.rs | 16 +- tests/derive/issues.rs | 18 +- tests/derive/naming.rs | 4 +- tests/derive/nested_subcommands.rs | 10 +- tests/derive/options.rs | 31 +++- tests/derive/subcommands.rs | 12 +- 25 files changed, 293 insertions(+), 111 deletions(-) diff --git a/clap_derive/src/attrs.rs b/clap_derive/src/attrs.rs index dbe8115b..5eb12450 100644 --- a/clap_derive/src/attrs.rs +++ b/clap_derive/src/attrs.rs @@ -43,6 +43,7 @@ pub struct Attrs { doc_comment: Vec, methods: Vec, value_parser: Option, + action: Option, parser: Option>, verbatim_doc_comment: Option, next_display_order: Option, @@ -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"); } @@ -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(), @@ -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(), @@ -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"); } @@ -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(), @@ -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(), @@ -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()), @@ -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, @@ -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))); } @@ -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)); } @@ -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 { @@ -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()), } } @@ -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 { @@ -846,6 +971,10 @@ impl Method { Some(Method::new(ident, quote!(#lit))) } + + pub(crate) fn args(&self) -> &TokenStream { + &self.args + } } 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)] diff --git a/clap_derive/src/derives/args.rs b/clap_derive/src/derives/args.rs index 98b83cc6..47565edb 100644 --- a/clap_derive/src/derives/args.rs +++ b/clap_derive/src/derives/args.rs @@ -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; @@ -287,6 +288,7 @@ pub fn gen_augment( #possible_values #validator #value_parser + #action } } @@ -299,6 +301,7 @@ pub fn gen_augment( #possible_values #validator #value_parser + #action }, Ty::OptionVec => quote_spanned! { ty.span()=> @@ -308,6 +311,7 @@ pub fn gen_augment( #possible_values #validator #value_parser + #action }, Ty::Vec => { @@ -318,6 +322,7 @@ pub fn gen_augment( #possible_values #validator #value_parser + #action } } @@ -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 } } }; diff --git a/clap_derive/src/parse.rs b/clap_derive/src/parse.rs index 9dbc1454..15cd3e0f 100644 --- a/clap_derive/src/parse.rs +++ b/clap_derive/src/parse.rs @@ -27,6 +27,7 @@ pub enum ClapAttr { Short(Ident), Long(Ident), ValueParser(Ident), + Action(Ident), Env(Ident), Flatten(Ident), ArgEnum(Ident), @@ -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)), diff --git a/examples/derive_ref/README.md b/examples/derive_ref/README.md index 1b97baf4..35bee90f 100644 --- a/examples/derive_ref/README.md +++ b/examples/derive_ref/README.md @@ -179,6 +179,10 @@ These correspond to a `clap::Arg`. - `value_parser [= ]`: `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 [= ]`: `clap::Arg::action` + - When not present: will auto-select an action based on the field type + - When present, implies `#[clap(value_parser)]` - `help = `: `clap::Arg::help` - When not present: [Doc comment summary](#doc-comments) - `long_help = `: `clap::Arg::long_help` @@ -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, } ``` diff --git a/examples/derive_ref/augment_args.rs b/examples/derive_ref/augment_args.rs index 59765ad6..390c72f4 100644 --- a/examples/derive_ref/augment_args.rs +++ b/examples/derive_ref/augment_args.rs @@ -2,20 +2,23 @@ use clap::{arg, Args as _, Command, FromArgMatches as _, Parser}; #[derive(Parser, Debug)] struct DerivedArgs { - #[clap(short, long)] + #[clap(short, long, action)] derived: bool, } 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 let cli = DerivedArgs::augment_args(cli); let matches = cli.get_matches(); - println!("Value of built: {:?}", matches.is_present("built")); + println!( + "Value of built: {:?}", + *matches.get_one::("built").unwrap() + ); println!( "Value of derived via ArgMatches: {:?}", - matches.is_present("derived") + *matches.get_one::("derived").unwrap() ); // Since DerivedArgs implements FromArgMatches, we can extract it from the unstructured ArgMatches. diff --git a/examples/derive_ref/augment_subcommands.rs b/examples/derive_ref/augment_subcommands.rs index 299c7f8c..199da98b 100644 --- a/examples/derive_ref/augment_subcommands.rs +++ b/examples/derive_ref/augment_subcommands.rs @@ -3,7 +3,7 @@ use clap::{Command, FromArgMatches as _, Parser, Subcommand as _}; #[derive(Parser, Debug)] enum Subcommands { Derived { - #[clap(short, long)] + #[clap(short, long, action)] derived_flag: bool, }, } diff --git a/examples/derive_ref/custom-bool.md b/examples/derive_ref/custom-bool.md index c2c9bb1d..619f9ba8 100644 --- a/examples/derive_ref/custom-bool.md +++ b/examples/derive_ref/custom-bool.md @@ -11,11 +11,11 @@ USAGE: custom-bool[EXE] [OPTIONS] --foo ARGS: - + [possible values: true, false] OPTIONS: --bar [default: false] - --foo + --foo [possible values: true, false] -h, --help Print help information -V, --version Print version information @@ -31,14 +31,14 @@ USAGE: For more information try --help $ 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, bar: false, boom: 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, bar: true, boom: false, diff --git a/examples/derive_ref/custom-bool.rs b/examples/derive_ref/custom-bool.rs index b0e4c81c..d3c321e7 100644 --- a/examples/derive_ref/custom-bool.rs +++ b/examples/derive_ref/custom-bool.rs @@ -1,21 +1,20 @@ -#![allow(deprecated)] // Can't opt-out of implicit flags until #3405 use clap::Parser; #[derive(Parser, Debug, PartialEq)] #[clap(author, version, about, long_about = None)] 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 // works as expected. - #[clap(long, parse(try_from_str))] + #[clap(long, action = clap::ArgAction::Set)] foo: bool, // 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, - // `bool` can be positional only with explicit `parse(...)` annotation - #[clap(parse(try_from_str))] + // `bool` can be positional only with explicit `action` annotation + #[clap(action = clap::ArgAction::Set)] boom: bool, } diff --git a/examples/derive_ref/hand_subcommand.rs b/examples/derive_ref/hand_subcommand.rs index d85a2949..e9423bdc 100644 --- a/examples/derive_ref/hand_subcommand.rs +++ b/examples/derive_ref/hand_subcommand.rs @@ -8,7 +8,7 @@ struct AddArgs { } #[derive(Parser, Debug)] struct RemoveArgs { - #[clap(short, long)] + #[clap(short, long, action)] force: bool, #[clap(value_parser)] name: Vec, @@ -69,7 +69,7 @@ impl Subcommand for CliSub { #[derive(Parser, Debug)] struct Cli { - #[clap(short, long)] + #[clap(short, long, action)] top_level: bool, #[clap(subcommand)] subcommand: CliSub, diff --git a/examples/escaped-positional-derive.rs b/examples/escaped-positional-derive.rs index dd52c090..54dbc853 100644 --- a/examples/escaped-positional-derive.rs +++ b/examples/escaped-positional-derive.rs @@ -5,7 +5,7 @@ use clap::Parser; #[derive(Parser)] #[clap(author, version, about, long_about = None)] struct Cli { - #[clap(short = 'f')] + #[clap(short = 'f', action)] eff: bool, #[clap(short = 'p', value_name = "PEAR", value_parser)] diff --git a/examples/tutorial_derive/01_quick.rs b/examples/tutorial_derive/01_quick.rs index b78bf782..bc8d6a9f 100644 --- a/examples/tutorial_derive/01_quick.rs +++ b/examples/tutorial_derive/01_quick.rs @@ -14,8 +14,8 @@ struct Cli { config: Option, /// Turn debugging information on - #[clap(short, long, parse(from_occurrences))] - debug: usize, + #[clap(short, long, action = clap::ArgAction::Count)] + debug: u64, #[clap(subcommand)] command: Option, @@ -26,7 +26,7 @@ enum Commands { /// does testing things Test { /// lists test values - #[clap(short, long)] + #[clap(short, long, action)] list: bool, }, } diff --git a/examples/tutorial_derive/03_01_flag_bool.rs b/examples/tutorial_derive/03_01_flag_bool.rs index 8b574b74..de677d8c 100644 --- a/examples/tutorial_derive/03_01_flag_bool.rs +++ b/examples/tutorial_derive/03_01_flag_bool.rs @@ -3,7 +3,7 @@ use clap::Parser; #[derive(Parser)] #[clap(author, version, about, long_about = None)] struct Cli { - #[clap(short, long)] + #[clap(short, long, action)] verbose: bool, } diff --git a/examples/tutorial_derive/03_01_flag_count.rs b/examples/tutorial_derive/03_01_flag_count.rs index 2ab88397..a520c7a2 100644 --- a/examples/tutorial_derive/03_01_flag_count.rs +++ b/examples/tutorial_derive/03_01_flag_count.rs @@ -3,8 +3,8 @@ use clap::Parser; #[derive(Parser)] #[clap(author, version, about, long_about = None)] struct Cli { - #[clap(short, long, parse(from_occurrences))] - verbose: usize, + #[clap(short, long, action = clap::ArgAction::Count)] + verbose: u64, } fn main() { diff --git a/examples/tutorial_derive/04_03_relations.rs b/examples/tutorial_derive/04_03_relations.rs index 32b85805..f8523902 100644 --- a/examples/tutorial_derive/04_03_relations.rs +++ b/examples/tutorial_derive/04_03_relations.rs @@ -13,15 +13,15 @@ struct Cli { set_ver: Option, /// auto inc major - #[clap(long)] + #[clap(long, action)] major: bool, /// auto inc minor - #[clap(long)] + #[clap(long, action)] minor: bool, /// auto inc patch - #[clap(long)] + #[clap(long, action)] patch: bool, /// some regular input diff --git a/examples/tutorial_derive/04_04_custom.rs b/examples/tutorial_derive/04_04_custom.rs index d163e20e..454d489c 100644 --- a/examples/tutorial_derive/04_04_custom.rs +++ b/examples/tutorial_derive/04_04_custom.rs @@ -8,15 +8,15 @@ struct Cli { set_ver: Option, /// auto inc major - #[clap(long)] + #[clap(long, action)] major: bool, /// auto inc minor - #[clap(long)] + #[clap(long, action)] minor: bool, /// auto inc patch - #[clap(long)] + #[clap(long, action)] patch: bool, /// some regular input diff --git a/examples/tutorial_derive/README.md b/examples/tutorial_derive/README.md index 93d35454..7e42a8ad 100644 --- a/examples/tutorial_derive/README.md +++ b/examples/tutorial_derive/README.md @@ -236,17 +236,11 @@ $ 03_01_flag_bool_derive --verbose verbose: true $ 03_01_flag_bool_derive --verbose --verbose -? failed -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 +verbose: true ``` -Or counted with `#[clap(parse(from_occurrences))]`: +Or counted with `#[clap(action = clap::ArgAction::Count)]`: [Example:](03_01_flag_count.rs) ```console diff --git a/tests/derive/doc_comments_help.rs b/tests/derive/doc_comments_help.rs index 697672ce..5ff6290f 100644 --- a/tests/derive/doc_comments_help.rs +++ b/tests/derive/doc_comments_help.rs @@ -23,7 +23,7 @@ fn doc_comments() { struct LoremIpsum { /// Fooify a bar /// and a baz - #[clap(short, long)] + #[clap(short, long, action)] foo: bool, } @@ -39,7 +39,12 @@ fn help_is_better_than_comments() { #[clap(name = "lorem-ipsum", about = "Dolor sit amet")] struct LoremIpsum { /// 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, } @@ -71,11 +76,11 @@ fn field_long_doc_comment_both_help_long_help() { /// Dot is removed from multiline comments. /// /// Long help - #[clap(long)] + #[clap(long, action)] foo: bool, /// Dot is removed from one short comment. - #[clap(long)] + #[clap(long, action)] bar: bool, } @@ -141,7 +146,7 @@ fn verbatim_doc_comment() { #[derive(Parser, Debug)] #[clap(verbatim_doc_comment)] struct SeeFigure1 { - #[clap(long)] + #[clap(long, action)] foo: bool, } @@ -171,10 +176,10 @@ fn verbatim_doc_comment_field() { #[derive(Parser, Debug)] struct Command { /// This help ends in a period. - #[clap(long, verbatim_doc_comment)] + #[clap(long, verbatim_doc_comment, action)] foo: bool, /// This help does not end in a period. - #[clap(long)] + #[clap(long, action)] bar: bool, } diff --git a/tests/derive/flags.rs b/tests/derive/flags.rs index cbf2ef9f..8b17dd6e 100644 --- a/tests/derive/flags.rs +++ b/tests/derive/flags.rs @@ -20,7 +20,7 @@ use clap::Parser; fn bool_type_is_flag() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, long)] + #[clap(short, long, action)] alice: bool, } @@ -32,24 +32,26 @@ fn bool_type_is_flag() { Opt { alice: true }, Opt::try_parse_from(&["test", "-a"]).unwrap() ); + assert_eq!( + Opt { alice: true }, + Opt::try_parse_from(&["test", "-a", "-a"]).unwrap() + ); assert_eq!( Opt { alice: true }, Opt::try_parse_from(&["test", "--alice"]).unwrap() ); 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", "-a"]).is_err()); - assert!(Opt::try_parse_from(&["test", "-a", "--alice"]).is_err()); } #[test] -fn from_occurrences() { +fn count() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, long, parse(from_occurrences))] + #[clap(short, long, action = clap::ArgAction::Count)] alice: u64, - #[clap(short, long, parse(from_occurrences))] - bob: u8, + #[clap(short, long, action = clap::ArgAction::Count)] + bob: u64, } assert_eq!( @@ -111,9 +113,9 @@ fn non_bool_type_flag() { fn mixed_type_flags() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, long)] + #[clap(short, long, action)] alice: bool, - #[clap(short, long, parse(from_occurrences))] + #[clap(short, long, action = clap::ArgAction::Count)] bob: u64, } @@ -191,10 +193,10 @@ fn ignore_qualified_bool_type() { } #[test] -fn override_implicit_from_flag() { +fn override_implicit_action() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(long, parse(try_from_str))] + #[clap(long, action = clap::ArgAction::Set)] arg: bool, } @@ -213,7 +215,7 @@ fn override_implicit_from_flag() { fn override_implicit_from_flag_positional() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(parse(try_from_str))] + #[clap(action = clap::ArgAction::Set)] arg: bool, } diff --git a/tests/derive/flatten.rs b/tests/derive/flatten.rs index 15bef4db..df451265 100644 --- a/tests/derive/flatten.rs +++ b/tests/derive/flatten.rs @@ -70,7 +70,7 @@ fn flatten_in_subcommand() { #[derive(Args, PartialEq, Debug)] struct Add { - #[clap(short)] + #[clap(short, action)] interactive: bool, #[clap(flatten)] common: Common, @@ -79,7 +79,7 @@ fn flatten_in_subcommand() { #[derive(Parser, PartialEq, Debug)] enum Opt { Fetch { - #[clap(short)] + #[clap(short, action)] all: bool, #[clap(flatten)] common: Common, @@ -227,7 +227,7 @@ fn docstrings_ordering_with_multiple_clap() { /// This is the docstring for Flattened #[derive(Args)] struct Flattened { - #[clap(long)] + #[clap(long, action)] foo: bool, } @@ -248,7 +248,7 @@ fn docstrings_ordering_with_multiple_clap_partial() { /// This is the docstring for Flattened #[derive(Args)] struct Flattened { - #[clap(long)] + #[clap(long, action)] foo: bool, } diff --git a/tests/derive/help.rs b/tests/derive/help.rs index 4254dda5..8e9f7d50 100644 --- a/tests/derive/help.rs +++ b/tests/derive/help.rs @@ -274,8 +274,8 @@ fn derive_generated_error_has_full_context() { #[derive(Debug, Parser)] enum SubCommands { Sub { - #[clap(short, long, parse(from_occurrences))] - verbose: u8, + #[clap(short, long, action = clap::ArgAction::Count)] + verbose: u64, }, } @@ -339,7 +339,7 @@ OPTIONS: #[clap(next_display_order = 10000)] struct A { /// second flag - #[clap(long)] + #[clap(long, action)] flag_a: bool, /// second option #[clap(long, value_parser)] @@ -350,7 +350,7 @@ OPTIONS: #[clap(next_display_order = 10)] struct B { /// first flag - #[clap(long)] + #[clap(long, action)] flag_b: bool, /// first option #[clap(long, value_parser)] @@ -397,7 +397,7 @@ OPTIONS: #[derive(Args, Debug)] struct A { /// second flag - #[clap(long)] + #[clap(long, action)] flag_a: bool, /// second option #[clap(long, value_parser)] @@ -407,7 +407,7 @@ OPTIONS: #[derive(Args, Debug)] struct B { /// first flag - #[clap(long)] + #[clap(long, action)] flag_b: bool, /// first option #[clap(long, value_parser)] @@ -453,7 +453,7 @@ OPTIONS: #[derive(Args, Debug)] struct A { /// first flag - #[clap(long)] + #[clap(long, action)] flag_a: bool, /// first option #[clap(long, value_parser)] @@ -463,7 +463,7 @@ OPTIONS: #[derive(Args, Debug)] struct B { /// second flag - #[clap(long)] + #[clap(long, action)] flag_b: bool, /// second option #[clap(long, value_parser)] diff --git a/tests/derive/issues.rs b/tests/derive/issues.rs index ee3ff1ba..22c93c87 100644 --- a/tests/derive/issues.rs +++ b/tests/derive/issues.rs @@ -5,14 +5,14 @@ use crate::utils; use clap::{ArgGroup, Args, Parser, Subcommand}; #[test] -fn issue_151() { +fn issue_151_groups_within_subcommands() { #[derive(Args, Debug)] #[clap(group = ArgGroup::new("verb").required(true).multiple(true))] struct Opt { - #[clap(long, group = "verb")] - foo: bool, - #[clap(long, group = "verb")] - bar: bool, + #[clap(long, group = "verb", value_parser)] + foo: Option, + #[clap(long, group = "verb", value_parser)] + bar: Option, } #[derive(Debug, Parser)] @@ -22,10 +22,10 @@ fn issue_151() { } assert!(Cli::try_parse_from(&["test"]).is_err()); - assert!(Cli::try_parse_from(&["test", "--foo"]).is_ok()); - assert!(Cli::try_parse_from(&["test", "--bar"]).is_ok()); - assert!(Cli::try_parse_from(&["test", "--zebra"]).is_err()); - assert!(Cli::try_parse_from(&["test", "--foo", "--bar"]).is_ok()); + assert!(Cli::try_parse_from(&["test", "--foo=v1"]).is_ok()); + assert!(Cli::try_parse_from(&["test", "--bar=v2"]).is_ok()); + assert!(Cli::try_parse_from(&["test", "--zebra=v3"]).is_err()); + assert!(Cli::try_parse_from(&["test", "--foo=v1", "--bar=v2"]).is_ok()); } #[test] diff --git a/tests/derive/naming.rs b/tests/derive/naming.rs index b7889921..e39cf2c8 100644 --- a/tests/derive/naming.rs +++ b/tests/derive/naming.rs @@ -5,7 +5,7 @@ fn test_standalone_long_generates_kebab_case() { #[derive(Parser, Debug, PartialEq)] #[allow(non_snake_case)] struct Opt { - #[clap(long)] + #[clap(long, action)] FOO_OPTION: bool, } @@ -19,7 +19,7 @@ fn test_standalone_long_generates_kebab_case() { fn test_custom_long_overwrites_default_name() { #[derive(Parser, Debug, PartialEq)] struct Opt { - #[clap(long = "foo")] + #[clap(long = "foo", action)] foo_option: bool, } diff --git a/tests/derive/nested_subcommands.rs b/tests/derive/nested_subcommands.rs index 98fec967..7fe74ab6 100644 --- a/tests/derive/nested_subcommands.rs +++ b/tests/derive/nested_subcommands.rs @@ -16,9 +16,9 @@ use clap::{Parser, Subcommand}; #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, long)] + #[clap(short, long, action)] force: bool, - #[clap(short, long, parse(from_occurrences))] + #[clap(short, long, action = clap::ArgAction::Count)] verbose: u64, #[clap(subcommand)] cmd: Sub, @@ -32,9 +32,9 @@ enum Sub { #[derive(Parser, PartialEq, Debug)] struct Opt2 { - #[clap(short, long)] + #[clap(short, long, action)] force: bool, - #[clap(short, long, parse(from_occurrences))] + #[clap(short, long, action = clap::ArgAction::Count)] verbose: u64, #[clap(subcommand)] cmd: Option, @@ -109,7 +109,7 @@ fn test_badinput() { #[derive(Parser, PartialEq, Debug)] struct Opt3 { - #[clap(short, long)] + #[clap(short, long, action)] all: bool, #[clap(subcommand)] cmd: Sub2, diff --git a/tests/derive/options.rs b/tests/derive/options.rs index fb23e245..8c774bc0 100644 --- a/tests/derive/options.rs +++ b/tests/derive/options.rs @@ -37,8 +37,11 @@ fn required_option() { Opt { arg: 42 }, 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", "-a42", "-a24"]).is_err()); } #[test] @@ -52,8 +55,11 @@ fn option_with_default() { Opt { arg: 24 }, 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!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err()); } #[test] @@ -67,8 +73,11 @@ fn option_with_raw_default() { Opt { arg: 24 }, 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!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err()); } #[test] @@ -157,8 +166,11 @@ fn option_type_is_optional() { Opt { arg: Some(42) }, 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!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err()); } #[test] @@ -176,8 +188,8 @@ fn required_with_option_type() { #[derive(Debug, PartialEq, Eq, Subcommand)] enum SubCommands { ExSub { - #[clap(short, long, parse(from_occurrences))] - verbose: u8, + #[clap(short, long, action = clap::ArgAction::Count)] + verbose: u64, }, } @@ -238,8 +250,13 @@ fn option_option_type_is_optional_value() { Opt { arg: Some(None) }, 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!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err()); } #[test] diff --git a/tests/derive/subcommands.rs b/tests/derive/subcommands.rs index 4f3ea1df..2dcb8976 100644 --- a/tests/derive/subcommands.rs +++ b/tests/derive/subcommands.rs @@ -20,19 +20,19 @@ use clap::{Args, Parser, Subcommand}; enum Opt { /// Fetch stuff from GitHub Fetch { - #[clap(long)] + #[clap(long, action)] all: bool, - #[clap(short, long)] /// Overwrite local branches. + #[clap(short, long, action)] force: bool, #[clap(value_parser)] repo: String, }, Add { - #[clap(short, long)] + #[clap(short, long, action)] interactive: bool, - #[clap(short, long)] + #[clap(short, long, action)] verbose: bool, }, } @@ -173,7 +173,7 @@ fn test_tuple_commands() { fn global_passed_down() { #[derive(Debug, PartialEq, Parser)] struct Opt { - #[clap(global = true, long)] + #[clap(global = true, long, action)] other: bool, #[clap(subcommand)] sub: Subcommands, @@ -187,7 +187,7 @@ fn global_passed_down() { #[derive(Debug, PartialEq, Args)] struct GlobalCmd { - #[clap(from_global)] + #[clap(from_global, action)] other: bool, }