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>,
methods: Vec<Method>,
value_parser: Option<ValueParser>,
action: Option<Action>,
parser: Option<Sp<Parser>>,
verbatim_doc_comment: Option<Ident>,
next_display_order: Option<Method>,
@ -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<Parser> {
@ -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)]

View file

@ -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
}
}
};

View file

@ -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)),

View file

@ -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`
@ -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,
}
```

View file

@ -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::<bool>("built").unwrap()
);
println!(
"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.

View file

@ -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,
},
}

View file

@ -11,11 +11,11 @@ USAGE:
custom-bool[EXE] [OPTIONS] --foo <FOO> <BOOM>
ARGS:
<BOOM>
<BOOM> [possible values: true, false]
OPTIONS:
--bar <BAR> [default: false]
--foo <FOO>
--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,

View file

@ -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,
}

View file

@ -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<String>,
@ -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,

View file

@ -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)]

View file

@ -14,8 +14,8 @@ struct Cli {
config: Option<PathBuf>,
/// 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<Commands>,
@ -26,7 +26,7 @@ enum Commands {
/// does testing things
Test {
/// lists test values
#[clap(short, long)]
#[clap(short, long, action)]
list: bool,
},
}

View file

@ -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,
}

View file

@ -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() {

View file

@ -13,15 +13,15 @@ struct Cli {
set_ver: Option<String>,
/// 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

View file

@ -8,15 +8,15 @@ struct Cli {
set_ver: Option<String>,
/// 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

View file

@ -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

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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)]

View file

@ -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<String>,
#[clap(long, group = "verb", value_parser)]
bar: Option<String>,
}
#[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]

View file

@ -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,
}

View file

@ -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<Sub>,
@ -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,

View file

@ -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]

View file

@ -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,
}