From 483b64ee8e38d1032a318d7ddc033611d9554070 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 13 Jun 2022 16:45:09 -0500 Subject: [PATCH 1/5] test(derive): Update another case to new approach --- tests/derive/flags.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/derive/flags.rs b/tests/derive/flags.rs index 1ad4189b747..d8e55290d0a 100644 --- a/tests/derive/flags.rs +++ b/tests/derive/flags.rs @@ -167,7 +167,7 @@ fn mixed_type_flags() { fn ignore_qualified_bool_type() { mod inner { #[allow(non_camel_case_types)] - #[derive(PartialEq, Debug)] + #[derive(PartialEq, Debug, Clone)] pub struct bool(pub String); impl std::str::FromStr for self::bool { @@ -181,6 +181,7 @@ fn ignore_qualified_bool_type() { #[derive(Parser, PartialEq, Debug)] struct Opt { + #[clap(action)] arg: inner::bool, } From b7668e84f5ed3565579c469dc3e6e7bcd12aeb8f Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 13 Jun 2022 16:47:25 -0500 Subject: [PATCH 2/5] test: Don't run legacy tests with v4 behavior --- tests/builder/legacy/mod.rs | 1 + tests/derive/legacy/mod.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/builder/legacy/mod.rs b/tests/builder/legacy/mod.rs index 4f759894f73..803709c1890 100644 --- a/tests/builder/legacy/mod.rs +++ b/tests/builder/legacy/mod.rs @@ -1,4 +1,5 @@ #![allow(deprecated)] +#![cfg(not(feature = "unstable-v4"))] mod app_from_crate; mod app_settings; diff --git a/tests/derive/legacy/mod.rs b/tests/derive/legacy/mod.rs index 6578b8c4b59..703cef3115f 100644 --- a/tests/derive/legacy/mod.rs +++ b/tests/derive/legacy/mod.rs @@ -1,4 +1,5 @@ #![allow(deprecated)] +#![cfg(not(feature = "unstable-v4"))] mod app_name; mod arg_enum; From 368624426410af96831cf20238d7c5fa3ecfc543 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 13 Jun 2022 16:56:34 -0500 Subject: [PATCH 3/5] test(derive): No longer supporting some wrapped types With the new `ArgMatches`, we need to know what the inner type is. Unfortunately, #3142 didn't list use cases for this. We dropped the `Option` alias changing `T` but we still have a `Result` in there that is aliased. One potential workaround if people need it is if we add an attribute to specify the `get_many::` type. This would also help with `ArgAction::Count` to support more data types. --- tests/derive/type_alias_regressions.rs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/tests/derive/type_alias_regressions.rs b/tests/derive/type_alias_regressions.rs index fe4b45739fa..c418644f413 100644 --- a/tests/derive/type_alias_regressions.rs +++ b/tests/derive/type_alias_regressions.rs @@ -1,27 +1,13 @@ +//! Regression test to ensure that type aliases do not cause compilation failures. #![allow(deprecated)] -/// Regression test to ensure that type aliases do not cause compilation failures. -use std::str::FromStr; - use clap::{Parser, Subcommand, ValueEnum}; // Result type alias #[allow(dead_code)] type Result = std::result::Result>; -// Wrapper to use for Option type alias -#[derive(Debug, PartialEq, Eq)] -struct Wrapper(T); - -impl FromStr for Wrapper { - type Err = ::Err; - - fn from_str(s: &str) -> std::result::Result { - T::from_str(s).map(Wrapper) - } -} - -type Option = std::option::Option>; +type Option = std::option::Option; #[derive(Parser)] pub struct Opts { From cc76d2881cdc0e2419bcfa4e594ed2ef9cc15710 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 13 Jun 2022 17:00:39 -0500 Subject: [PATCH 4/5] test(derive): Allow specializing tests for unstable-v4 --- tests/derive/main.rs | 4 +- tests/derive/next/app_name.rs | 97 +++ tests/derive/next/arguments.rs | 124 ++++ tests/derive/next/author_version_about.rs | 51 ++ tests/derive/next/basic.rs | 51 ++ tests/derive/next/boxed.rs | 49 ++ tests/derive/next/custom_string_parsers.rs | 319 +++++++++ tests/derive/next/default_value.rs | 57 ++ tests/derive/next/deny_warnings.rs | 53 ++ tests/derive/next/doc_comments_help.rs | 248 +++++++ .../derive/next/explicit_name_no_renaming.rs | 36 + tests/derive/next/flags.rs | 232 +++++++ tests/derive/next/flatten.rs | 264 ++++++++ tests/derive/next/generic.rs | 152 +++++ tests/derive/next/help.rs | 527 +++++++++++++++ tests/derive/next/issues.rs | 132 ++++ tests/derive/next/macros.rs | 53 ++ tests/derive/next/mod.rs | 34 + tests/derive/next/naming.rs | 360 ++++++++++ tests/derive/next/nested_subcommands.rs | 203 ++++++ tests/derive/next/non_literal_attributes.rs | 157 +++++ tests/derive/next/options.rs | 522 +++++++++++++++ tests/derive/next/privacy.rs | 37 ++ tests/derive/next/raw_bool_literal.rs | 29 + tests/derive/next/raw_idents.rs | 24 + tests/derive/next/rename_all_env.rs | 47 ++ tests/derive/next/skip.rs | 157 +++++ tests/derive/next/structopt.rs | 23 + tests/derive/next/subcommands.rs | 622 ++++++++++++++++++ tests/derive/next/type_alias_regressions.rs | 36 + tests/derive/next/utf8.rs | 227 +++++++ tests/derive/next/utils.rs | 56 ++ tests/derive/next/value_enum.rs | 527 +++++++++++++++ 33 files changed, 5509 insertions(+), 1 deletion(-) create mode 100644 tests/derive/next/app_name.rs create mode 100644 tests/derive/next/arguments.rs create mode 100644 tests/derive/next/author_version_about.rs create mode 100644 tests/derive/next/basic.rs create mode 100644 tests/derive/next/boxed.rs create mode 100644 tests/derive/next/custom_string_parsers.rs create mode 100644 tests/derive/next/default_value.rs create mode 100644 tests/derive/next/deny_warnings.rs create mode 100644 tests/derive/next/doc_comments_help.rs create mode 100644 tests/derive/next/explicit_name_no_renaming.rs create mode 100644 tests/derive/next/flags.rs create mode 100644 tests/derive/next/flatten.rs create mode 100644 tests/derive/next/generic.rs create mode 100644 tests/derive/next/help.rs create mode 100644 tests/derive/next/issues.rs create mode 100644 tests/derive/next/macros.rs create mode 100644 tests/derive/next/mod.rs create mode 100644 tests/derive/next/naming.rs create mode 100644 tests/derive/next/nested_subcommands.rs create mode 100644 tests/derive/next/non_literal_attributes.rs create mode 100644 tests/derive/next/options.rs create mode 100644 tests/derive/next/privacy.rs create mode 100644 tests/derive/next/raw_bool_literal.rs create mode 100644 tests/derive/next/raw_idents.rs create mode 100644 tests/derive/next/rename_all_env.rs create mode 100644 tests/derive/next/skip.rs create mode 100644 tests/derive/next/structopt.rs create mode 100644 tests/derive/next/subcommands.rs create mode 100644 tests/derive/next/type_alias_regressions.rs create mode 100644 tests/derive/next/utf8.rs create mode 100644 tests/derive/next/utils.rs create mode 100644 tests/derive/next/value_enum.rs diff --git a/tests/derive/main.rs b/tests/derive/main.rs index 78a8eee36a5..cc5b529fe33 100644 --- a/tests/derive/main.rs +++ b/tests/derive/main.rs @@ -1,5 +1,8 @@ #![cfg(feature = "derive")] +mod legacy; +mod next; + mod app_name; mod arguments; mod author_version_about; @@ -15,7 +18,6 @@ mod flatten; mod generic; mod help; mod issues; -mod legacy; mod macros; mod naming; mod nested_subcommands; diff --git a/tests/derive/next/app_name.rs b/tests/derive/next/app_name.rs new file mode 100644 index 00000000000..9e2f8d802c1 --- /dev/null +++ b/tests/derive/next/app_name.rs @@ -0,0 +1,97 @@ +use clap::CommandFactory; +use clap::Parser; +#[test] +fn app_name_in_short_help_from_struct() { + #[derive(Parser)] + #[clap(name = "my-cmd")] + struct MyApp {} + + let mut help = Vec::new(); + MyApp::command().write_help(&mut help).unwrap(); + let help = String::from_utf8(help).unwrap(); + + assert!(help.contains("my-cmd")); +} + +#[test] +fn app_name_in_long_help_from_struct() { + #[derive(Parser)] + #[clap(name = "my-cmd")] + struct MyApp {} + + let mut help = Vec::new(); + MyApp::command().write_long_help(&mut help).unwrap(); + let help = String::from_utf8(help).unwrap(); + + assert!(help.contains("my-cmd")); +} + +#[test] +fn app_name_in_short_help_from_enum() { + #[derive(Parser)] + #[clap(name = "my-cmd")] + enum MyApp {} + + let mut help = Vec::new(); + MyApp::command().write_help(&mut help).unwrap(); + let help = String::from_utf8(help).unwrap(); + + assert!(help.contains("my-cmd")); +} + +#[test] +fn app_name_in_long_help_from_enum() { + #[derive(Parser)] + #[clap(name = "my-cmd")] + enum MyApp {} + + let mut help = Vec::new(); + MyApp::command().write_long_help(&mut help).unwrap(); + let help = String::from_utf8(help).unwrap(); + + assert!(help.contains("my-cmd")); +} + +#[test] +fn app_name_in_short_version_from_struct() { + #[derive(Parser)] + #[clap(name = "my-cmd")] + struct MyApp {} + + let version = MyApp::command().render_version(); + + assert!(version.contains("my-cmd")); +} + +#[test] +fn app_name_in_long_version_from_struct() { + #[derive(Parser)] + #[clap(name = "my-cmd")] + struct MyApp {} + + let version = MyApp::command().render_long_version(); + + assert!(version.contains("my-cmd")); +} + +#[test] +fn app_name_in_short_version_from_enum() { + #[derive(Parser)] + #[clap(name = "my-cmd")] + enum MyApp {} + + let version = MyApp::command().render_version(); + + assert!(version.contains("my-cmd")); +} + +#[test] +fn app_name_in_long_version_from_enum() { + #[derive(Parser)] + #[clap(name = "my-cmd")] + enum MyApp {} + + let version = MyApp::command().render_long_version(); + + assert!(version.contains("my-cmd")); +} diff --git a/tests/derive/next/arguments.rs b/tests/derive/next/arguments.rs new file mode 100644 index 00000000000..0d3f0b1e262 --- /dev/null +++ b/tests/derive/next/arguments.rs @@ -0,0 +1,124 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// This work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +use clap::CommandFactory; +use clap::Parser; + +#[test] +fn required_argument() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_parser)] + arg: i32, + } + assert_eq!( + Opt { arg: 42 }, + Opt::try_parse_from(&["test", "42"]).unwrap() + ); + assert!(Opt::try_parse_from(&["test"]).is_err()); + assert!(Opt::try_parse_from(&["test", "42", "24"]).is_err()); +} + +#[test] +fn argument_with_default() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_parser, default_value = "42")] + arg: i32, + } + assert_eq!( + Opt { arg: 24 }, + Opt::try_parse_from(&["test", "24"]).unwrap() + ); + assert_eq!(Opt { arg: 42 }, Opt::try_parse_from(&["test"]).unwrap()); + assert!(Opt::try_parse_from(&["test", "42", "24"]).is_err()); +} + +#[test] +fn auto_value_name() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_parser)] + my_special_arg: i32, + } + + let mut help = Vec::new(); + Opt::command().write_help(&mut help).unwrap(); + let help = String::from_utf8(help).unwrap(); + + assert!(help.contains("MY_SPECIAL_ARG")); + // Ensure the implicit `num_vals` is just 1 + assert_eq!( + Opt { my_special_arg: 10 }, + Opt::try_parse_from(&["test", "10"]).unwrap() + ); +} + +#[test] +fn explicit_value_name() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_parser, value_name = "BROWNIE_POINTS")] + my_special_arg: i32, + } + + let mut help = Vec::new(); + Opt::command().write_help(&mut help).unwrap(); + let help = String::from_utf8(help).unwrap(); + + assert!(help.contains("BROWNIE_POINTS")); + assert!(!help.contains("MY_SPECIAL_ARG")); + // Ensure the implicit `num_vals` is just 1 + assert_eq!( + Opt { my_special_arg: 10 }, + Opt::try_parse_from(&["test", "10"]).unwrap() + ); +} + +#[test] +fn option_type_is_optional() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_parser)] + arg: Option, + } + assert_eq!( + Opt { arg: Some(42) }, + Opt::try_parse_from(&["test", "42"]).unwrap() + ); + assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap()); + assert!(Opt::try_parse_from(&["test", "42", "24"]).is_err()); +} + +#[test] +fn vec_type_is_multiple_values() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_parser)] + arg: Vec, + } + assert_eq!( + Opt { arg: vec![24] }, + Opt::try_parse_from(&["test", "24"]).unwrap() + ); + assert_eq!(Opt { arg: vec![] }, Opt::try_parse_from(&["test"]).unwrap()); + assert_eq!( + Opt { arg: vec![24, 42] }, + Opt::try_parse_from(&["test", "24", "42"]).unwrap() + ); + assert_eq!( + clap::ErrorKind::ValueValidation, + Opt::try_parse_from(&["test", "NOPE"]).err().unwrap().kind() + ); +} diff --git a/tests/derive/next/author_version_about.rs b/tests/derive/next/author_version_about.rs new file mode 100644 index 00000000000..3c68cd56129 --- /dev/null +++ b/tests/derive/next/author_version_about.rs @@ -0,0 +1,51 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// This work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +use crate::utils; + +use clap::Parser; + +#[test] +fn no_author_version_about() { + #[derive(Parser, PartialEq, Debug)] + #[clap(name = "foo")] + struct Opt {} + + let output = utils::get_long_help::(); + assert!(output.starts_with("foo \n\nUSAGE:")); +} + +#[test] +fn use_env() { + #[derive(Parser, PartialEq, Debug)] + #[clap(author, about, version)] + struct Opt {} + + let output = utils::get_long_help::(); + assert!(output.starts_with("clap")); + assert!(output + .contains("A simple to use, efficient, and full-featured Command Line Argument Parser")); +} + +#[test] +fn explicit_version_not_str_lit() { + const VERSION: &str = "custom version"; + + #[derive(Parser)] + #[clap(version = VERSION)] + pub struct Opt {} + + let output = utils::get_long_help::(); + assert!(output.contains("custom version")); +} diff --git a/tests/derive/next/basic.rs b/tests/derive/next/basic.rs new file mode 100644 index 00000000000..f32d17791ae --- /dev/null +++ b/tests/derive/next/basic.rs @@ -0,0 +1,51 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// This work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +use clap::Parser; + +#[test] +fn basic() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short = 'a', long = "arg", value_parser)] + arg: i32, + } + assert_eq!( + Opt { arg: 24 }, + Opt::try_parse_from(&["test", "-a24"]).unwrap() + ); +} + +#[test] +fn update_basic() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short = 'a', long = "arg", value_parser)] + single_value: i32, + } + + let mut opt = Opt::try_parse_from(&["test", "-a0"]).unwrap(); + + opt.update_from(&["test", "-a42"]); + + assert_eq!(Opt { single_value: 42 }, opt); +} + +#[test] +fn unit_struct() { + #[derive(Parser, PartialEq, Debug)] + struct Opt; + + assert_eq!(Opt {}, Opt::try_parse_from(&["test"]).unwrap()); +} diff --git a/tests/derive/next/boxed.rs b/tests/derive/next/boxed.rs new file mode 100644 index 00000000000..0af81f6fcfd --- /dev/null +++ b/tests/derive/next/boxed.rs @@ -0,0 +1,49 @@ +use clap::{Args, Parser, Subcommand}; + +#[derive(Parser, PartialEq, Debug)] +struct Opt { + #[clap(subcommand)] + sub: Box, +} + +#[derive(Subcommand, PartialEq, Debug)] +enum Sub { + Flame { + #[clap(flatten)] + arg: Box, + }, +} + +#[derive(Args, PartialEq, Debug)] +struct Ext { + #[clap(value_parser)] + arg: u32, +} + +#[test] +fn boxed_flatten_subcommand() { + assert_eq!( + Opt { + sub: Box::new(Sub::Flame { + arg: Box::new(Ext { arg: 1 }) + }) + }, + Opt::try_parse_from(&["test", "flame", "1"]).unwrap() + ); +} + +#[test] +fn update_boxed_flatten_subcommand() { + let mut opt = Opt::try_parse_from(&["test", "flame", "1"]).unwrap(); + + opt.update_from(&["test", "flame", "42"]); + + assert_eq!( + Opt { + sub: Box::new(Sub::Flame { + arg: Box::new(Ext { arg: 42 }) + }) + }, + opt + ); +} diff --git a/tests/derive/next/custom_string_parsers.rs b/tests/derive/next/custom_string_parsers.rs new file mode 100644 index 00000000000..ef7f0e88a6b --- /dev/null +++ b/tests/derive/next/custom_string_parsers.rs @@ -0,0 +1,319 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// This work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +#![allow(deprecated)] + +use clap::Parser; + +use std::ffi::CString; +use std::num::ParseIntError; +use std::path::PathBuf; + +#[derive(Parser, PartialEq, Debug)] +struct PathOpt { + #[clap(short, long, value_parser)] + path: PathBuf, + + #[clap(short, default_value = "../", value_parser)] + default_path: PathBuf, + + #[clap(short, value_parser, multiple_occurrences(true))] + vector_path: Vec, + + #[clap(short, value_parser)] + option_path_1: Option, + + #[clap(short = 'q', value_parser)] + option_path_2: Option, +} + +#[test] +fn test_path_opt_simple() { + assert_eq!( + PathOpt { + path: PathBuf::from("/usr/bin"), + default_path: PathBuf::from("../"), + vector_path: vec![ + PathBuf::from("/a/b/c"), + PathBuf::from("/d/e/f"), + PathBuf::from("/g/h/i"), + ], + option_path_1: None, + option_path_2: Some(PathBuf::from("j.zip")), + }, + PathOpt::try_parse_from(&[ + "test", "-p", "/usr/bin", "-v", "/a/b/c", "-v", "/d/e/f", "-v", "/g/h/i", "-q", + "j.zip", + ]) + .unwrap() + ); +} + +fn parse_hex(input: &str) -> Result { + u64::from_str_radix(input, 16) +} + +#[derive(Parser, PartialEq, Debug)] +struct HexOpt { + #[clap(short, value_parser = parse_hex)] + number: u64, +} + +#[test] +fn test_parse_hex() { + assert_eq!( + HexOpt { number: 5 }, + HexOpt::try_parse_from(&["test", "-n", "5"]).unwrap() + ); + assert_eq!( + HexOpt { + number: 0x00ab_cdef + }, + HexOpt::try_parse_from(&["test", "-n", "abcdef"]).unwrap() + ); + + let err = HexOpt::try_parse_from(&["test", "-n", "gg"]).unwrap_err(); + assert!( + err.to_string().contains("invalid digit found in string"), + "{}", + err + ); +} + +#[derive(Debug)] +struct ErrCode(u32); +impl std::error::Error for ErrCode {} +impl std::fmt::Display for ErrCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} +fn custom_parser_2(_: &str) -> Result<&'static str, ErrCode> { + Ok("B") +} + +#[derive(Parser, PartialEq, Debug)] +struct NoOpOpt { + #[clap(short, value_parser = custom_parser_2)] + b: &'static str, +} + +#[test] +fn test_every_custom_parser() { + assert_eq!( + NoOpOpt { b: "B" }, + NoOpOpt::try_parse_from(&["test", "-b=?"]).unwrap() + ); +} + +#[test] +fn update_every_custom_parser() { + let mut opt = NoOpOpt { b: "0" }; + + opt.try_update_from(&["test", "-b=?"]).unwrap(); + + assert_eq!(NoOpOpt { b: "B" }, opt); +} + +// Note: can't use `Vec` directly, as clap would instead look for +// conversion function from `&str` to `u8`. +type Bytes = Vec; + +#[derive(Parser, PartialEq, Debug)] +struct DefaultedOpt { + #[clap(short, parse(from_str))] + bytes: Bytes, + + #[clap(short, value_parser)] + integer: u64, + + #[clap(short, value_parser)] + path: PathBuf, +} + +#[test] +fn test_parser_with_default_value() { + assert_eq!( + DefaultedOpt { + bytes: b"E\xc2\xb2=p\xc2\xb2c\xc2\xb2+m\xc2\xb2c\xe2\x81\xb4".to_vec(), + integer: 9000, + path: PathBuf::from("src/lib.rs"), + }, + DefaultedOpt::try_parse_from(&[ + "test", + "-b", + "E²=p²c²+m²c⁴", + "-i", + "9000", + "-p", + "src/lib.rs", + ]) + .unwrap() + ); +} + +#[derive(PartialEq, Debug)] +struct Foo(u8); + +fn foo(value: u64) -> Foo { + Foo(value as u8) +} + +#[derive(Parser, PartialEq, Debug)] +struct Occurrences { + #[clap(short, long, parse(from_occurrences))] + signed: i32, + + #[clap(short, parse(from_occurrences))] + little_signed: i8, + + #[clap(short, parse(from_occurrences))] + unsigned: usize, + + #[clap(short = 'r', parse(from_occurrences))] + little_unsigned: u8, + + #[clap(short, long, parse(from_occurrences = foo))] + custom: Foo, +} + +#[test] +fn test_parser_occurrences() { + assert_eq!( + Occurrences { + signed: 3, + little_signed: 1, + unsigned: 0, + little_unsigned: 4, + custom: Foo(5), + }, + Occurrences::try_parse_from(&[ + "test", "-s", "--signed", "--signed", "-l", "-rrrr", "-cccc", "--custom", + ]) + .unwrap() + ); +} + +#[test] +fn test_custom_bool() { + fn parse_bool(s: &str) -> Result { + match s { + "true" => Ok(true), + "false" => Ok(false), + _ => Err(format!("invalid bool {}", s)), + } + } + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, parse(try_from_str = parse_bool))] + debug: bool, + #[clap( + short, + default_value = "false", + parse(try_from_str = parse_bool) + )] + verbose: bool, + #[clap(short, parse(try_from_str = parse_bool))] + tribool: Option, + #[clap(short, parse(try_from_str = parse_bool), multiple_occurrences(true))] + bitset: Vec, + } + + assert!(Opt::try_parse_from(&["test"]).is_err()); + assert!(Opt::try_parse_from(&["test", "-d"]).is_err()); + assert!(Opt::try_parse_from(&["test", "-dfoo"]).is_err()); + assert_eq!( + Opt { + debug: false, + verbose: false, + tribool: None, + bitset: vec![], + }, + Opt::try_parse_from(&["test", "-dfalse"]).unwrap() + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: None, + bitset: vec![], + }, + Opt::try_parse_from(&["test", "-dtrue"]).unwrap() + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: None, + bitset: vec![], + }, + Opt::try_parse_from(&["test", "-dtrue", "-vfalse"]).unwrap() + ); + assert_eq!( + Opt { + debug: true, + verbose: true, + tribool: None, + bitset: vec![], + }, + Opt::try_parse_from(&["test", "-dtrue", "-vtrue"]).unwrap() + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: Some(false), + bitset: vec![], + }, + Opt::try_parse_from(&["test", "-dtrue", "-tfalse"]).unwrap() + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: Some(true), + bitset: vec![], + }, + Opt::try_parse_from(&["test", "-dtrue", "-ttrue"]).unwrap() + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: None, + bitset: vec![false, true, false, false], + }, + Opt::try_parse_from(&["test", "-dtrue", "-bfalse", "-btrue", "-bfalse", "-bfalse"]) + .unwrap() + ); +} + +#[test] +fn test_cstring() { + #[derive(Parser)] + struct Opt { + #[clap(parse(try_from_str = CString::new))] + c_string: CString, + } + + assert!(Opt::try_parse_from(&["test"]).is_err()); + assert_eq!( + Opt::try_parse_from(&["test", "bla"]) + .unwrap() + .c_string + .to_bytes(), + b"bla" + ); + assert!(Opt::try_parse_from(&["test", "bla\0bla"]).is_err()); +} diff --git a/tests/derive/next/default_value.rs b/tests/derive/next/default_value.rs new file mode 100644 index 00000000000..fc07e4c1128 --- /dev/null +++ b/tests/derive/next/default_value.rs @@ -0,0 +1,57 @@ +use clap::{CommandFactory, Parser}; + +use crate::utils; + +#[test] +fn default_value() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_parser, default_value = "3")] + arg: i32, + } + assert_eq!(Opt { arg: 3 }, Opt::try_parse_from(&["test"]).unwrap()); + assert_eq!(Opt { arg: 1 }, Opt::try_parse_from(&["test", "1"]).unwrap()); + + let help = utils::get_long_help::(); + assert!(help.contains("[default: 3]")); +} + +#[test] +fn default_value_t() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_parser, default_value_t = 3)] + arg: i32, + } + assert_eq!(Opt { arg: 3 }, Opt::try_parse_from(&["test"]).unwrap()); + assert_eq!(Opt { arg: 1 }, Opt::try_parse_from(&["test", "1"]).unwrap()); + + let help = utils::get_long_help::(); + assert!(help.contains("[default: 3]")); +} + +#[test] +fn auto_default_value_t() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_parser, default_value_t)] + arg: i32, + } + assert_eq!(Opt { arg: 0 }, Opt::try_parse_from(&["test"]).unwrap()); + assert_eq!(Opt { arg: 1 }, Opt::try_parse_from(&["test", "1"]).unwrap()); + + let help = utils::get_long_help::(); + assert!(help.contains("[default: 0]")); +} + +#[test] +fn detect_os_variant() { + #![allow(unused_parens)] // needed for `as_ref` call + + #[derive(clap::Parser)] + pub struct Options { + #[clap(value_parser, default_value_os = ("123".as_ref()))] + x: String, + } + Options::command().debug_assert(); +} diff --git a/tests/derive/next/deny_warnings.rs b/tests/derive/next/deny_warnings.rs new file mode 100644 index 00000000000..6e05b9277cc --- /dev/null +++ b/tests/derive/next/deny_warnings.rs @@ -0,0 +1,53 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// This work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +#![deny(warnings)] + +use clap::Parser; + +fn try_str(s: &str) -> Result { + Ok(s.into()) +} + +#[test] +fn warning_never_struct() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(value_parser = try_str, default_value_t)] + s: String, + } + assert_eq!( + Opt { + s: "foo".to_string() + }, + Opt::try_parse_from(&["test", "foo"]).unwrap() + ); +} + +#[test] +fn warning_never_enum() { + #[derive(Parser, Debug, PartialEq)] + enum Opt { + Foo { + #[clap(value_parser = try_str, default_value_t)] + s: String, + }, + } + assert_eq!( + Opt::Foo { + s: "foo".to_string() + }, + Opt::try_parse_from(&["test", "foo", "foo"]).unwrap() + ); +} diff --git a/tests/derive/next/doc_comments_help.rs b/tests/derive/next/doc_comments_help.rs new file mode 100644 index 00000000000..e6aab515ce8 --- /dev/null +++ b/tests/derive/next/doc_comments_help.rs @@ -0,0 +1,248 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// This work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +use crate::utils; + +use clap::{CommandFactory, Parser, ValueEnum}; + +#[test] +fn doc_comments() { + /// Lorem ipsum + #[derive(Parser, PartialEq, Debug)] + struct LoremIpsum { + /// Fooify a bar + /// and a baz + #[clap(short, long, action)] + foo: bool, + } + + let help = utils::get_long_help::(); + assert!(help.contains("Lorem ipsum")); + assert!(help.contains("Fooify a bar and a baz")); +} + +#[test] +fn help_is_better_than_comments() { + /// Lorem ipsum + #[derive(Parser, PartialEq, Debug)] + #[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", + action + )] + foo: bool, + } + + let help = utils::get_long_help::(); + assert!(help.contains("Dolor sit amet")); + assert!(!help.contains("Lorem ipsum")); + assert!(help.contains("DO NOT PASS A BAR")); +} + +#[test] +fn empty_line_in_doc_comment_is_double_linefeed() { + /// Foo. + /// + /// Bar + #[derive(Parser, PartialEq, Debug)] + #[clap(name = "lorem-ipsum")] + struct LoremIpsum {} + + let help = utils::get_long_help::(); + assert!(help.starts_with("lorem-ipsum \nFoo.\n\nBar\n\nUSAGE:")); +} + +#[test] +fn field_long_doc_comment_both_help_long_help() { + /// Lorem ipsumclap + #[derive(Parser, PartialEq, Debug)] + #[clap(name = "lorem-ipsum", about = "Dolor sit amet")] + struct LoremIpsum { + /// Dot is removed from multiline comments. + /// + /// Long help + #[clap(long, action)] + foo: bool, + + /// Dot is removed from one short comment. + #[clap(long, action)] + bar: bool, + } + + let short_help = utils::get_help::(); + let long_help = utils::get_long_help::(); + + assert!(short_help.contains("Dot is removed from one short comment")); + assert!(!short_help.contains("Dot is removed from one short comment.")); + assert!(short_help.contains("Dot is removed from multiline comments")); + assert!(!short_help.contains("Dot is removed from multiline comments.")); + assert!(long_help.contains("Long help")); + assert!(!short_help.contains("Long help")); +} + +#[test] +fn top_long_doc_comment_both_help_long_help() { + /// Lorem ipsumclap + #[derive(Parser, Debug)] + #[clap(name = "lorem-ipsum", about = "Dolor sit amet")] + struct LoremIpsum { + #[clap(subcommand)] + foo: SubCommand, + } + + #[derive(Parser, Debug)] + pub enum SubCommand { + /// DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES + /// + /// Or something else + Foo { + #[clap(value_parser, help = "foo")] + bars: String, + }, + } + + let short_help = utils::get_help::(); + let long_help = utils::get_subcommand_long_help::("foo"); + + assert!(!short_help.contains("Or something else")); + assert!(long_help.contains("DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")); + assert!(long_help.contains("Or something else")); +} + +#[test] +fn verbatim_doc_comment() { + /// DANCE! + /// + /// () + /// | + /// ( () ) + /// ) ________ // ) + /// () |\ \ // + /// ( \\__ \ ______\// + /// \__) | | + /// | | | + /// \ | | + /// \|_______| + /// // \\ + /// (( || + /// \\ || + /// ( () || + /// ( () ) ) + #[derive(Parser, Debug)] + #[clap(verbatim_doc_comment)] + struct SeeFigure1 { + #[clap(long, action)] + foo: bool, + } + + let help = utils::get_long_help::(); + let sample = r#" + () + | + ( () ) + ) ________ // ) + () |\ \ // +( \\__ \ ______\// + \__) | | + | | | + \ | | + \|_______| + // \\ + (( || + \\ || + ( () || + ( () ) )"#; + + assert!(help.contains(sample)) +} + +#[test] +fn verbatim_doc_comment_field() { + #[derive(Parser, Debug)] + struct Command { + /// This help ends in a period. + #[clap(long, verbatim_doc_comment, action)] + foo: bool, + /// This help does not end in a period. + #[clap(long, action)] + bar: bool, + } + + let help = utils::get_long_help::(); + + assert!(help.contains("This help ends in a period.")); + assert!(help.contains("This help does not end in a period")); +} + +#[test] +fn multiline_separates_default() { + #[derive(Parser, Debug)] + struct Command { + /// Multiline + /// + /// Doc comment + #[clap(long, default_value = "x", value_parser)] + x: String, + } + + let help = utils::get_long_help::(); + assert!(!help.contains("Doc comment [default")); + assert!(help.lines().any(|s| s.trim().starts_with("[default"))); + + // The short help should still have the default on the same line + let help = utils::get_help::(); + assert!(help.contains("Multiline [default")); +} + +#[test] +fn argenum_multiline_doc_comment() { + #[derive(ValueEnum, Clone)] + enum LoremIpsum { + /// Multiline + /// + /// Doc comment + Bar, + } +} + +#[test] +fn doc_comment_about_handles_both_abouts() { + /// Opts doc comment summary + #[derive(Parser, Debug)] + pub struct Opts { + #[clap(subcommand)] + pub cmd: Sub, + } + + /// Sub doc comment summary + /// + /// Sub doc comment body + #[derive(Parser, PartialEq, Eq, Debug)] + pub enum Sub { + Compress { + #[clap(value_parser)] + output: String, + }, + } + + let cmd = Opts::command(); + assert_eq!(cmd.get_about(), Some("Opts doc comment summary")); + // clap will fallback to `about` on `None`. The main care about is not providing a `Sub` doc + // comment. + assert_eq!(cmd.get_long_about(), None); +} diff --git a/tests/derive/next/explicit_name_no_renaming.rs b/tests/derive/next/explicit_name_no_renaming.rs new file mode 100644 index 00000000000..15402e85ab4 --- /dev/null +++ b/tests/derive/next/explicit_name_no_renaming.rs @@ -0,0 +1,36 @@ +use crate::utils; + +use clap::Parser; + +#[test] +fn explicit_short_long_no_rename() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short = '.', long = ".foo", value_parser)] + foo: String, + } + + assert_eq!( + Opt { foo: "long".into() }, + Opt::try_parse_from(&["test", "--.foo", "long"]).unwrap() + ); + + assert_eq!( + Opt { + foo: "short".into(), + }, + Opt::try_parse_from(&["test", "-.", "short"]).unwrap() + ); +} + +#[test] +fn explicit_name_no_rename() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(name = ".options", value_parser)] + foo: String, + } + + let help = utils::get_long_help::(); + assert!(help.contains("<.options>")) +} diff --git a/tests/derive/next/flags.rs b/tests/derive/next/flags.rs new file mode 100644 index 00000000000..d8e55290d0a --- /dev/null +++ b/tests/derive/next/flags.rs @@ -0,0 +1,232 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// This work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +#![allow(deprecated)] + +use clap::Parser; + +#[test] +fn bool_type_is_flag() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, long, action)] + alice: bool, + } + + assert_eq!( + Opt { alice: false }, + Opt::try_parse_from(&["test"]).unwrap() + ); + assert_eq!( + 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()); +} + +#[test] +fn count() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, long, action = clap::ArgAction::Count)] + alice: u8, + #[clap(short, long, action = clap::ArgAction::Count)] + bob: u8, + } + + assert_eq!( + Opt { alice: 0, bob: 0 }, + Opt::try_parse_from(&["test"]).unwrap() + ); + assert_eq!( + Opt { alice: 1, bob: 0 }, + Opt::try_parse_from(&["test", "-a"]).unwrap() + ); + assert_eq!( + Opt { alice: 2, bob: 0 }, + Opt::try_parse_from(&["test", "-a", "-a"]).unwrap() + ); + assert_eq!( + Opt { alice: 2, bob: 2 }, + Opt::try_parse_from(&["test", "-a", "--alice", "-bb"]).unwrap() + ); + assert_eq!( + Opt { alice: 3, bob: 1 }, + Opt::try_parse_from(&["test", "-aaa", "--bob"]).unwrap() + ); + assert!(Opt::try_parse_from(&["test", "-i"]).is_err()); + assert!(Opt::try_parse_from(&["test", "-a", "foo"]).is_err()); +} + +#[test] +fn non_bool_type_flag() { + fn parse_from_flag(b: bool) -> std::sync::atomic::AtomicBool { + std::sync::atomic::AtomicBool::new(b) + } + + #[derive(Parser, Debug)] + struct Opt { + #[clap(short, long, parse(from_flag = parse_from_flag))] + alice: std::sync::atomic::AtomicBool, + #[clap(short, long, parse(from_flag))] + bob: std::sync::atomic::AtomicBool, + } + + let falsey = Opt::try_parse_from(&["test"]).unwrap(); + assert!(!falsey.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(!falsey.bob.load(std::sync::atomic::Ordering::Relaxed)); + + let alice = Opt::try_parse_from(&["test", "-a"]).unwrap(); + assert!(alice.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(!alice.bob.load(std::sync::atomic::Ordering::Relaxed)); + + let bob = Opt::try_parse_from(&["test", "-b"]).unwrap(); + assert!(!bob.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(bob.bob.load(std::sync::atomic::Ordering::Relaxed)); + + let both = Opt::try_parse_from(&["test", "-b", "-a"]).unwrap(); + assert!(both.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(both.bob.load(std::sync::atomic::Ordering::Relaxed)); +} + +#[test] +fn mixed_type_flags() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, long, action)] + alice: bool, + #[clap(short, long, action = clap::ArgAction::Count)] + bob: u8, + } + + assert_eq!( + Opt { + alice: false, + bob: 0 + }, + Opt::try_parse_from(&["test"]).unwrap() + ); + assert_eq!( + Opt { + alice: true, + bob: 0 + }, + Opt::try_parse_from(&["test", "-a"]).unwrap() + ); + assert_eq!( + Opt { + alice: true, + bob: 0 + }, + Opt::try_parse_from(&["test", "-a"]).unwrap() + ); + assert_eq!( + Opt { + alice: false, + bob: 1 + }, + Opt::try_parse_from(&["test", "-b"]).unwrap() + ); + assert_eq!( + Opt { + alice: true, + bob: 1 + }, + Opt::try_parse_from(&["test", "--alice", "--bob"]).unwrap() + ); + assert_eq!( + Opt { + alice: true, + bob: 4 + }, + Opt::try_parse_from(&["test", "-bb", "-a", "-bb"]).unwrap() + ); +} + +#[test] +fn ignore_qualified_bool_type() { + mod inner { + #[allow(non_camel_case_types)] + #[derive(PartialEq, Debug, Clone)] + pub struct bool(pub String); + + impl std::str::FromStr for self::bool { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(self::bool(s.into())) + } + } + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(action)] + arg: inner::bool, + } + + assert_eq!( + Opt { + arg: inner::bool("success".into()) + }, + Opt::try_parse_from(&["test", "success"]).unwrap() + ); +} + +#[test] +fn override_implicit_action() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(long, action = clap::ArgAction::Set)] + arg: bool, + } + + assert_eq!( + Opt { arg: false }, + Opt::try_parse_from(&["test", "--arg", "false"]).unwrap() + ); + + assert_eq!( + Opt { arg: true }, + Opt::try_parse_from(&["test", "--arg", "true"]).unwrap() + ); +} + +#[test] +fn override_implicit_from_flag_positional() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(action = clap::ArgAction::Set)] + arg: bool, + } + + assert_eq!( + Opt { arg: false }, + Opt::try_parse_from(&["test", "false"]).unwrap() + ); + + assert_eq!( + Opt { arg: true }, + Opt::try_parse_from(&["test", "true"]).unwrap() + ); +} diff --git a/tests/derive/next/flatten.rs b/tests/derive/next/flatten.rs new file mode 100644 index 00000000000..df451265baa --- /dev/null +++ b/tests/derive/next/flatten.rs @@ -0,0 +1,264 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// This work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +use crate::utils; + +use clap::{Args, Parser, Subcommand}; + +#[test] +fn flatten() { + #[derive(Args, PartialEq, Debug)] + struct Common { + #[clap(value_parser)] + arg: i32, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(flatten)] + common: Common, + } + assert_eq!( + Opt { + common: Common { arg: 42 } + }, + Opt::try_parse_from(&["test", "42"]).unwrap() + ); + assert!(Opt::try_parse_from(&["test"]).is_err()); + assert!(Opt::try_parse_from(&["test", "42", "24"]).is_err()); +} + +#[cfg(debug_assertions)] +#[test] +#[should_panic] +fn flatten_twice() { + #[derive(Args, PartialEq, Debug)] + struct Common { + #[clap(value_parser)] + arg: i32, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(flatten)] + c1: Common, + // Defines "arg" twice, so this should not work. + #[clap(flatten)] + c2: Common, + } + Opt::try_parse_from(&["test", "42", "43"]).unwrap(); +} + +#[test] +fn flatten_in_subcommand() { + #[derive(Args, PartialEq, Debug)] + struct Common { + #[clap(value_parser)] + arg: i32, + } + + #[derive(Args, PartialEq, Debug)] + struct Add { + #[clap(short, action)] + interactive: bool, + #[clap(flatten)] + common: Common, + } + + #[derive(Parser, PartialEq, Debug)] + enum Opt { + Fetch { + #[clap(short, action)] + all: bool, + #[clap(flatten)] + common: Common, + }, + + Add(Add), + } + + assert_eq!( + Opt::Fetch { + all: false, + common: Common { arg: 42 } + }, + Opt::try_parse_from(&["test", "fetch", "42"]).unwrap() + ); + assert_eq!( + Opt::Add(Add { + interactive: true, + common: Common { arg: 43 } + }), + Opt::try_parse_from(&["test", "add", "-i", "43"]).unwrap() + ); +} + +#[test] +fn update_args_with_flatten() { + #[derive(Args, PartialEq, Debug)] + struct Common { + #[clap(value_parser)] + arg: i32, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(flatten)] + common: Common, + } + + let mut opt = Opt { + common: Common { arg: 42 }, + }; + opt.try_update_from(&["test"]).unwrap(); + assert_eq!(Opt::try_parse_from(&["test", "42"]).unwrap(), opt); + + let mut opt = Opt { + common: Common { arg: 42 }, + }; + opt.try_update_from(&["test", "52"]).unwrap(); + assert_eq!(Opt::try_parse_from(&["test", "52"]).unwrap(), opt); +} + +#[derive(Subcommand, PartialEq, Debug)] +enum BaseCli { + Command1(Command1), +} + +#[derive(Args, PartialEq, Debug)] +struct Command1 { + #[clap(value_parser)] + arg1: i32, + #[clap(value_parser)] + arg2: i32, +} + +#[derive(Args, PartialEq, Debug)] +struct Command2 { + #[clap(value_parser)] + arg2: i32, +} + +#[derive(Parser, PartialEq, Debug)] +enum Opt { + #[clap(flatten)] + BaseCli(BaseCli), + Command2(Command2), +} + +#[test] +fn merge_subcommands_with_flatten() { + assert_eq!( + Opt::BaseCli(BaseCli::Command1(Command1 { arg1: 42, arg2: 44 })), + Opt::try_parse_from(&["test", "command1", "42", "44"]).unwrap() + ); + assert_eq!( + Opt::Command2(Command2 { arg2: 43 }), + Opt::try_parse_from(&["test", "command2", "43"]).unwrap() + ); +} + +#[test] +fn update_subcommands_with_flatten() { + let mut opt = Opt::BaseCli(BaseCli::Command1(Command1 { arg1: 12, arg2: 14 })); + opt.try_update_from(&["test", "command1", "42", "44"]) + .unwrap(); + assert_eq!( + Opt::try_parse_from(&["test", "command1", "42", "44"]).unwrap(), + opt + ); + + let mut opt = Opt::BaseCli(BaseCli::Command1(Command1 { arg1: 12, arg2: 14 })); + opt.try_update_from(&["test", "command1", "42"]).unwrap(); + assert_eq!( + Opt::try_parse_from(&["test", "command1", "42", "14"]).unwrap(), + opt + ); + + let mut opt = Opt::BaseCli(BaseCli::Command1(Command1 { arg1: 12, arg2: 14 })); + opt.try_update_from(&["test", "command2", "43"]).unwrap(); + assert_eq!( + Opt::try_parse_from(&["test", "command2", "43"]).unwrap(), + opt + ); +} + +#[test] +fn flatten_with_doc_comment() { + #[derive(Args, PartialEq, Debug)] + struct Common { + /// This is an arg. Arg means "argument". Command line argument. + #[clap(value_parser)] + arg: i32, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + /// The very important comment that clippy had me put here. + /// It knows better. + #[clap(flatten)] + common: Common, + } + assert_eq!( + Opt { + common: Common { arg: 42 } + }, + Opt::try_parse_from(&["test", "42"]).unwrap() + ); + + let help = utils::get_help::(); + assert!(help.contains("This is an arg.")); + assert!(!help.contains("The very important")); +} + +#[test] +fn docstrings_ordering_with_multiple_clap() { + /// This is the docstring for Flattened + #[derive(Args)] + struct Flattened { + #[clap(long, action)] + foo: bool, + } + + /// This is the docstring for Command + #[derive(Parser)] + struct Command { + #[clap(flatten)] + flattened: Flattened, + } + + let short_help = utils::get_help::(); + + assert!(short_help.contains("This is the docstring for Command")); +} + +#[test] +fn docstrings_ordering_with_multiple_clap_partial() { + /// This is the docstring for Flattened + #[derive(Args)] + struct Flattened { + #[clap(long, action)] + foo: bool, + } + + #[derive(Parser)] + struct Command { + #[clap(flatten)] + flattened: Flattened, + } + + let short_help = utils::get_help::(); + + assert!(short_help.contains("This is the docstring for Flattened")); +} diff --git a/tests/derive/next/generic.rs b/tests/derive/next/generic.rs new file mode 100644 index 00000000000..cea94729083 --- /dev/null +++ b/tests/derive/next/generic.rs @@ -0,0 +1,152 @@ +use clap::{Args, Parser}; + +#[test] +fn generic_struct_flatten() { + #[derive(Args, PartialEq, Debug)] + struct Inner { + #[clap(value_parser)] + pub answer: isize, + } + + #[derive(Parser, PartialEq, Debug)] + struct Outer { + #[clap(flatten)] + pub inner: T, + } + + assert_eq!( + Outer { + inner: Inner { answer: 42 } + }, + Outer::parse_from(&["--answer", "42"]) + ) +} + +#[test] +fn generic_struct_flatten_w_where_clause() { + #[derive(Args, PartialEq, Debug)] + struct Inner { + #[clap(value_parser)] + pub answer: isize, + } + + #[derive(Parser, PartialEq, Debug)] + struct Outer + where + T: Args, + { + #[clap(flatten)] + pub inner: T, + } + + assert_eq!( + Outer { + inner: Inner { answer: 42 } + }, + Outer::parse_from(&["--answer", "42"]) + ) +} + +#[test] +fn generic_enum() { + #[derive(Args, PartialEq, Debug)] + struct Inner { + #[clap(value_parser)] + pub answer: isize, + } + + #[derive(Parser, PartialEq, Debug)] + enum GenericEnum { + Start(T), + Stop, + } + + assert_eq!( + GenericEnum::Start(Inner { answer: 42 }), + GenericEnum::parse_from(&["test", "start", "42"]) + ) +} + +#[test] +fn generic_enum_w_where_clause() { + #[derive(Args, PartialEq, Debug)] + struct Inner { + #[clap(value_parser)] + pub answer: isize, + } + + #[derive(Parser, PartialEq, Debug)] + enum GenericEnum + where + T: Args, + { + Start(T), + Stop, + } + + assert_eq!( + GenericEnum::Start(Inner { answer: 42 }), + GenericEnum::parse_from(&["test", "start", "42"]) + ) +} + +#[test] +fn generic_w_fromstr_trait_bound() { + use std::str::FromStr; + + #[derive(Parser, PartialEq, Debug)] + struct Opt + where + T: FromStr + Send + Sync + Clone + 'static, + ::Err: std::error::Error + Sync + Send + 'static, + { + #[clap(value_parser)] + answer: T, + } + + assert_eq!( + Opt:: { answer: 42 }, + Opt::::parse_from(&["--answer", "42"]) + ) +} + +#[test] +fn generic_wo_trait_bound() { + use std::time::Duration; + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_parser)] + answer: isize, + #[clap(skip)] + took: Option, + } + + assert_eq!( + Opt:: { + answer: 42, + took: None + }, + Opt::::parse_from(&["--answer", "42"]) + ) +} + +#[test] +fn generic_where_clause_w_trailing_comma() { + use std::str::FromStr; + + #[derive(Parser, PartialEq, Debug)] + struct Opt + where + T: FromStr + Send + Sync + Clone + 'static, + ::Err: std::error::Error + Sync + Send + 'static, + { + #[clap(value_parser)] + pub answer: T, + } + + assert_eq!( + Opt:: { answer: 42 }, + Opt::::parse_from(&["--answer", "42"]) + ) +} diff --git a/tests/derive/next/help.rs b/tests/derive/next/help.rs new file mode 100644 index 00000000000..8496248949b --- /dev/null +++ b/tests/derive/next/help.rs @@ -0,0 +1,527 @@ +use clap::{AppSettings, Args, CommandFactory, Parser, Subcommand}; + +#[test] +fn arg_help_heading_applied() { + #[derive(Debug, Clone, Parser)] + struct CliOptions { + #[clap(long, value_parser)] + #[clap(help_heading = Some("HEADING A"))] + should_be_in_section_a: u32, + + #[clap(long, value_parser)] + no_section: u32, + } + + let cmd = CliOptions::command(); + + let should_be_in_section_a = if cfg!(feature = "unstable-v4") { + cmd.get_arguments() + .find(|a| a.get_id() == "should_be_in_section_a") + .unwrap() + } else { + cmd.get_arguments() + .find(|a| a.get_id() == "should-be-in-section-a") + .unwrap() + }; + assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A")); + + let should_be_in_section_b = if cfg!(feature = "unstable-v4") { + cmd.get_arguments() + .find(|a| a.get_id() == "no_section") + .unwrap() + } else { + cmd.get_arguments() + .find(|a| a.get_id() == "no-section") + .unwrap() + }; + assert_eq!(should_be_in_section_b.get_help_heading(), None); +} + +#[test] +fn app_help_heading_applied() { + #[derive(Debug, Clone, Parser)] + #[clap(next_help_heading = "DEFAULT")] + struct CliOptions { + #[clap(long, value_parser)] + #[clap(help_heading = Some("HEADING A"))] + should_be_in_section_a: u32, + + #[clap(long, value_parser)] + should_be_in_default_section: u32, + } + + let cmd = CliOptions::command(); + + let should_be_in_section_a = if cfg!(feature = "unstable-v4") { + cmd.get_arguments() + .find(|a| a.get_id() == "should_be_in_section_a") + .unwrap() + } else { + cmd.get_arguments() + .find(|a| a.get_id() == "should-be-in-section-a") + .unwrap() + }; + assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A")); + + let should_be_in_default_section = if cfg!(feature = "unstable-v4") { + cmd.get_arguments() + .find(|a| a.get_id() == "should_be_in_default_section") + .unwrap() + } else { + cmd.get_arguments() + .find(|a| a.get_id() == "should-be-in-default-section") + .unwrap() + }; + assert_eq!( + should_be_in_default_section.get_help_heading(), + Some("DEFAULT") + ); +} + +#[test] +fn app_help_heading_flattened() { + // Used to help track the cause in tests + #![allow(clippy::enum_variant_names)] + + #[derive(Debug, Clone, Parser)] + struct CliOptions { + #[clap(flatten)] + options_a: OptionsA, + + #[clap(flatten)] + options_b: OptionsB, + + #[clap(subcommand)] + sub_a: SubA, + + #[clap(long, value_parser)] + should_be_in_default_section: u32, + } + + #[derive(Debug, Clone, Args)] + #[clap(next_help_heading = "HEADING A")] + struct OptionsA { + #[clap(long, value_parser)] + should_be_in_section_a: u32, + } + + #[derive(Debug, Clone, Args)] + #[clap(next_help_heading = "HEADING B")] + struct OptionsB { + #[clap(long, value_parser)] + should_be_in_section_b: u32, + } + + #[derive(Debug, Clone, Subcommand)] + enum SubA { + #[clap(flatten)] + SubB(SubB), + #[clap(subcommand)] + SubC(SubC), + SubAOne, + #[clap(next_help_heading = "SUB A")] + SubATwo { + #[clap(value_parser)] + should_be_in_sub_a: u32, + }, + } + + #[derive(Debug, Clone, Subcommand)] + enum SubB { + #[clap(next_help_heading = "SUB B")] + SubBOne { + #[clap(value_parser)] + should_be_in_sub_b: u32, + }, + } + + #[derive(Debug, Clone, Subcommand)] + enum SubC { + #[clap(next_help_heading = "SUB C")] + SubCOne { + #[clap(value_parser)] + should_be_in_sub_c: u32, + }, + } + + let cmd = CliOptions::command(); + + let should_be_in_section_a = if cfg!(feature = "unstable-v4") { + cmd.get_arguments() + .find(|a| a.get_id() == "should_be_in_section_a") + .unwrap() + } else { + cmd.get_arguments() + .find(|a| a.get_id() == "should-be-in-section-a") + .unwrap() + }; + assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A")); + + let should_be_in_section_b = if cfg!(feature = "unstable-v4") { + cmd.get_arguments() + .find(|a| a.get_id() == "should_be_in_section_b") + .unwrap() + } else { + cmd.get_arguments() + .find(|a| a.get_id() == "should-be-in-section-b") + .unwrap() + }; + assert_eq!(should_be_in_section_b.get_help_heading(), Some("HEADING B")); + + let should_be_in_default_section = if cfg!(feature = "unstable-v4") { + cmd.get_arguments() + .find(|a| a.get_id() == "should_be_in_default_section") + .unwrap() + } else { + cmd.get_arguments() + .find(|a| a.get_id() == "should-be-in-default-section") + .unwrap() + }; + assert_eq!(should_be_in_default_section.get_help_heading(), None); + + let sub_a_two = cmd.find_subcommand("sub-a-two").unwrap(); + + let should_be_in_sub_a = if cfg!(feature = "unstable-v4") { + sub_a_two + .get_arguments() + .find(|a| a.get_id() == "should_be_in_sub_a") + .unwrap() + } else { + sub_a_two + .get_arguments() + .find(|a| a.get_id() == "should-be-in-sub-a") + .unwrap() + }; + assert_eq!(should_be_in_sub_a.get_help_heading(), Some("SUB A")); + + let sub_b_one = cmd.find_subcommand("sub-b-one").unwrap(); + + let should_be_in_sub_b = if cfg!(feature = "unstable-v4") { + sub_b_one + .get_arguments() + .find(|a| a.get_id() == "should_be_in_sub_b") + .unwrap() + } else { + sub_b_one + .get_arguments() + .find(|a| a.get_id() == "should-be-in-sub-b") + .unwrap() + }; + assert_eq!(should_be_in_sub_b.get_help_heading(), Some("SUB B")); + + let sub_c = cmd.find_subcommand("sub-c").unwrap(); + let sub_c_one = sub_c.find_subcommand("sub-c-one").unwrap(); + + let should_be_in_sub_c = if cfg!(feature = "unstable-v4") { + sub_c_one + .get_arguments() + .find(|a| a.get_id() == "should_be_in_sub_c") + .unwrap() + } else { + sub_c_one + .get_arguments() + .find(|a| a.get_id() == "should-be-in-sub-c") + .unwrap() + }; + assert_eq!(should_be_in_sub_c.get_help_heading(), Some("SUB C")); +} + +#[test] +fn flatten_field_with_help_heading() { + #[derive(Debug, Clone, Parser)] + struct CliOptions { + #[clap(flatten)] + #[clap(next_help_heading = "HEADING A")] + options_a: OptionsA, + } + + #[derive(Debug, Clone, Args)] + struct OptionsA { + #[clap(long, value_parser)] + should_be_in_section_a: u32, + } + + let cmd = CliOptions::command(); + + let should_be_in_section_a = if cfg!(feature = "unstable-v4") { + cmd.get_arguments() + .find(|a| a.get_id() == "should_be_in_section_a") + .unwrap() + } else { + cmd.get_arguments() + .find(|a| a.get_id() == "should-be-in-section-a") + .unwrap() + }; + assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A")); +} + +// The challenge with this test is creating an error situation not caught by `clap`'s error checking +// but by the code that `clap_derive` generates. +// +// Ultimately, the easiest way to confirm is to put a debug statement in the desired error path. +#[test] +fn derive_generated_error_has_full_context() { + #[derive(Debug, Parser)] + #[clap(subcommand_negates_reqs = true)] + struct Opts { + #[clap(long, value_parser)] + req_str: String, + + #[clap(subcommand)] + cmd: Option, + } + + #[derive(Debug, Parser)] + enum SubCommands { + Sub { + #[clap(short, long, action = clap::ArgAction::Count)] + verbose: u8, + }, + } + + let result = Opts::try_parse_from(&["test", "sub"]); + assert!( + result.is_err(), + "`SubcommandsNegateReqs` with non-optional `req_str` should fail: {:?}", + result.unwrap() + ); + + if cfg!(feature = "unstable-v4") { + let expected = r#"error: The following required argument was not provided: req_str + +USAGE: + clap --req-str + clap + +For more information try --help +"#; + assert_eq!(result.unwrap_err().to_string(), expected); + } else { + let expected = r#"error: The following required argument was not provided: req-str + +USAGE: + clap --req-str + clap + +For more information try --help +"#; + assert_eq!(result.unwrap_err().to_string(), expected); + } +} + +#[test] +fn derive_order_next_order() { + static HELP: &str = "test 1.2 + +USAGE: + test [OPTIONS] + +OPTIONS: + --flag-b first flag + --option-b first option + -h, --help Print help information + -V, --version Print version information + --flag-a second flag + --option-a second option +"; + + #[derive(Parser, Debug)] + #[clap(name = "test", version = "1.2")] + #[clap(setting = AppSettings::DeriveDisplayOrder)] + struct Args { + #[clap(flatten)] + a: A, + #[clap(flatten)] + b: B, + } + + #[derive(Args, Debug)] + #[clap(next_display_order = 10000)] + struct A { + /// second flag + #[clap(long, action)] + flag_a: bool, + /// second option + #[clap(long, value_parser)] + option_a: Option, + } + + #[derive(Args, Debug)] + #[clap(next_display_order = 10)] + struct B { + /// first flag + #[clap(long, action)] + flag_b: bool, + /// first option + #[clap(long, value_parser)] + option_b: Option, + } + + use clap::CommandFactory; + let mut cmd = Args::command(); + + let mut buffer: Vec = Default::default(); + cmd.write_help(&mut buffer).unwrap(); + let help = String::from_utf8(buffer).unwrap(); + snapbox::assert_eq(HELP, help); +} + +#[test] +fn derive_order_next_order_flatten() { + static HELP: &str = "test 1.2 + +USAGE: + test [OPTIONS] + +OPTIONS: + --flag-b first flag + --option-b first option + -h, --help Print help information + -V, --version Print version information + --flag-a second flag + --option-a second option +"; + + #[derive(Parser, Debug)] + #[clap(setting = AppSettings::DeriveDisplayOrder)] + #[clap(name = "test", version = "1.2")] + struct Args { + #[clap(flatten)] + #[clap(next_display_order = 10000)] + a: A, + #[clap(flatten)] + #[clap(next_display_order = 10)] + b: B, + } + + #[derive(Args, Debug)] + struct A { + /// second flag + #[clap(long, action)] + flag_a: bool, + /// second option + #[clap(long, value_parser)] + option_a: Option, + } + + #[derive(Args, Debug)] + struct B { + /// first flag + #[clap(long, action)] + flag_b: bool, + /// first option + #[clap(long, value_parser)] + option_b: Option, + } + + use clap::CommandFactory; + let mut cmd = Args::command(); + + let mut buffer: Vec = Default::default(); + cmd.write_help(&mut buffer).unwrap(); + let help = String::from_utf8(buffer).unwrap(); + snapbox::assert_eq(HELP, help); +} + +#[test] +fn derive_order_no_next_order() { + static HELP: &str = "test 1.2 + +USAGE: + test [OPTIONS] + +OPTIONS: + --flag-a first flag + --flag-b second flag + -h, --help Print help information + --option-a first option + --option-b second option + -V, --version Print version information +"; + + #[derive(Parser, Debug)] + #[clap(name = "test", version = "1.2")] + #[clap(setting = AppSettings::DeriveDisplayOrder)] + #[clap(next_display_order = None)] + struct Args { + #[clap(flatten)] + a: A, + #[clap(flatten)] + b: B, + } + + #[derive(Args, Debug)] + struct A { + /// first flag + #[clap(long, action)] + flag_a: bool, + /// first option + #[clap(long, value_parser)] + option_a: Option, + } + + #[derive(Args, Debug)] + struct B { + /// second flag + #[clap(long, action)] + flag_b: bool, + /// second option + #[clap(long, value_parser)] + option_b: Option, + } + + use clap::CommandFactory; + let mut cmd = Args::command(); + + let mut buffer: Vec = Default::default(); + cmd.write_help(&mut buffer).unwrap(); + let help = String::from_utf8(buffer).unwrap(); + snapbox::assert_eq(HELP, help); +} + +#[test] +#[cfg(feature = "unstable-v4")] +fn derive_possible_value_help() { + static HELP: &str = "clap +Application help + +USAGE: + clap + +ARGS: + + Argument help + + Possible values: + - foo: Foo help + - bar: Bar help + +OPTIONS: + -h, --help + Print help information +"; + + /// Application help + #[derive(Parser, PartialEq, Debug)] + struct Args { + /// Argument help + #[clap(value_enum, value_parser)] + arg: ArgChoice, + } + + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + enum ArgChoice { + /// Foo help + Foo, + /// Bar help + Bar, + } + + use clap::CommandFactory; + let mut cmd = Args::command(); + + let mut buffer: Vec = Default::default(); + cmd.write_long_help(&mut buffer).unwrap(); + let help = String::from_utf8(buffer).unwrap(); + snapbox::assert_eq(HELP, help); +} diff --git a/tests/derive/next/issues.rs b/tests/derive/next/issues.rs new file mode 100644 index 00000000000..22c93c87172 --- /dev/null +++ b/tests/derive/next/issues.rs @@ -0,0 +1,132 @@ +// https://github.com/TeXitoi/structopt/issues/{NUMBER} + +use crate::utils; + +use clap::{ArgGroup, Args, Parser, Subcommand}; + +#[test] +fn issue_151_groups_within_subcommands() { + #[derive(Args, Debug)] + #[clap(group = ArgGroup::new("verb").required(true).multiple(true))] + struct Opt { + #[clap(long, group = "verb", value_parser)] + foo: Option, + #[clap(long, group = "verb", value_parser)] + bar: Option, + } + + #[derive(Debug, Parser)] + struct Cli { + #[clap(flatten)] + a: Opt, + } + + assert!(Cli::try_parse_from(&["test"]).is_err()); + 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] +fn issue_289() { + #[derive(Parser)] + #[clap(infer_subcommands = true)] + enum Args { + SomeCommand { + #[clap(subcommand)] + sub: SubSubCommand, + }, + AnotherCommand, + } + + #[derive(Subcommand)] + #[clap(infer_subcommands = true)] + enum SubSubCommand { + TestCommand, + } + + assert!(Args::try_parse_from(&["test", "some-command", "test-command"]).is_ok()); + assert!(Args::try_parse_from(&["test", "some", "test-command"]).is_ok()); + assert!(Args::try_parse_from(&["test", "some-command", "test"]).is_ok()); + assert!(Args::try_parse_from(&["test", "some", "test"]).is_ok()); +} + +#[test] +fn issue_324() { + fn my_version() -> &'static str { + "MY_VERSION" + } + + #[derive(Parser)] + #[clap(version = my_version())] + struct Opt { + #[clap(subcommand)] + _cmd: SubCommand, + } + + #[derive(Subcommand)] + enum SubCommand { + Start, + } + + let help = utils::get_long_help::(); + assert!(help.contains("MY_VERSION")); +} + +#[test] +fn issue_418() { + #[derive(Debug, Parser)] + struct Opts { + #[clap(subcommand)] + /// The command to run + command: Command, + } + + #[derive(Debug, Subcommand)] + enum Command { + /// Reticulate the splines + #[clap(visible_alias = "ret")] + Reticulate { + /// How many splines + #[clap(value_parser)] + num_splines: u8, + }, + /// Frobnicate the rest + #[clap(visible_alias = "frob")] + Frobnicate, + } + + let help = utils::get_long_help::(); + assert!(help.contains("Reticulate the splines [aliases: ret]")); +} + +#[test] +fn issue_490() { + use clap::Parser; + use std::iter::FromIterator; + use std::str::FromStr; + + struct U16ish; + impl FromStr for U16ish { + type Err = (); + fn from_str(_: &str) -> Result { + unimplemented!() + } + } + impl<'a> FromIterator<&'a U16ish> for Vec { + fn from_iter>(_: T) -> Self { + unimplemented!() + } + } + + #[derive(Parser, Debug)] + struct Opt { + #[clap(value_parser)] + opt_vec: Vec, + #[clap(long, value_parser)] + opt_opt_vec: Option>, + } + + // Assert that it compiles +} diff --git a/tests/derive/next/macros.rs b/tests/derive/next/macros.rs new file mode 100644 index 00000000000..5dbec71a39c --- /dev/null +++ b/tests/derive/next/macros.rs @@ -0,0 +1,53 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// This work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +use clap::Parser; + +// Tests that clap_derive properly detects an `Option` field +// that results from a macro expansion +#[test] +fn use_option() { + macro_rules! expand_ty { + ($name:ident: $ty:ty) => { + #[derive(Parser)] + struct Outer { + #[clap(short, long, value_parser)] + #[allow(dead_code)] + $name: $ty, + } + }; + } + + expand_ty!(my_field: Option); +} + +#[test] +fn issue_447() { + macro_rules! Command { + ( $name:ident, [ + #[$meta:meta] $var:ident($inner:ty) + ] ) => { + #[derive(Debug, PartialEq, clap::Parser)] + enum $name { + #[$meta] + $var($inner), + } + }; + } + + Command! {GitCmd, [ + #[clap(external_subcommand)] + Ext(Vec) + ]} +} diff --git a/tests/derive/next/mod.rs b/tests/derive/next/mod.rs new file mode 100644 index 00000000000..ced022ec408 --- /dev/null +++ b/tests/derive/next/mod.rs @@ -0,0 +1,34 @@ +#![cfg(feature = "derive")] +#![cfg(feature = "unstable-v4")] + +mod app_name; +mod arguments; +mod author_version_about; +mod basic; +mod boxed; +mod custom_string_parsers; +mod default_value; +mod deny_warnings; +mod doc_comments_help; +mod explicit_name_no_renaming; +mod flags; +mod flatten; +mod generic; +mod help; +mod issues; +mod macros; +mod naming; +mod nested_subcommands; +mod non_literal_attributes; +mod options; +mod privacy; +mod raw_bool_literal; +mod raw_idents; +mod rename_all_env; +mod skip; +mod structopt; +mod subcommands; +mod type_alias_regressions; +mod utf8; +mod utils; +mod value_enum; diff --git a/tests/derive/next/naming.rs b/tests/derive/next/naming.rs new file mode 100644 index 00000000000..d0b897f7e89 --- /dev/null +++ b/tests/derive/next/naming.rs @@ -0,0 +1,360 @@ +use clap::Parser; + +#[test] +fn test_standalone_long_generates_kebab_case() { + #[derive(Parser, Debug, PartialEq)] + #[allow(non_snake_case)] + struct Opt { + #[clap(long, action)] + FOO_OPTION: bool, + } + + assert_eq!( + Opt { FOO_OPTION: true }, + Opt::try_parse_from(&["test", "--foo-option"]).unwrap() + ); +} + +#[test] +fn test_custom_long_overwrites_default_name() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(long = "foo", action)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::try_parse_from(&["test", "--foo"]).unwrap() + ); +} + +#[test] +fn test_standalone_long_uses_previous_defined_custom_name() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(name = "foo", long, action)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::try_parse_from(&["test", "--foo"]).unwrap() + ); +} + +#[test] +fn test_standalone_long_ignores_afterwards_defined_custom_name() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(long, name = "foo", action)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::try_parse_from(&["test", "--foo-option"]).unwrap() + ); +} + +#[test] +fn test_standalone_short_generates_kebab_case() { + #[derive(Parser, Debug, PartialEq)] + #[allow(non_snake_case)] + struct Opt { + #[clap(short, action)] + FOO_OPTION: bool, + } + + assert_eq!( + Opt { FOO_OPTION: true }, + Opt::try_parse_from(&["test", "-f"]).unwrap() + ); +} + +#[test] +fn test_custom_short_overwrites_default_name() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(short = 'o', action)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::try_parse_from(&["test", "-o"]).unwrap() + ); +} + +#[test] +fn test_standalone_short_uses_previous_defined_custom_name() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(name = "option", short, action)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::try_parse_from(&["test", "-o"]).unwrap() + ); +} + +#[test] +fn test_standalone_short_ignores_afterwards_defined_custom_name() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(short, name = "option", action)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::try_parse_from(&["test", "-f"]).unwrap() + ); +} + +#[test] +fn test_standalone_long_uses_previous_defined_casing() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(rename_all = "screaming_snake", long, action)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::try_parse_from(&["test", "--FOO_OPTION"]).unwrap() + ); +} + +#[test] +fn test_standalone_short_uses_previous_defined_casing() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(rename_all = "screaming_snake", short, action)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::try_parse_from(&["test", "-F"]).unwrap() + ); +} + +#[test] +fn test_standalone_long_works_with_verbatim_casing() { + #[derive(Parser, Debug, PartialEq)] + #[allow(non_snake_case)] + struct Opt { + #[clap(rename_all = "verbatim", long, action)] + _fOO_oPtiON: bool, + } + + assert_eq!( + Opt { _fOO_oPtiON: true }, + Opt::try_parse_from(&["test", "--_fOO_oPtiON"]).unwrap() + ); +} + +#[test] +fn test_standalone_short_works_with_verbatim_casing() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(rename_all = "verbatim", short, action)] + _foo: bool, + } + + assert_eq!( + Opt { _foo: true }, + Opt::try_parse_from(&["test", "-_"]).unwrap() + ); +} + +#[test] +fn test_rename_all_is_propagated_from_struct_to_fields() { + #[derive(Parser, Debug, PartialEq)] + #[clap(rename_all = "screaming_snake")] + struct Opt { + #[clap(long, action)] + foo: bool, + } + + assert_eq!( + Opt { foo: true }, + Opt::try_parse_from(&["test", "--FOO"]).unwrap() + ); +} + +#[test] +fn test_rename_all_is_not_propagated_from_struct_into_flattened() { + #[derive(Parser, Debug, PartialEq)] + #[clap(rename_all = "screaming_snake")] + struct Opt { + #[clap(flatten)] + foo: Foo, + } + + #[derive(Parser, Debug, PartialEq)] + struct Foo { + #[clap(long, action)] + foo: bool, + } + + assert_eq!( + Opt { + foo: Foo { foo: true } + }, + Opt::try_parse_from(&["test", "--foo"]).unwrap() + ); +} + +#[test] +fn test_lower_is_renamed() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(rename_all = "lower", long, action)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::try_parse_from(&["test", "--foooption"]).unwrap() + ); +} + +#[test] +fn test_upper_is_renamed() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(rename_all = "upper", long, action)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::try_parse_from(&["test", "--FOOOPTION"]).unwrap() + ); +} + +#[test] +fn test_single_word_enum_variant_is_default_renamed_into_kebab_case() { + #[derive(Parser, Debug, PartialEq)] + enum Opt { + Command { + #[clap(value_parser)] + foo: u32, + }, + } + + assert_eq!( + Opt::Command { foo: 0 }, + Opt::try_parse_from(&["test", "command", "0"]).unwrap() + ); +} + +#[test] +fn test_multi_word_enum_variant_is_renamed() { + #[derive(Parser, Debug, PartialEq)] + enum Opt { + FirstCommand { + #[clap(value_parser)] + foo: u32, + }, + } + + assert_eq!( + Opt::FirstCommand { foo: 0 }, + Opt::try_parse_from(&["test", "first-command", "0"]).unwrap() + ); +} + +#[test] +fn test_rename_all_is_not_propagated_from_struct_into_subcommand() { + #[derive(Parser, Debug, PartialEq)] + #[clap(rename_all = "screaming_snake")] + struct Opt { + #[clap(subcommand)] + foo: Foo, + } + + #[derive(Parser, Debug, PartialEq)] + enum Foo { + Command { + #[clap(long, action)] + foo: bool, + }, + } + + assert_eq!( + Opt { + foo: Foo::Command { foo: true } + }, + Opt::try_parse_from(&["test", "command", "--foo"]).unwrap() + ); +} + +#[test] +fn test_rename_all_is_propagated_from_enum_to_variants() { + #[derive(Parser, Debug, PartialEq)] + #[clap(rename_all = "screaming_snake")] + enum Opt { + FirstVariant, + SecondVariant { + #[clap(long, value_parser)] + foo: String, + }, + } + + assert_eq!( + Opt::FirstVariant, + Opt::try_parse_from(&["test", "FIRST_VARIANT"]).unwrap() + ); +} + +#[test] +fn test_rename_all_is_propagated_from_enum_to_variant_fields() { + #[derive(Parser, Debug, PartialEq)] + #[clap(rename_all = "screaming_snake")] + enum Opt { + FirstVariant, + SecondVariant { + #[clap(long, value_parser)] + foo: String, + }, + } + + assert_eq!( + Opt::SecondVariant { + foo: "value".into() + }, + Opt::try_parse_from(&["test", "SECOND_VARIANT", "--FOO", "value"]).unwrap() + ); +} + +#[test] +fn test_rename_all_is_propagation_can_be_overridden() { + #[derive(Parser, Debug, PartialEq)] + #[clap(rename_all = "screaming_snake")] + enum Opt { + #[clap(rename_all = "kebab_case")] + FirstVariant { + #[clap(long, action)] + foo_option: bool, + }, + SecondVariant { + #[clap(rename_all = "kebab_case", long, action)] + foo_option: bool, + }, + } + + assert_eq!( + Opt::FirstVariant { foo_option: true }, + Opt::try_parse_from(&["test", "first-variant", "--foo-option"]).unwrap() + ); + + assert_eq!( + Opt::SecondVariant { foo_option: true }, + Opt::try_parse_from(&["test", "SECOND_VARIANT", "--foo-option"]).unwrap() + ); +} diff --git a/tests/derive/next/nested_subcommands.rs b/tests/derive/next/nested_subcommands.rs new file mode 100644 index 00000000000..59a5ad57053 --- /dev/null +++ b/tests/derive/next/nested_subcommands.rs @@ -0,0 +1,203 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// This work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +use clap::{Parser, Subcommand}; + +#[derive(Parser, PartialEq, Debug)] +struct Opt { + #[clap(short, long, action)] + force: bool, + #[clap(short, long, action = clap::ArgAction::Count)] + verbose: u8, + #[clap(subcommand)] + cmd: Sub, +} + +#[derive(Subcommand, PartialEq, Debug)] +enum Sub { + Fetch {}, + Add {}, +} + +#[derive(Parser, PartialEq, Debug)] +struct Opt2 { + #[clap(short, long, action)] + force: bool, + #[clap(short, long, action = clap::ArgAction::Count)] + verbose: u8, + #[clap(subcommand)] + cmd: Option, +} + +#[test] +fn test_no_cmd() { + let result = Opt::try_parse_from(&["test"]); + assert!(result.is_err()); + + assert_eq!( + Opt2 { + force: false, + verbose: 0, + cmd: None + }, + Opt2::try_parse_from(&["test"]).unwrap() + ); +} + +#[test] +fn test_fetch() { + assert_eq!( + Opt { + force: false, + verbose: 3, + cmd: Sub::Fetch {} + }, + Opt::try_parse_from(&["test", "-vvv", "fetch"]).unwrap() + ); + assert_eq!( + Opt { + force: true, + verbose: 0, + cmd: Sub::Fetch {} + }, + Opt::try_parse_from(&["test", "--force", "fetch"]).unwrap() + ); +} + +#[test] +fn test_add() { + assert_eq!( + Opt { + force: false, + verbose: 0, + cmd: Sub::Add {} + }, + Opt::try_parse_from(&["test", "add"]).unwrap() + ); + assert_eq!( + Opt { + force: false, + verbose: 2, + cmd: Sub::Add {} + }, + Opt::try_parse_from(&["test", "-vv", "add"]).unwrap() + ); +} + +#[test] +fn test_badinput() { + let result = Opt::try_parse_from(&["test", "badcmd"]); + assert!(result.is_err()); + let result = Opt::try_parse_from(&["test", "add", "--verbose"]); + assert!(result.is_err()); + let result = Opt::try_parse_from(&["test", "--badopt", "add"]); + assert!(result.is_err()); + let result = Opt::try_parse_from(&["test", "add", "--badopt"]); + assert!(result.is_err()); +} + +#[derive(Parser, PartialEq, Debug)] +struct Opt3 { + #[clap(short, long, action)] + all: bool, + #[clap(subcommand)] + cmd: Sub2, +} + +#[derive(Subcommand, PartialEq, Debug)] +enum Sub2 { + Foo { + #[clap(value_parser)] + file: String, + #[clap(subcommand)] + cmd: Sub3, + }, + Bar {}, +} + +#[derive(Subcommand, PartialEq, Debug)] +enum Sub3 { + Baz {}, + Quux {}, +} + +#[test] +fn test_subsubcommand() { + assert_eq!( + Opt3 { + all: true, + cmd: Sub2::Foo { + file: "lib.rs".to_string(), + cmd: Sub3::Quux {} + } + }, + Opt3::try_parse_from(&["test", "--all", "foo", "lib.rs", "quux"]).unwrap() + ); +} + +#[derive(Parser, PartialEq, Debug)] +enum SubSubCmdWithOption { + Remote { + #[clap(subcommand)] + cmd: Option, + }, + Stash { + #[clap(subcommand)] + cmd: Stash, + }, +} +#[derive(Subcommand, PartialEq, Debug)] +enum Remote { + Add { + #[clap(value_parser)] + name: String, + #[clap(value_parser)] + url: String, + }, + Remove { + #[clap(value_parser)] + name: String, + }, +} + +#[derive(Subcommand, PartialEq, Debug)] +enum Stash { + Save, + Pop, +} + +#[test] +fn sub_sub_cmd_with_option() { + fn make(args: &[&str]) -> Option { + SubSubCmdWithOption::try_parse_from(args).ok() + } + assert_eq!( + Some(SubSubCmdWithOption::Remote { cmd: None }), + make(&["", "remote"]) + ); + assert_eq!( + Some(SubSubCmdWithOption::Remote { + cmd: Some(Remote::Add { + name: "origin".into(), + url: "http".into() + }) + }), + make(&["", "remote", "add", "origin", "http"]) + ); + assert_eq!( + Some(SubSubCmdWithOption::Stash { cmd: Stash::Save }), + make(&["", "stash", "save"]) + ); + assert_eq!(None, make(&["", "stash"])); +} diff --git a/tests/derive/next/non_literal_attributes.rs b/tests/derive/next/non_literal_attributes.rs new file mode 100644 index 00000000000..bf950fb7baa --- /dev/null +++ b/tests/derive/next/non_literal_attributes.rs @@ -0,0 +1,157 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// This work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +use clap::{ErrorKind, Parser}; +use std::num::ParseIntError; + +pub const DISPLAY_ORDER: usize = 2; + +// Check if the global settings compile +#[derive(Parser, Debug, PartialEq, Eq)] +#[clap(allow_hyphen_values = true)] +struct Opt { + #[clap( + long = "x", + display_order = DISPLAY_ORDER, + next_line_help = true, + default_value = "0", + require_equals = true, + value_parser + )] + x: i32, + + #[clap(short = 'l', long = "level", value_parser, aliases = &["set-level", "lvl"])] + level: String, + + #[clap(long("values"), value_parser)] + values: Vec, + + #[clap(name = "FILE", value_parser, requires_if("FILE", "values"))] + files: Vec, +} + +#[test] +fn test_slice() { + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: Vec::new(), + values: vec![], + }, + Opt::try_parse_from(&["test", "-l", "1"]).unwrap() + ); + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: Vec::new(), + values: vec![], + }, + Opt::try_parse_from(&["test", "--level", "1"]).unwrap() + ); + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: Vec::new(), + values: vec![], + }, + Opt::try_parse_from(&["test", "--set-level", "1"]).unwrap() + ); + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: Vec::new(), + values: vec![], + }, + Opt::try_parse_from(&["test", "--lvl", "1"]).unwrap() + ); +} + +#[test] +fn test_multi_args() { + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: vec!["file".to_string()], + values: vec![], + }, + Opt::try_parse_from(&["test", "-l", "1", "file"]).unwrap() + ); + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: vec!["FILE".to_string()], + values: vec![1], + }, + Opt::try_parse_from(&["test", "-l", "1", "--values", "1", "--", "FILE"]).unwrap() + ); +} + +#[test] +fn test_multi_args_fail() { + let result = Opt::try_parse_from(&["test", "-l", "1", "--", "FILE"]); + assert!(result.is_err()); +} + +#[test] +fn test_bool() { + assert_eq!( + Opt { + x: 1, + level: "1".to_string(), + files: vec![], + values: vec![], + }, + Opt::try_parse_from(&["test", "-l", "1", "--x=1"]).unwrap() + ); + let result = Opt::try_parse_from(&["test", "-l", "1", "--x", "1"]); + assert!(result.is_err()); + assert_eq!(result.unwrap_err().kind(), ErrorKind::NoEquals); +} + +fn parse_hex(input: &str) -> Result { + u64::from_str_radix(input, 16) +} + +#[derive(Parser, PartialEq, Debug)] +struct HexOpt { + #[clap(short, value_parser = parse_hex)] + number: u64, +} + +#[test] +fn test_parse_hex_function_path() { + assert_eq!( + HexOpt { number: 5 }, + HexOpt::try_parse_from(&["test", "-n", "5"]).unwrap() + ); + assert_eq!( + HexOpt { + number: 0x00ab_cdef + }, + HexOpt::try_parse_from(&["test", "-n", "abcdef"]).unwrap() + ); + + let err = HexOpt::try_parse_from(&["test", "-n", "gg"]).unwrap_err(); + assert!( + err.to_string().contains("invalid digit found in string"), + "{}", + err + ); +} diff --git a/tests/derive/next/options.rs b/tests/derive/next/options.rs new file mode 100644 index 00000000000..f55dd8805e9 --- /dev/null +++ b/tests/derive/next/options.rs @@ -0,0 +1,522 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// This work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +#![allow(clippy::option_option)] + +use crate::utils; + +use clap::{Parser, Subcommand}; + +#[test] +fn required_option() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, long, value_parser)] + arg: i32, + } + assert_eq!( + Opt { arg: 42 }, + Opt::try_parse_from(&["test", "-a42"]).unwrap() + ); + assert_eq!( + Opt { arg: 42 }, + Opt::try_parse_from(&["test", "-a", "42"]).unwrap() + ); + assert_eq!( + 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()); +} + +#[test] +fn option_with_default() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, value_parser, default_value = "42")] + arg: i32, + } + assert_eq!( + 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()); +} + +#[test] +fn option_with_raw_default() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, value_parser, default_value = "42")] + arg: i32, + } + assert_eq!( + 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()); +} + +#[test] +fn option_from_str() { + #[derive(Clone, Debug, PartialEq)] + struct A; + + impl std::str::FromStr for A { + type Err = std::convert::Infallible; + + fn from_str(_: &str) -> Result { + Ok(A) + } + } + + #[derive(Debug, Parser, PartialEq)] + struct Opt { + #[clap(value_parser)] + a: Option, + } + + assert_eq!(Opt { a: None }, Opt::try_parse_from(&["test"]).unwrap()); + assert_eq!( + Opt { a: Some(A) }, + Opt::try_parse_from(&["test", "foo"]).unwrap() + ); +} + +#[test] +fn vec_from_str() { + #[derive(Clone, Debug, PartialEq)] + struct A; + + impl std::str::FromStr for A { + type Err = std::convert::Infallible; + + fn from_str(_: &str) -> Result { + Ok(A) + } + } + + #[derive(Debug, Parser, PartialEq)] + struct Opt { + #[clap(value_parser)] + a: Vec, + } + + assert_eq!( + Opt { a: Vec::new() }, + Opt::try_parse_from(&["test"]).unwrap() + ); + assert_eq!( + Opt { a: vec![A] }, + Opt::try_parse_from(&["test", "foo"]).unwrap() + ); +} + +#[test] +fn option_vec_from_str() { + #[derive(Clone, Debug, PartialEq)] + struct A; + + impl std::str::FromStr for A { + type Err = std::convert::Infallible; + + fn from_str(_: &str) -> Result { + Ok(A) + } + } + + #[derive(Debug, Parser, PartialEq)] + struct Opt { + #[clap(short, value_parser)] + a: Option>, + } + + assert_eq!(Opt { a: None }, Opt::try_parse_from(&["test"]).unwrap()); + assert_eq!( + Opt { a: Some(vec![A]) }, + Opt::try_parse_from(&["test", "-a", "foo"]).unwrap() + ); +} + +#[test] +fn option_type_is_optional() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, value_parser)] + arg: Option, + } + assert_eq!( + 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()); +} + +#[test] +fn required_with_option_type() { + #[derive(Debug, PartialEq, Eq, Parser)] + #[clap(subcommand_negates_reqs = true)] + struct Opt { + #[clap(value_parser, required = true)] + req_str: Option, + + #[clap(subcommand)] + cmd: Option, + } + + #[derive(Debug, PartialEq, Eq, Subcommand)] + enum SubCommands { + ExSub { + #[clap(short, long, action = clap::ArgAction::Count)] + verbose: u8, + }, + } + + assert_eq!( + Opt { + req_str: Some(("arg").into()), + cmd: None, + }, + Opt::try_parse_from(&["test", "arg"]).unwrap() + ); + + assert_eq!( + Opt { + req_str: None, + cmd: Some(SubCommands::ExSub { verbose: 1 }), + }, + Opt::try_parse_from(&["test", "ex-sub", "-v"]).unwrap() + ); + + assert!(Opt::try_parse_from(&["test"]).is_err()); +} + +#[test] +fn ignore_qualified_option_type() { + fn parser(s: &str) -> Result, std::convert::Infallible> { + Ok(Some(s.to_string())) + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_parser = parser)] + arg: ::std::option::Option, + } + + assert_eq!( + Opt { + arg: Some("success".into()) + }, + Opt::try_parse_from(&["test", "success"]).unwrap() + ); +} + +#[test] +fn option_option_type_is_optional_value() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, value_parser)] + #[allow(clippy::option_option)] + arg: Option>, + } + assert_eq!( + Opt { + arg: Some(Some(42)) + }, + Opt::try_parse_from(&["test", "-a42"]).unwrap() + ); + assert_eq!( + 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()); +} + +#[test] +fn option_option_type_help() { + #[derive(Parser, Debug)] + struct Opt { + #[clap(long, value_name = "val", value_parser)] + arg: Option>, + } + let help = utils::get_help::(); + assert!(help.contains("--arg []")); + assert!(!help.contains("--arg []...")); +} + +#[test] +fn two_option_option_types() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, value_parser)] + arg: Option>, + + #[clap(long, value_parser)] + field: Option>, + } + assert_eq!( + Opt { + arg: Some(Some(42)), + field: Some(Some("f".into())) + }, + Opt::try_parse_from(&["test", "-a42", "--field", "f"]).unwrap() + ); + assert_eq!( + Opt { + arg: Some(Some(42)), + field: Some(None) + }, + Opt::try_parse_from(&["test", "-a42", "--field"]).unwrap() + ); + assert_eq!( + Opt { + arg: Some(None), + field: Some(None) + }, + Opt::try_parse_from(&["test", "-a", "--field"]).unwrap() + ); + assert_eq!( + Opt { + arg: Some(None), + field: Some(Some("f".into())) + }, + Opt::try_parse_from(&["test", "-a", "--field", "f"]).unwrap() + ); + assert_eq!( + Opt { + arg: None, + field: Some(None) + }, + Opt::try_parse_from(&["test", "--field"]).unwrap() + ); + assert_eq!( + Opt { + arg: None, + field: None + }, + Opt::try_parse_from(&["test"]).unwrap() + ); +} + +#[test] +fn vec_type_is_multiple_occurrences() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, long, value_parser)] + arg: Vec, + } + assert_eq!( + Opt { arg: vec![24] }, + Opt::try_parse_from(&["test", "-a24"]).unwrap() + ); + assert_eq!(Opt { arg: vec![] }, Opt::try_parse_from(&["test"]).unwrap()); + assert_eq!( + Opt { arg: vec![24, 42] }, + Opt::try_parse_from(&["test", "-a", "24", "-a", "42"]).unwrap() + ); +} + +#[test] +fn vec_type_with_required() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, long, required = true, value_parser)] + arg: Vec, + } + assert_eq!( + Opt { arg: vec![24] }, + Opt::try_parse_from(&["test", "-a24"]).unwrap() + ); + assert!(Opt::try_parse_from(&["test"]).is_err()); + assert_eq!( + Opt { arg: vec![24, 42] }, + Opt::try_parse_from(&["test", "-a", "24", "-a", "42"]).unwrap() + ); +} + +#[test] +fn vec_type_with_multiple_values_only() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, long, multiple_values(true), value_parser)] + arg: Vec, + } + assert_eq!( + Opt { arg: vec![24] }, + Opt::try_parse_from(&["test", "-a24"]).unwrap() + ); + assert_eq!(Opt { arg: vec![] }, Opt::try_parse_from(&["test"]).unwrap()); + assert_eq!( + Opt { arg: vec![24, 42] }, + Opt::try_parse_from(&["test", "-a", "24", "42"]).unwrap() + ); +} + +#[test] +fn ignore_qualified_vec_type() { + fn parser(s: &str) -> Result, std::convert::Infallible> { + Ok(vec![s.to_string()]) + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_parser = parser)] + arg: ::std::vec::Vec, + } + + assert_eq!( + Opt { + arg: vec!["success".into()] + }, + Opt::try_parse_from(&["test", "success"]).unwrap() + ); +} + +#[test] +fn option_vec_type() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, value_parser)] + arg: Option>, + } + assert_eq!( + Opt { arg: Some(vec![1]) }, + Opt::try_parse_from(&["test", "-a", "1"]).unwrap() + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2]) + }, + Opt::try_parse_from(&["test", "-a", "1", "-a", "2"]).unwrap() + ); + + assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap()); +} + +#[test] +fn option_vec_type_structopt_behavior() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, long, multiple_values(true), min_values(0), value_parser)] + arg: Option>, + } + assert_eq!( + Opt { arg: Some(vec![1]) }, + Opt::try_parse_from(&["test", "-a", "1"]).unwrap() + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2]) + }, + Opt::try_parse_from(&["test", "-a", "1", "2"]).unwrap() + ); + + assert_eq!( + Opt { arg: Some(vec![]) }, + Opt::try_parse_from(&["test", "-a"]).unwrap() + ); + + assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap()); +} + +#[test] +fn two_option_vec_types() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(short, value_parser)] + arg: Option>, + + #[clap(short, value_parser)] + b: Option>, + } + + assert_eq!( + Opt { + arg: Some(vec![1]), + b: None, + }, + Opt::try_parse_from(&["test", "-a", "1"]).unwrap() + ); + + assert_eq!( + Opt { + arg: Some(vec![1]), + b: Some(vec![1]) + }, + Opt::try_parse_from(&["test", "-a", "1", "-b", "1"]).unwrap() + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2]), + b: Some(vec![1, 2]) + }, + Opt::try_parse_from(&["test", "-a", "1", "-a", "2", "-b", "1", "-b", "2"]).unwrap() + ); + + assert_eq!( + Opt { arg: None, b: None }, + Opt::try_parse_from(&["test"]).unwrap() + ); +} + +#[test] +fn explicit_value_parser() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(long, value_parser = clap::value_parser!(i32))] + arg: i32, + } + assert_eq!( + Opt { arg: 42 }, + Opt::try_parse_from(&["test", "--arg", "42"]).unwrap() + ); +} + +#[test] +fn implicit_value_parser() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(long, value_parser)] + arg: i32, + } + assert_eq!( + Opt { arg: 42 }, + Opt::try_parse_from(&["test", "--arg", "42"]).unwrap() + ); +} diff --git a/tests/derive/next/privacy.rs b/tests/derive/next/privacy.rs new file mode 100644 index 00000000000..224a23a4cc1 --- /dev/null +++ b/tests/derive/next/privacy.rs @@ -0,0 +1,37 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// This work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +mod options { + use clap::Parser; + + #[derive(Debug, Parser)] + pub struct Options { + #[clap(subcommand)] + pub subcommand: super::subcommands::SubCommand, + } +} + +mod subcommands { + use clap::Subcommand; + + #[derive(Debug, Subcommand)] + pub enum SubCommand { + /// foo + Foo { + /// foo + #[clap(value_parser)] + bars: String, + }, + } +} diff --git a/tests/derive/next/raw_bool_literal.rs b/tests/derive/next/raw_bool_literal.rs new file mode 100644 index 00000000000..7dd3618a7d0 --- /dev/null +++ b/tests/derive/next/raw_bool_literal.rs @@ -0,0 +1,29 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use clap::Parser; + +#[test] +fn raw_bool_literal() { + #[derive(Parser, Debug, PartialEq)] + #[clap(name = "raw_bool")] + struct Opt { + #[clap(raw(false), value_parser)] + a: String, + #[clap(raw(true), value_parser)] + b: String, + } + + assert_eq!( + Opt { + a: "one".into(), + b: "--help".into() + }, + Opt::try_parse_from(&["test", "one", "--", "--help"]).unwrap() + ); +} diff --git a/tests/derive/next/raw_idents.rs b/tests/derive/next/raw_idents.rs new file mode 100644 index 00000000000..25762c0e6bb --- /dev/null +++ b/tests/derive/next/raw_idents.rs @@ -0,0 +1,24 @@ +use clap::Parser; + +#[test] +fn raw_idents() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(short, long, value_parser)] + r#type: String, + } + + assert_eq!( + Opt { + r#type: "long".into() + }, + Opt::try_parse_from(&["test", "--type", "long"]).unwrap() + ); + + assert_eq!( + Opt { + r#type: "short".into() + }, + Opt::try_parse_from(&["test", "-t", "short"]).unwrap() + ); +} diff --git a/tests/derive/next/rename_all_env.rs b/tests/derive/next/rename_all_env.rs new file mode 100644 index 00000000000..46327c12ff0 --- /dev/null +++ b/tests/derive/next/rename_all_env.rs @@ -0,0 +1,47 @@ +#![cfg(feature = "env")] + +use crate::utils; + +use clap::Parser; + +#[test] +fn it_works() { + #[derive(Debug, PartialEq, Parser)] + #[clap(rename_all_env = "kebab")] + struct BehaviorModel { + #[clap(env, value_parser)] + be_nice: String, + } + + let help = utils::get_help::(); + assert!(help.contains("[env: be-nice=]")); +} + +#[test] +fn default_is_screaming() { + #[derive(Debug, PartialEq, Parser)] + struct BehaviorModel { + #[clap(env, value_parser)] + be_nice: String, + } + + let help = utils::get_help::(); + assert!(help.contains("[env: BE_NICE=]")); +} + +#[test] +fn overridable() { + #[derive(Debug, PartialEq, Parser)] + #[clap(rename_all_env = "kebab")] + struct BehaviorModel { + #[clap(env, value_parser)] + be_nice: String, + + #[clap(rename_all_env = "pascal", env, value_parser)] + be_aggressive: String, + } + + let help = utils::get_help::(); + assert!(help.contains("[env: be-nice=]")); + assert!(help.contains("[env: BeAggressive=]")); +} diff --git a/tests/derive/next/skip.rs b/tests/derive/next/skip.rs new file mode 100644 index 00000000000..d2e3b7ec086 --- /dev/null +++ b/tests/derive/next/skip.rs @@ -0,0 +1,157 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use clap::Parser; + +#[test] +fn skip_1() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(short, value_parser)] + x: u32, + #[clap(skip)] + s: u32, + } + + assert!(Opt::try_parse_from(&["test", "-x", "10", "20"]).is_err()); + + let mut opt = Opt::try_parse_from(&["test", "-x", "10"]).unwrap(); + assert_eq!( + opt, + Opt { + x: 10, + s: 0, // default + } + ); + opt.s = 42; + + opt.update_from(&["test", "-x", "22"]); + + assert_eq!(opt, Opt { x: 22, s: 42 }); +} + +#[test] +fn skip_2() { + #[derive(Parser, Debug, PartialEq)] + struct Opt { + #[clap(short, value_parser)] + x: u32, + #[clap(skip)] + ss: String, + #[clap(skip)] + sn: u8, + #[clap(value_parser)] + y: u32, + #[clap(skip)] + sz: u16, + #[clap(value_parser)] + t: u32, + } + + assert_eq!( + Opt::try_parse_from(&["test", "-x", "10", "20", "30"]).unwrap(), + Opt { + x: 10, + ss: String::from(""), + sn: 0, + y: 20, + sz: 0, + t: 30, + } + ); +} + +#[test] +fn skip_enum() { + #[derive(Debug, PartialEq)] + #[allow(unused)] + enum Kind { + A, + B, + } + + impl Default for Kind { + fn default() -> Self { + Kind::B + } + } + + #[derive(Parser, Debug, PartialEq)] + pub struct Opt { + #[clap(long, short, value_parser)] + number: u32, + #[clap(skip)] + k: Kind, + #[clap(skip)] + v: Vec, + } + + assert_eq!( + Opt::try_parse_from(&["test", "-n", "10"]).unwrap(), + Opt { + number: 10, + k: Kind::B, + v: vec![], + } + ); +} + +#[test] +fn skip_help_doc_comments() { + #[derive(Parser, Debug, PartialEq)] + pub struct Opt { + #[clap(skip, help = "internal_stuff")] + a: u32, + + #[clap(skip, long_help = "internal_stuff\ndo not touch")] + b: u32, + + /// Not meant to be used by clap. + /// + /// I want a default here. + #[clap(skip)] + c: u32, + + #[clap(short, value_parser)] + n: u32, + } + + assert_eq!( + Opt::try_parse_from(&["test", "-n", "10"]).unwrap(), + Opt { + n: 10, + a: 0, + b: 0, + c: 0, + } + ); +} + +#[test] +fn skip_val() { + #[derive(Parser, Debug, PartialEq)] + pub struct Opt { + #[clap(long, short, value_parser)] + number: u32, + + #[clap(skip = "key")] + k: String, + + #[clap(skip = vec![1, 2, 3])] + v: Vec, + } + + assert_eq!( + Opt::try_parse_from(&["test", "-n", "10"]).unwrap(), + Opt { + number: 10, + k: "key".to_string(), + v: vec![1, 2, 3] + } + ); +} diff --git a/tests/derive/next/structopt.rs b/tests/derive/next/structopt.rs new file mode 100644 index 00000000000..56024856827 --- /dev/null +++ b/tests/derive/next/structopt.rs @@ -0,0 +1,23 @@ +#![allow(deprecated)] + +use clap::{AppSettings, StructOpt}; + +#[test] +fn compatible() { + #[derive(StructOpt)] + #[structopt(author, version, about)] + #[structopt(global_setting(AppSettings::PropagateVersion))] + struct Cli { + #[structopt(subcommand)] + command: Commands, + } + + #[derive(StructOpt)] + #[structopt(setting(AppSettings::SubcommandRequiredElseHelp))] + enum Commands { + /// Adds files to myapp + Add { name: Option }, + } + + Cli::from_iter(["test", "add"]); +} diff --git a/tests/derive/next/subcommands.rs b/tests/derive/next/subcommands.rs new file mode 100644 index 00000000000..2dcb897643f --- /dev/null +++ b/tests/derive/next/subcommands.rs @@ -0,0 +1,622 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// This work was derived from Structopt (https://github.com/TeXitoi/structopt) +// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the +// MIT/Apache 2.0 license. + +use crate::utils; + +use clap::{Args, Parser, Subcommand}; + +#[derive(Parser, PartialEq, Debug)] +enum Opt { + /// Fetch stuff from GitHub + Fetch { + #[clap(long, action)] + all: bool, + /// Overwrite local branches. + #[clap(short, long, action)] + force: bool, + #[clap(value_parser)] + repo: String, + }, + + Add { + #[clap(short, long, action)] + interactive: bool, + #[clap(short, long, action)] + verbose: bool, + }, +} + +#[test] +fn test_fetch() { + assert_eq!( + Opt::Fetch { + all: true, + force: false, + repo: "origin".to_string() + }, + Opt::try_parse_from(&["test", "fetch", "--all", "origin"]).unwrap() + ); + assert_eq!( + Opt::Fetch { + all: false, + force: true, + repo: "origin".to_string() + }, + Opt::try_parse_from(&["test", "fetch", "-f", "origin"]).unwrap() + ); +} + +#[test] +fn test_add() { + assert_eq!( + Opt::Add { + interactive: false, + verbose: false + }, + Opt::try_parse_from(&["test", "add"]).unwrap() + ); + assert_eq!( + Opt::Add { + interactive: true, + verbose: true + }, + Opt::try_parse_from(&["test", "add", "-i", "-v"]).unwrap() + ); +} + +#[test] +fn test_no_parse() { + let result = Opt::try_parse_from(&["test", "badcmd", "-i", "-v"]); + assert!(result.is_err()); + + let result = Opt::try_parse_from(&["test", "add", "--badoption"]); + assert!(result.is_err()); + + let result = Opt::try_parse_from(&["test"]); + assert!(result.is_err()); +} + +#[derive(Parser, PartialEq, Debug)] +enum Opt2 { + DoSomething { + #[clap(value_parser)] + arg: String, + }, +} + +#[test] +/// This test is specifically to make sure that hyphenated subcommands get +/// processed correctly. +fn test_hyphenated_subcommands() { + assert_eq!( + Opt2::DoSomething { + arg: "blah".to_string() + }, + Opt2::try_parse_from(&["test", "do-something", "blah"]).unwrap() + ); +} + +#[derive(Parser, PartialEq, Debug)] +enum Opt3 { + Add, + Init, + Fetch, +} + +#[test] +fn test_null_commands() { + assert_eq!(Opt3::Add, Opt3::try_parse_from(&["test", "add"]).unwrap()); + assert_eq!(Opt3::Init, Opt3::try_parse_from(&["test", "init"]).unwrap()); + assert_eq!( + Opt3::Fetch, + Opt3::try_parse_from(&["test", "fetch"]).unwrap() + ); +} + +#[derive(Parser, PartialEq, Debug)] +#[clap(about = "Not shown")] +struct Add { + #[clap(value_parser)] + file: String, +} +/// Not shown +#[derive(Parser, PartialEq, Debug)] +struct Fetch { + #[clap(value_parser)] + remote: String, +} +#[derive(Parser, PartialEq, Debug)] +enum Opt4 { + // Not shown + /// Add a file + Add(Add), + Init, + /// download history from remote + Fetch(Fetch), +} + +#[test] +fn test_tuple_commands() { + assert_eq!( + Opt4::Add(Add { + file: "f".to_string() + }), + Opt4::try_parse_from(&["test", "add", "f"]).unwrap() + ); + assert_eq!(Opt4::Init, Opt4::try_parse_from(&["test", "init"]).unwrap()); + assert_eq!( + Opt4::Fetch(Fetch { + remote: "origin".to_string() + }), + Opt4::try_parse_from(&["test", "fetch", "origin"]).unwrap() + ); + + let output = utils::get_long_help::(); + + assert!(output.contains("download history from remote")); + assert!(output.contains("Add a file")); + assert!(!output.contains("Not shown")); +} + +#[test] +fn global_passed_down() { + #[derive(Debug, PartialEq, Parser)] + struct Opt { + #[clap(global = true, long, action)] + other: bool, + #[clap(subcommand)] + sub: Subcommands, + } + + #[derive(Debug, PartialEq, Subcommand)] + enum Subcommands { + Add, + Global(GlobalCmd), + } + + #[derive(Debug, PartialEq, Args)] + struct GlobalCmd { + #[clap(from_global, action)] + other: bool, + } + + assert_eq!( + Opt::try_parse_from(&["test", "global"]).unwrap(), + Opt { + other: false, + sub: Subcommands::Global(GlobalCmd { other: false }) + } + ); + + assert_eq!( + Opt::try_parse_from(&["test", "global", "--other"]).unwrap(), + Opt { + other: true, + sub: Subcommands::Global(GlobalCmd { other: true }) + } + ); +} + +#[test] +fn external_subcommand() { + #[derive(Debug, PartialEq, Parser)] + struct Opt { + #[clap(subcommand)] + sub: Subcommands, + } + + #[derive(Debug, PartialEq, Subcommand)] + enum Subcommands { + Add, + Remove, + #[clap(external_subcommand)] + Other(Vec), + } + + assert_eq!( + Opt::try_parse_from(&["test", "add"]).unwrap(), + Opt { + sub: Subcommands::Add + } + ); + + assert_eq!( + Opt::try_parse_from(&["test", "remove"]).unwrap(), + Opt { + sub: Subcommands::Remove + } + ); + + assert!(Opt::try_parse_from(&["test"]).is_err()); + + assert_eq!( + Opt::try_parse_from(&["test", "git", "status"]).unwrap(), + Opt { + sub: Subcommands::Other(vec!["git".into(), "status".into()]) + } + ); +} + +#[test] +fn external_subcommand_os_string() { + use std::ffi::OsString; + + #[derive(Debug, PartialEq, Parser)] + struct Opt { + #[clap(subcommand)] + sub: Subcommands, + } + + #[derive(Debug, PartialEq, Subcommand)] + enum Subcommands { + #[clap(external_subcommand)] + Other(Vec), + } + + assert_eq!( + Opt::try_parse_from(&["test", "git", "status"]).unwrap(), + Opt { + sub: Subcommands::Other(vec!["git".into(), "status".into()]) + } + ); + + assert!(Opt::try_parse_from(&["test"]).is_err()); +} + +#[test] +fn external_subcommand_optional() { + #[derive(Debug, PartialEq, Parser)] + struct Opt { + #[clap(subcommand)] + sub: Option, + } + + #[derive(Debug, PartialEq, Subcommand)] + enum Subcommands { + #[clap(external_subcommand)] + Other(Vec), + } + + assert_eq!( + Opt::try_parse_from(&["test", "git", "status"]).unwrap(), + Opt { + sub: Some(Subcommands::Other(vec!["git".into(), "status".into()])) + } + ); + + assert_eq!(Opt::try_parse_from(&["test"]).unwrap(), Opt { sub: None }); +} + +#[test] +fn enum_in_enum_subsubcommand() { + #[derive(Parser, Debug, PartialEq)] + pub enum Opt { + #[clap(alias = "l")] + List, + #[clap(subcommand, alias = "d")] + Daemon(DaemonCommand), + } + + #[derive(Subcommand, Debug, PartialEq)] + pub enum DaemonCommand { + Start, + Stop, + } + + let result = Opt::try_parse_from(&["test"]); + assert!(result.is_err()); + + let result = Opt::try_parse_from(&["test", "list"]).unwrap(); + assert_eq!(Opt::List, result); + + let result = Opt::try_parse_from(&["test", "l"]).unwrap(); + assert_eq!(Opt::List, result); + + let result = Opt::try_parse_from(&["test", "daemon"]); + assert!(result.is_err()); + + let result = Opt::try_parse_from(&["test", "daemon", "start"]).unwrap(); + assert_eq!(Opt::Daemon(DaemonCommand::Start), result); + + let result = Opt::try_parse_from(&["test", "d", "start"]).unwrap(); + assert_eq!(Opt::Daemon(DaemonCommand::Start), result); +} + +#[test] +fn update_subcommands() { + #[derive(Parser, PartialEq, Debug)] + enum Opt { + Command1(Command1), + Command2(Command2), + } + + #[derive(Parser, PartialEq, Debug)] + struct Command1 { + #[clap(value_parser)] + arg1: i32, + #[clap(value_parser)] + arg2: i32, + } + + #[derive(Parser, PartialEq, Debug)] + struct Command2 { + #[clap(value_parser)] + arg2: i32, + } + + // Full subcommand update + let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 }); + opt.try_update_from(&["test", "command1", "42", "44"]) + .unwrap(); + assert_eq!( + Opt::try_parse_from(&["test", "command1", "42", "44"]).unwrap(), + opt + ); + + // Partial subcommand update + let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 }); + opt.try_update_from(&["test", "command1", "42"]).unwrap(); + assert_eq!( + Opt::try_parse_from(&["test", "command1", "42", "14"]).unwrap(), + opt + ); + + // Change subcommand + let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 }); + opt.try_update_from(&["test", "command2", "43"]).unwrap(); + assert_eq!( + Opt::try_parse_from(&["test", "command2", "43"]).unwrap(), + opt + ); +} + +#[test] +fn update_sub_subcommands() { + #[derive(Parser, PartialEq, Debug)] + enum Opt { + #[clap(subcommand)] + Child1(Child1), + #[clap(subcommand)] + Child2(Child2), + } + + #[derive(Subcommand, PartialEq, Debug)] + enum Child1 { + Command1(Command1), + Command2(Command2), + } + + #[derive(Subcommand, PartialEq, Debug)] + enum Child2 { + Command1(Command1), + Command2(Command2), + } + + #[derive(Args, PartialEq, Debug)] + struct Command1 { + #[clap(value_parser)] + arg1: i32, + #[clap(value_parser)] + arg2: i32, + } + + #[derive(Args, PartialEq, Debug)] + struct Command2 { + #[clap(value_parser)] + arg2: i32, + } + + // Full subcommand update + let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 })); + opt.try_update_from(&["test", "child1", "command1", "42", "44"]) + .unwrap(); + assert_eq!( + Opt::try_parse_from(&["test", "child1", "command1", "42", "44"]).unwrap(), + opt + ); + + // Partial subcommand update + let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 })); + opt.try_update_from(&["test", "child1", "command1", "42"]) + .unwrap(); + assert_eq!( + Opt::try_parse_from(&["test", "child1", "command1", "42", "14"]).unwrap(), + opt + ); + + // Partial subcommand update + let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 })); + opt.try_update_from(&["test", "child1", "command2", "43"]) + .unwrap(); + assert_eq!( + Opt::try_parse_from(&["test", "child1", "command2", "43"]).unwrap(), + opt + ); + + // Change subcommand + let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 })); + opt.try_update_from(&["test", "child2", "command2", "43"]) + .unwrap(); + assert_eq!( + Opt::try_parse_from(&["test", "child2", "command2", "43"]).unwrap(), + opt + ); +} + +#[test] +fn update_ext_subcommand() { + #[derive(Parser, PartialEq, Debug)] + enum Opt { + Command1(Command1), + Command2(Command2), + #[clap(external_subcommand)] + Ext(Vec), + } + + #[derive(Args, PartialEq, Debug)] + struct Command1 { + #[clap(value_parser)] + arg1: i32, + #[clap(value_parser)] + arg2: i32, + } + + #[derive(Args, PartialEq, Debug)] + struct Command2 { + #[clap(value_parser)] + arg2: i32, + } + + // Full subcommand update + let mut opt = Opt::Ext(vec!["12".into(), "14".into()]); + opt.try_update_from(&["test", "ext", "42", "44"]).unwrap(); + assert_eq!( + Opt::try_parse_from(&["test", "ext", "42", "44"]).unwrap(), + opt + ); + + // No partial subcommand update + let mut opt = Opt::Ext(vec!["12".into(), "14".into()]); + opt.try_update_from(&["test", "ext", "42"]).unwrap(); + assert_eq!(Opt::try_parse_from(&["test", "ext", "42"]).unwrap(), opt); + + // Change subcommand + let mut opt = Opt::Ext(vec!["12".into(), "14".into()]); + opt.try_update_from(&["test", "command2", "43"]).unwrap(); + assert_eq!( + Opt::try_parse_from(&["test", "command2", "43"]).unwrap(), + opt + ); + + let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 }); + opt.try_update_from(&["test", "ext", "42", "44"]).unwrap(); + assert_eq!( + Opt::try_parse_from(&["test", "ext", "42", "44"]).unwrap(), + opt + ); +} +#[test] +fn subcommand_name_not_literal() { + fn get_name() -> &'static str { + "renamed" + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(subcommand)] + subcmd: SubCmd, + } + + #[derive(Subcommand, PartialEq, Debug)] + enum SubCmd { + #[clap(name = get_name())] + SubCmd1, + } + + assert!(Opt::try_parse_from(&["test", "renamed"]).is_ok()); +} + +#[test] +fn skip_subcommand() { + #[derive(Debug, PartialEq, Parser)] + struct Opt { + #[clap(subcommand)] + sub: Subcommands, + } + + #[derive(Debug, PartialEq, Subcommand)] + enum Subcommands { + Add, + Remove, + + #[allow(dead_code)] + #[clap(skip)] + Skip, + } + + assert_eq!( + Opt::try_parse_from(&["test", "add"]).unwrap(), + Opt { + sub: Subcommands::Add + } + ); + + assert_eq!( + Opt::try_parse_from(&["test", "remove"]).unwrap(), + Opt { + sub: Subcommands::Remove + } + ); + + let res = Opt::try_parse_from(&["test", "skip"]); + assert_eq!(res.unwrap_err().kind(), clap::ErrorKind::UnknownArgument,); +} + +#[test] +#[cfg(feature = "unstable-v4")] +fn built_in_subcommand_escaped() { + #[derive(Debug, PartialEq, Parser)] + enum Command { + Install { + #[clap(value_parser)] + arg: Option, + }, + #[clap(external_subcommand)] + Custom(Vec), + } + + assert_eq!( + Command::try_parse_from(&["test", "install", "arg"]).unwrap(), + Command::Install { + arg: Some(String::from("arg")) + } + ); + assert_eq!( + Command::try_parse_from(&["test", "--", "install"]).unwrap(), + Command::Custom(vec![String::from("install")]) + ); + assert_eq!( + Command::try_parse_from(&["test", "--", "install", "arg"]).unwrap(), + Command::Custom(vec![String::from("install"), String::from("arg")]) + ); +} + +#[test] +#[cfg(not(feature = "unstable-v4"))] +fn built_in_subcommand_escaped() { + #[derive(Debug, PartialEq, Parser)] + enum Command { + Install { + #[clap(value_parser)] + arg: Option, + }, + #[clap(external_subcommand)] + Custom(Vec), + } + + assert_eq!( + Command::try_parse_from(&["test", "install", "arg"]).unwrap(), + Command::Install { + arg: Some(String::from("arg")) + } + ); + assert_eq!( + Command::try_parse_from(&["test", "--", "install"]).unwrap(), + Command::Install { arg: None } + ); + assert_eq!( + Command::try_parse_from(&["test", "--", "install", "arg"]).unwrap(), + Command::Install { arg: None } + ); +} diff --git a/tests/derive/next/type_alias_regressions.rs b/tests/derive/next/type_alias_regressions.rs new file mode 100644 index 00000000000..c418644f413 --- /dev/null +++ b/tests/derive/next/type_alias_regressions.rs @@ -0,0 +1,36 @@ +//! Regression test to ensure that type aliases do not cause compilation failures. +#![allow(deprecated)] + +use clap::{Parser, Subcommand, ValueEnum}; + +// Result type alias +#[allow(dead_code)] +type Result = std::result::Result>; + +type Option = std::option::Option; + +#[derive(Parser)] +pub struct Opts { + #[clap(value_parser)] + another_string: String, + #[clap(subcommand)] + command: Command, + #[clap(short, long, value_enum, value_parser)] + choice: ArgChoice, +} + +#[derive(Subcommand, PartialEq, Debug)] +enum Command { + DoSomething { arg: Option }, +} + +#[derive(ValueEnum, PartialEq, Debug, Clone)] +enum ArgChoice { + Foo, + Bar, +} + +#[test] +fn type_alias_regressions() { + Opts::try_parse_from(["test", "value", "--choice=foo", "do-something"]).unwrap(); +} diff --git a/tests/derive/next/utf8.rs b/tests/derive/next/utf8.rs new file mode 100644 index 00000000000..6389ef67168 --- /dev/null +++ b/tests/derive/next/utf8.rs @@ -0,0 +1,227 @@ +#![cfg(not(windows))] + +use clap::{ErrorKind, Parser}; +use std::ffi::OsString; +use std::os::unix::ffi::OsStringExt; + +#[derive(Parser, Debug, PartialEq, Eq)] +struct Positional { + #[clap(value_parser)] + arg: String, +} + +#[derive(Parser, Debug, PartialEq, Eq)] +struct Named { + #[clap(short, long, value_parser)] + arg: String, +} + +#[test] +fn invalid_utf8_strict_positional() { + let m = Positional::try_parse_from(vec![OsString::from(""), OsString::from_vec(vec![0xe9])]); + assert!(m.is_err()); + assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8); +} + +#[test] +fn invalid_utf8_strict_option_short_space() { + let m = Named::try_parse_from(vec![ + OsString::from(""), + OsString::from("-a"), + OsString::from_vec(vec![0xe9]), + ]); + assert!(m.is_err()); + assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8); +} + +#[test] +fn invalid_utf8_strict_option_short_equals() { + let m = Named::try_parse_from(vec![ + OsString::from(""), + OsString::from_vec(vec![0x2d, 0x61, 0x3d, 0xe9]), + ]); + assert!(m.is_err()); + assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8); +} + +#[test] +fn invalid_utf8_strict_option_short_no_space() { + let m = Named::try_parse_from(vec![ + OsString::from(""), + OsString::from_vec(vec![0x2d, 0x61, 0xe9]), + ]); + assert!(m.is_err()); + assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8); +} + +#[test] +fn invalid_utf8_strict_option_long_space() { + let m = Named::try_parse_from(vec![ + OsString::from(""), + OsString::from("--arg"), + OsString::from_vec(vec![0xe9]), + ]); + assert!(m.is_err()); + assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8); +} + +#[test] +fn invalid_utf8_strict_option_long_equals() { + let m = Named::try_parse_from(vec![ + OsString::from(""), + OsString::from_vec(vec![0x2d, 0x2d, 0x61, 0x72, 0x67, 0x3d, 0xe9]), + ]); + assert!(m.is_err()); + assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8); +} + +#[derive(Parser, Debug, PartialEq, Eq)] +struct PositionalOs { + #[clap(value_parser)] + arg: OsString, +} + +#[derive(Parser, Debug, PartialEq, Eq)] +struct NamedOs { + #[clap(short, long, value_parser)] + arg: OsString, +} + +#[test] +fn invalid_utf8_positional() { + let r = PositionalOs::try_parse_from(vec![OsString::from(""), OsString::from_vec(vec![0xe9])]); + assert_eq!( + r.unwrap(), + PositionalOs { + arg: OsString::from_vec(vec![0xe9]) + } + ); +} + +#[test] +fn invalid_utf8_option_short_space() { + let r = NamedOs::try_parse_from(vec![ + OsString::from(""), + OsString::from("-a"), + OsString::from_vec(vec![0xe9]), + ]); + assert_eq!( + r.unwrap(), + NamedOs { + arg: OsString::from_vec(vec![0xe9]) + } + ); +} + +#[test] +fn invalid_utf8_option_short_equals() { + let r = NamedOs::try_parse_from(vec![ + OsString::from(""), + OsString::from_vec(vec![0x2d, 0x61, 0x3d, 0xe9]), + ]); + assert_eq!( + r.unwrap(), + NamedOs { + arg: OsString::from_vec(vec![0xe9]) + } + ); +} + +#[test] +fn invalid_utf8_option_short_no_space() { + let r = NamedOs::try_parse_from(vec![ + OsString::from(""), + OsString::from_vec(vec![0x2d, 0x61, 0xe9]), + ]); + assert_eq!( + r.unwrap(), + NamedOs { + arg: OsString::from_vec(vec![0xe9]) + } + ); +} + +#[test] +fn invalid_utf8_option_long_space() { + let r = NamedOs::try_parse_from(vec![ + OsString::from(""), + OsString::from("--arg"), + OsString::from_vec(vec![0xe9]), + ]); + assert_eq!( + r.unwrap(), + NamedOs { + arg: OsString::from_vec(vec![0xe9]) + } + ); +} + +#[test] +fn invalid_utf8_option_long_equals() { + let r = NamedOs::try_parse_from(vec![ + OsString::from(""), + OsString::from_vec(vec![0x2d, 0x2d, 0x61, 0x72, 0x67, 0x3d, 0xe9]), + ]); + assert_eq!( + r.unwrap(), + NamedOs { + arg: OsString::from_vec(vec![0xe9]) + } + ); +} + +#[derive(Debug, PartialEq, Parser)] +enum External { + #[clap(external_subcommand)] + Other(Vec), +} + +#[test] +fn refuse_invalid_utf8_subcommand_with_allow_external_subcommands() { + let m = External::try_parse_from(vec![ + OsString::from(""), + OsString::from_vec(vec![0xe9]), + OsString::from("normal"), + ]); + assert!(m.is_err()); + assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8); +} + +#[test] +fn refuse_invalid_utf8_subcommand_args_with_allow_external_subcommands() { + let m = External::try_parse_from(vec![ + OsString::from(""), + OsString::from("subcommand"), + OsString::from("normal"), + OsString::from_vec(vec![0xe9]), + OsString::from("--another_normal"), + ]); + assert!(m.is_err()); + assert_eq!(m.unwrap_err().kind(), ErrorKind::InvalidUtf8); +} + +#[derive(Debug, PartialEq, Parser)] +enum ExternalOs { + #[clap(external_subcommand)] + Other(Vec), +} + +#[test] +fn allow_invalid_utf8_subcommand_args_with_allow_external_subcommands() { + let m = ExternalOs::try_parse_from(vec![ + OsString::from(""), + OsString::from("subcommand"), + OsString::from("normal"), + OsString::from_vec(vec![0xe9]), + OsString::from("--another_normal"), + ]); + assert_eq!( + m.unwrap(), + ExternalOs::Other(vec![ + OsString::from("subcommand"), + OsString::from("normal"), + OsString::from_vec(vec![0xe9]), + OsString::from("--another_normal"), + ]) + ); +} diff --git a/tests/derive/next/utils.rs b/tests/derive/next/utils.rs new file mode 100644 index 00000000000..6b649c3e07c --- /dev/null +++ b/tests/derive/next/utils.rs @@ -0,0 +1,56 @@ +// Hi, future me (or whoever you are)! +// +// Yes, we do need this attr. +// No, the warnings cannot be fixed otherwise. +// Accept and endure. Do not touch. +#![allow(unused)] + +use clap::CommandFactory; + +pub fn get_help() -> String { + let mut output = Vec::new(); + ::command() + .write_help(&mut output) + .unwrap(); + let output = String::from_utf8(output).unwrap(); + + eprintln!("\n%%% HELP %%%:=====\n{}\n=====\n", output); + eprintln!("\n%%% HELP (DEBUG) %%%:=====\n{:?}\n=====\n", output); + + output +} + +pub fn get_long_help() -> String { + let mut output = Vec::new(); + ::command() + .write_long_help(&mut output) + .unwrap(); + let output = String::from_utf8(output).unwrap(); + + eprintln!("\n%%% LONG_HELP %%%:=====\n{}\n=====\n", output); + eprintln!("\n%%% LONG_HELP (DEBUG) %%%:=====\n{:?}\n=====\n", output); + + output +} + +pub fn get_subcommand_long_help(subcmd: &str) -> String { + let mut output = Vec::new(); + ::command() + .get_subcommands_mut() + .find(|s| s.get_name() == subcmd) + .unwrap() + .write_long_help(&mut output) + .unwrap(); + let output = String::from_utf8(output).unwrap(); + + eprintln!( + "\n%%% SUBCOMMAND `{}` HELP %%%:=====\n{}\n=====\n", + subcmd, output + ); + eprintln!( + "\n%%% SUBCOMMAND `{}` HELP (DEBUG) %%%:=====\n{:?}\n=====\n", + subcmd, output + ); + + output +} diff --git a/tests/derive/next/value_enum.rs b/tests/derive/next/value_enum.rs new file mode 100644 index 00000000000..4f93f57d1ba --- /dev/null +++ b/tests/derive/next/value_enum.rs @@ -0,0 +1,527 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) , +// Kevin Knapp (@kbknapp) , and +// Ana Hobden (@hoverbear) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +use clap::Parser; + +#[test] +fn basic() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + enum ArgChoice { + Foo, + Bar, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_enum, value_parser)] + arg: ArgChoice, + } + + assert_eq!( + Opt { + arg: ArgChoice::Foo + }, + Opt::try_parse_from(&["", "foo"]).unwrap() + ); + assert_eq!( + Opt { + arg: ArgChoice::Bar + }, + Opt::try_parse_from(&["", "bar"]).unwrap() + ); + assert!(Opt::try_parse_from(&["", "fOo"]).is_err()); +} + +#[test] +fn default_value() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + enum ArgChoice { + Foo, + Bar, + } + + impl Default for ArgChoice { + fn default() -> Self { + Self::Bar + } + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_enum, value_parser, default_value_t)] + arg: ArgChoice, + } + + assert_eq!( + Opt { + arg: ArgChoice::Foo + }, + Opt::try_parse_from(&["", "foo"]).unwrap() + ); + assert_eq!( + Opt { + arg: ArgChoice::Bar + }, + Opt::try_parse_from(&["", "bar"]).unwrap() + ); + assert_eq!( + Opt { + arg: ArgChoice::Bar + }, + Opt::try_parse_from(&[""]).unwrap() + ); +} + +#[test] +fn multi_word_is_renamed_kebab() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + #[allow(non_camel_case_types)] + enum ArgChoice { + FooBar, + BAR_BAZ, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_enum, value_parser)] + arg: ArgChoice, + } + + assert_eq!( + Opt { + arg: ArgChoice::FooBar + }, + Opt::try_parse_from(&["", "foo-bar"]).unwrap() + ); + assert_eq!( + Opt { + arg: ArgChoice::BAR_BAZ + }, + Opt::try_parse_from(&["", "bar-baz"]).unwrap() + ); + assert!(Opt::try_parse_from(&["", "FooBar"]).is_err()); +} + +#[test] +fn variant_with_defined_casing() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + enum ArgChoice { + #[clap(rename_all = "screaming_snake")] + FooBar, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_enum, value_parser)] + arg: ArgChoice, + } + + assert_eq!( + Opt { + arg: ArgChoice::FooBar + }, + Opt::try_parse_from(&["", "FOO_BAR"]).unwrap() + ); + assert!(Opt::try_parse_from(&["", "FooBar"]).is_err()); +} + +#[test] +fn casing_is_propagated_from_parent() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + #[clap(rename_all = "screaming_snake")] + enum ArgChoice { + FooBar, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_enum, value_parser)] + arg: ArgChoice, + } + + assert_eq!( + Opt { + arg: ArgChoice::FooBar + }, + Opt::try_parse_from(&["", "FOO_BAR"]).unwrap() + ); + assert!(Opt::try_parse_from(&["", "FooBar"]).is_err()); +} + +#[test] +fn casing_propagation_is_overridden() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + #[clap(rename_all = "screaming_snake")] + enum ArgChoice { + #[clap(rename_all = "camel")] + FooBar, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_enum, value_parser)] + arg: ArgChoice, + } + + assert_eq!( + Opt { + arg: ArgChoice::FooBar + }, + Opt::try_parse_from(&["", "fooBar"]).unwrap() + ); + assert!(Opt::try_parse_from(&["", "FooBar"]).is_err()); + assert!(Opt::try_parse_from(&["", "FOO_BAR"]).is_err()); +} + +#[test] +fn ignore_case() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + enum ArgChoice { + Foo, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_enum, value_parser, ignore_case(true))] + arg: ArgChoice, + } + + assert_eq!( + Opt { + arg: ArgChoice::Foo + }, + Opt::try_parse_from(&["", "foo"]).unwrap() + ); + assert_eq!( + Opt { + arg: ArgChoice::Foo + }, + Opt::try_parse_from(&["", "fOo"]).unwrap() + ); +} + +#[test] +fn ignore_case_set_to_false() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + enum ArgChoice { + Foo, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_enum, ignore_case(false), value_parser)] + arg: ArgChoice, + } + + assert_eq!( + Opt { + arg: ArgChoice::Foo + }, + Opt::try_parse_from(&["", "foo"]).unwrap() + ); + assert!(Opt::try_parse_from(&["", "fOo"]).is_err()); +} + +#[test] +fn alias() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + enum ArgChoice { + #[clap(alias = "TOTP")] + Totp, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_enum, ignore_case(false), value_parser)] + arg: ArgChoice, + } + + assert_eq!( + Opt { + arg: ArgChoice::Totp + }, + Opt::try_parse_from(&["", "totp"]).unwrap() + ); + assert_eq!( + Opt { + arg: ArgChoice::Totp + }, + Opt::try_parse_from(&["", "TOTP"]).unwrap() + ); +} + +#[test] +fn multiple_alias() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + enum ArgChoice { + #[clap(alias = "TOTP", alias = "t")] + Totp, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_enum, ignore_case(false), value_parser)] + arg: ArgChoice, + } + + assert_eq!( + Opt { + arg: ArgChoice::Totp + }, + Opt::try_parse_from(&["", "totp"]).unwrap() + ); + assert_eq!( + Opt { + arg: ArgChoice::Totp + }, + Opt::try_parse_from(&["", "TOTP"]).unwrap() + ); + assert_eq!( + Opt { + arg: ArgChoice::Totp + }, + Opt::try_parse_from(&["", "t"]).unwrap() + ); +} + +#[test] +fn skip_variant() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + #[allow(dead_code)] // silence warning about `Baz` being unused + enum ArgChoice { + Foo, + Bar, + #[clap(skip)] + Baz, + } + + assert_eq!( + ::value_variants() + .iter() + .map(clap::ValueEnum::to_possible_value) + .map(Option::unwrap) + .collect::>(), + vec![ + clap::PossibleValue::new("foo"), + clap::PossibleValue::new("bar") + ] + ); + + { + use clap::ValueEnum; + assert!(ArgChoice::from_str("foo", true).is_ok()); + assert!(ArgChoice::from_str("bar", true).is_ok()); + assert!(ArgChoice::from_str("baz", true).is_err()); + } +} + +#[test] +fn skip_non_unit_variant() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + #[allow(dead_code)] // silence warning about `Baz` being unused + enum ArgChoice { + Foo, + Bar, + #[clap(skip)] + Baz(usize), + } + + assert_eq!( + ::value_variants() + .iter() + .map(clap::ValueEnum::to_possible_value) + .map(Option::unwrap) + .collect::>(), + vec![ + clap::PossibleValue::new("foo"), + clap::PossibleValue::new("bar") + ] + ); + + { + use clap::ValueEnum; + assert!(ArgChoice::from_str("foo", true).is_ok()); + assert!(ArgChoice::from_str("bar", true).is_ok()); + assert!(ArgChoice::from_str("baz", true).is_err()); + } +} + +#[test] +fn from_str_invalid() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + enum ArgChoice { + Foo, + } + + { + use clap::ValueEnum; + assert!(ArgChoice::from_str("bar", true).is_err()); + } +} + +#[test] +fn option_type() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + enum ArgChoice { + Foo, + Bar, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_enum, value_parser)] + arg: Option, + } + + assert_eq!(Opt { arg: None }, Opt::try_parse_from(&[""]).unwrap()); + assert_eq!( + Opt { + arg: Some(ArgChoice::Foo) + }, + Opt::try_parse_from(&["", "foo"]).unwrap() + ); + assert_eq!( + Opt { + arg: Some(ArgChoice::Bar) + }, + Opt::try_parse_from(&["", "bar"]).unwrap() + ); + assert!(Opt::try_parse_from(&["", "fOo"]).is_err()); +} + +#[test] +fn option_option_type() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + enum ArgChoice { + Foo, + Bar, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_enum, long, value_parser)] + arg: Option>, + } + + assert_eq!(Opt { arg: None }, Opt::try_parse_from(&[""]).unwrap()); + assert_eq!( + Opt { arg: Some(None) }, + Opt::try_parse_from(&["", "--arg"]).unwrap() + ); + assert_eq!( + Opt { + arg: Some(Some(ArgChoice::Foo)) + }, + Opt::try_parse_from(&["", "--arg", "foo"]).unwrap() + ); + assert_eq!( + Opt { + arg: Some(Some(ArgChoice::Bar)) + }, + Opt::try_parse_from(&["", "--arg", "bar"]).unwrap() + ); + assert!(Opt::try_parse_from(&["", "--arg", "fOo"]).is_err()); +} + +#[test] +fn vec_type() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + enum ArgChoice { + Foo, + Bar, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_enum, short, long, value_parser)] + arg: Vec, + } + + assert_eq!(Opt { arg: vec![] }, Opt::try_parse_from(&[""]).unwrap()); + assert_eq!( + Opt { + arg: vec![ArgChoice::Foo] + }, + Opt::try_parse_from(&["", "-a", "foo"]).unwrap() + ); + assert_eq!( + Opt { + arg: vec![ArgChoice::Foo, ArgChoice::Bar] + }, + Opt::try_parse_from(&["", "-a", "foo", "-a", "bar"]).unwrap() + ); + assert!(Opt::try_parse_from(&["", "-a", "fOo"]).is_err()); +} + +#[test] +fn option_vec_type() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + enum ArgChoice { + Foo, + Bar, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(value_enum, short, long, value_parser)] + arg: Option>, + } + + assert_eq!(Opt { arg: None }, Opt::try_parse_from(&[""]).unwrap()); + assert_eq!( + Opt { + arg: Some(vec![ArgChoice::Foo]) + }, + Opt::try_parse_from(&["", "-a", "foo"]).unwrap() + ); + assert_eq!( + Opt { + arg: Some(vec![ArgChoice::Foo, ArgChoice::Bar]) + }, + Opt::try_parse_from(&["", "-a", "foo", "-a", "bar"]).unwrap() + ); + assert!(Opt::try_parse_from(&["", "-a", "fOo"]).is_err()); +} + +#[test] +fn vec_type_default_value() { + #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] + enum ArgChoice { + Foo, + Bar, + Baz, + } + + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap( + value_enum, + short, + long, + default_value = "foo,bar", + value_delimiter = ',', + value_parser + )] + arg: Vec, + } + + assert_eq!( + Opt { + arg: vec![ArgChoice::Foo, ArgChoice::Bar] + }, + Opt::try_parse_from(&[""]).unwrap() + ); + + assert_eq!( + Opt { + arg: vec![ArgChoice::Foo, ArgChoice::Baz] + }, + Opt::try_parse_from(&["", "-a", "foo,baz"]).unwrap() + ); +} From 0a529c14cc40ee1bddf41df144a0d501d1b67fe0 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 13 Jun 2022 17:04:50 -0500 Subject: [PATCH 5/5] fix(derive): Switch default actions/parsers for unstable-v4 --- clap_derive/src/attrs.rs | 31 ++++++- tests/derive/next/arguments.rs | 8 +- tests/derive/next/basic.rs | 4 +- tests/derive/next/boxed.rs | 1 - tests/derive/next/custom_string_parsers.rs | 14 ++-- tests/derive/next/default_value.rs | 8 +- tests/derive/next/doc_comments_help.rs | 28 +++---- .../derive/next/explicit_name_no_renaming.rs | 4 +- tests/derive/next/flags.rs | 5 +- tests/derive/next/flatten.rs | 17 ++-- tests/derive/next/generic.rs | 7 -- tests/derive/next/help.rs | 55 ++++++------- tests/derive/next/issues.rs | 8 +- tests/derive/next/macros.rs | 2 +- tests/derive/next/naming.rs | 52 ++++++------ tests/derive/next/nested_subcommands.rs | 19 ++--- tests/derive/next/non_literal_attributes.rs | 7 +- tests/derive/next/options.rs | 38 ++++----- tests/derive/next/privacy.rs | 1 - tests/derive/next/raw_bool_literal.rs | 4 +- tests/derive/next/raw_idents.rs | 2 +- tests/derive/next/rename_all_env.rs | 8 +- tests/derive/next/skip.rs | 14 ++-- tests/derive/next/subcommands.rs | 35 +++----- tests/derive/next/type_alias_regressions.rs | 3 +- tests/derive/next/utf8.rs | 6 +- tests/derive/next/value_enum.rs | 31 ++++--- tests/derive_ui/next/bool_value_enum.rs | 2 +- tests/derive_ui/next/bool_value_enum.stderr | 82 ++----------------- .../derive_ui/{ => next}/non_existent_attr.rs | 0 tests/derive_ui/next/non_existent_attr.stderr | 5 ++ tests/derive_ui/stable/non_existent_attr.rs | 21 +++++ .../{ => stable}/non_existent_attr.stderr | 6 +- 33 files changed, 218 insertions(+), 310 deletions(-) rename tests/derive_ui/{ => next}/non_existent_attr.rs (100%) create mode 100644 tests/derive_ui/next/non_existent_attr.stderr create mode 100644 tests/derive_ui/stable/non_existent_attr.rs rename tests/derive_ui/{ => stable}/non_existent_attr.stderr (80%) diff --git a/clap_derive/src/attrs.rs b/clap_derive/src/attrs.rs index 5960b8ac387..ae784b519e3 100644 --- a/clap_derive/src/attrs.rs +++ b/clap_derive/src/attrs.rs @@ -784,9 +784,18 @@ impl Attrs { .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 { + let span = action.span(); + default_value_parser(inner_type, span) + } else if !self.ignore_parser() || cfg!(not(feature = "unstable-v4")) { self.parser(field_type).value_parser() + } else { + let inner_type = inner_type(field_type); + let span = self + .action + .as_ref() + .map(|a| a.span()) + .unwrap_or_else(|| self.kind.span()); + default_value_parser(inner_type, span) } }) } @@ -797,13 +806,27 @@ impl Attrs { .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 { + let span = value_parser.span(); + default_action(field_type, span) + } else if !self.ignore_parser() || cfg!(not(feature = "unstable-v4")) { self.parser(field_type).action() + } else { + let span = self + .value_parser + .as_ref() + .map(|a| a.span()) + .unwrap_or_else(|| self.kind.span()); + default_action(field_type, span) } }) } + #[cfg(feature = "unstable-v4")] + pub fn ignore_parser(&self) -> bool { + self.parser.is_none() + } + + #[cfg(not(feature = "unstable-v4"))] pub fn ignore_parser(&self) -> bool { self.value_parser.is_some() || self.action.is_some() } diff --git a/tests/derive/next/arguments.rs b/tests/derive/next/arguments.rs index 0d3f0b1e262..57c5866d4ee 100644 --- a/tests/derive/next/arguments.rs +++ b/tests/derive/next/arguments.rs @@ -19,7 +19,6 @@ use clap::Parser; fn required_argument() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_parser)] arg: i32, } assert_eq!( @@ -34,7 +33,7 @@ fn required_argument() { fn argument_with_default() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_parser, default_value = "42")] + #[clap(default_value = "42")] arg: i32, } assert_eq!( @@ -49,7 +48,6 @@ fn argument_with_default() { fn auto_value_name() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_parser)] my_special_arg: i32, } @@ -69,7 +67,7 @@ fn auto_value_name() { fn explicit_value_name() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_parser, value_name = "BROWNIE_POINTS")] + #[clap(value_name = "BROWNIE_POINTS")] my_special_arg: i32, } @@ -90,7 +88,6 @@ fn explicit_value_name() { fn option_type_is_optional() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_parser)] arg: Option, } assert_eq!( @@ -105,7 +102,6 @@ fn option_type_is_optional() { fn vec_type_is_multiple_values() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_parser)] arg: Vec, } assert_eq!( diff --git a/tests/derive/next/basic.rs b/tests/derive/next/basic.rs index f32d17791ae..d2e1ca9001a 100644 --- a/tests/derive/next/basic.rs +++ b/tests/derive/next/basic.rs @@ -18,7 +18,7 @@ use clap::Parser; fn basic() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short = 'a', long = "arg", value_parser)] + #[clap(short = 'a', long = "arg")] arg: i32, } assert_eq!( @@ -31,7 +31,7 @@ fn basic() { fn update_basic() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short = 'a', long = "arg", value_parser)] + #[clap(short = 'a', long = "arg")] single_value: i32, } diff --git a/tests/derive/next/boxed.rs b/tests/derive/next/boxed.rs index 0af81f6fcfd..03efbe9c551 100644 --- a/tests/derive/next/boxed.rs +++ b/tests/derive/next/boxed.rs @@ -16,7 +16,6 @@ enum Sub { #[derive(Args, PartialEq, Debug)] struct Ext { - #[clap(value_parser)] arg: u32, } diff --git a/tests/derive/next/custom_string_parsers.rs b/tests/derive/next/custom_string_parsers.rs index ef7f0e88a6b..e0ce4ac9965 100644 --- a/tests/derive/next/custom_string_parsers.rs +++ b/tests/derive/next/custom_string_parsers.rs @@ -22,19 +22,19 @@ use std::path::PathBuf; #[derive(Parser, PartialEq, Debug)] struct PathOpt { - #[clap(short, long, value_parser)] + #[clap(short, long)] path: PathBuf, - #[clap(short, default_value = "../", value_parser)] + #[clap(short, default_value = "../")] default_path: PathBuf, - #[clap(short, value_parser, multiple_occurrences(true))] + #[clap(short, multiple_occurrences(true))] vector_path: Vec, - #[clap(short, value_parser)] + #[clap(short)] option_path_1: Option, - #[clap(short = 'q', value_parser)] + #[clap(short = 'q')] option_path_2: Option, } @@ -135,10 +135,10 @@ struct DefaultedOpt { #[clap(short, parse(from_str))] bytes: Bytes, - #[clap(short, value_parser)] + #[clap(short)] integer: u64, - #[clap(short, value_parser)] + #[clap(short)] path: PathBuf, } diff --git a/tests/derive/next/default_value.rs b/tests/derive/next/default_value.rs index fc07e4c1128..db2b7838014 100644 --- a/tests/derive/next/default_value.rs +++ b/tests/derive/next/default_value.rs @@ -6,7 +6,7 @@ use crate::utils; fn default_value() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_parser, default_value = "3")] + #[clap(default_value = "3")] arg: i32, } assert_eq!(Opt { arg: 3 }, Opt::try_parse_from(&["test"]).unwrap()); @@ -20,7 +20,7 @@ fn default_value() { fn default_value_t() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_parser, default_value_t = 3)] + #[clap(default_value_t = 3)] arg: i32, } assert_eq!(Opt { arg: 3 }, Opt::try_parse_from(&["test"]).unwrap()); @@ -34,7 +34,7 @@ fn default_value_t() { fn auto_default_value_t() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_parser, default_value_t)] + #[clap(default_value_t)] arg: i32, } assert_eq!(Opt { arg: 0 }, Opt::try_parse_from(&["test"]).unwrap()); @@ -50,7 +50,7 @@ fn detect_os_variant() { #[derive(clap::Parser)] pub struct Options { - #[clap(value_parser, default_value_os = ("123".as_ref()))] + #[clap(default_value_os = ("123".as_ref()))] x: String, } Options::command().debug_assert(); diff --git a/tests/derive/next/doc_comments_help.rs b/tests/derive/next/doc_comments_help.rs index e6aab515ce8..bfe152a6102 100644 --- a/tests/derive/next/doc_comments_help.rs +++ b/tests/derive/next/doc_comments_help.rs @@ -23,7 +23,7 @@ fn doc_comments() { struct LoremIpsum { /// Fooify a bar /// and a baz - #[clap(short, long, action)] + #[clap(short, long)] foo: bool, } @@ -39,12 +39,7 @@ 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", - action - )] + #[clap(short, long, help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")] foo: bool, } @@ -76,11 +71,11 @@ fn field_long_doc_comment_both_help_long_help() { /// Dot is removed from multiline comments. /// /// Long help - #[clap(long, action)] + #[clap(long)] foo: bool, /// Dot is removed from one short comment. - #[clap(long, action)] + #[clap(long)] bar: bool, } @@ -111,7 +106,7 @@ fn top_long_doc_comment_both_help_long_help() { /// /// Or something else Foo { - #[clap(value_parser, help = "foo")] + #[clap(help = "foo")] bars: String, }, } @@ -146,7 +141,7 @@ fn verbatim_doc_comment() { #[derive(Parser, Debug)] #[clap(verbatim_doc_comment)] struct SeeFigure1 { - #[clap(long, action)] + #[clap(long)] foo: bool, } @@ -176,10 +171,10 @@ fn verbatim_doc_comment_field() { #[derive(Parser, Debug)] struct Command { /// This help ends in a period. - #[clap(long, verbatim_doc_comment, action)] + #[clap(long, verbatim_doc_comment)] foo: bool, /// This help does not end in a period. - #[clap(long, action)] + #[clap(long)] bar: bool, } @@ -196,7 +191,7 @@ fn multiline_separates_default() { /// Multiline /// /// Doc comment - #[clap(long, default_value = "x", value_parser)] + #[clap(long, default_value = "x")] x: String, } @@ -234,10 +229,7 @@ fn doc_comment_about_handles_both_abouts() { /// Sub doc comment body #[derive(Parser, PartialEq, Eq, Debug)] pub enum Sub { - Compress { - #[clap(value_parser)] - output: String, - }, + Compress { output: String }, } let cmd = Opts::command(); diff --git a/tests/derive/next/explicit_name_no_renaming.rs b/tests/derive/next/explicit_name_no_renaming.rs index 15402e85ab4..dcdbc3820c7 100644 --- a/tests/derive/next/explicit_name_no_renaming.rs +++ b/tests/derive/next/explicit_name_no_renaming.rs @@ -6,7 +6,7 @@ use clap::Parser; fn explicit_short_long_no_rename() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short = '.', long = ".foo", value_parser)] + #[clap(short = '.', long = ".foo")] foo: String, } @@ -27,7 +27,7 @@ fn explicit_short_long_no_rename() { fn explicit_name_no_rename() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(name = ".options", value_parser)] + #[clap(name = ".options")] foo: String, } diff --git a/tests/derive/next/flags.rs b/tests/derive/next/flags.rs index d8e55290d0a..02bcc45b478 100644 --- a/tests/derive/next/flags.rs +++ b/tests/derive/next/flags.rs @@ -20,7 +20,7 @@ use clap::Parser; fn bool_type_is_flag() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, long, action)] + #[clap(short, long)] alice: bool, } @@ -113,7 +113,7 @@ fn non_bool_type_flag() { fn mixed_type_flags() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, long, action)] + #[clap(short, long)] alice: bool, #[clap(short, long, action = clap::ArgAction::Count)] bob: u8, @@ -181,7 +181,6 @@ fn ignore_qualified_bool_type() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(action)] arg: inner::bool, } diff --git a/tests/derive/next/flatten.rs b/tests/derive/next/flatten.rs index df451265baa..77561997bd6 100644 --- a/tests/derive/next/flatten.rs +++ b/tests/derive/next/flatten.rs @@ -20,7 +20,6 @@ use clap::{Args, Parser, Subcommand}; fn flatten() { #[derive(Args, PartialEq, Debug)] struct Common { - #[clap(value_parser)] arg: i32, } @@ -45,7 +44,6 @@ fn flatten() { fn flatten_twice() { #[derive(Args, PartialEq, Debug)] struct Common { - #[clap(value_parser)] arg: i32, } @@ -64,13 +62,12 @@ fn flatten_twice() { fn flatten_in_subcommand() { #[derive(Args, PartialEq, Debug)] struct Common { - #[clap(value_parser)] arg: i32, } #[derive(Args, PartialEq, Debug)] struct Add { - #[clap(short, action)] + #[clap(short)] interactive: bool, #[clap(flatten)] common: Common, @@ -79,7 +76,7 @@ fn flatten_in_subcommand() { #[derive(Parser, PartialEq, Debug)] enum Opt { Fetch { - #[clap(short, action)] + #[clap(short)] all: bool, #[clap(flatten)] common: Common, @@ -108,7 +105,6 @@ fn flatten_in_subcommand() { fn update_args_with_flatten() { #[derive(Args, PartialEq, Debug)] struct Common { - #[clap(value_parser)] arg: i32, } @@ -138,15 +134,13 @@ enum BaseCli { #[derive(Args, PartialEq, Debug)] struct Command1 { - #[clap(value_parser)] arg1: i32, - #[clap(value_parser)] + arg2: i32, } #[derive(Args, PartialEq, Debug)] struct Command2 { - #[clap(value_parser)] arg2: i32, } @@ -199,7 +193,6 @@ fn flatten_with_doc_comment() { #[derive(Args, PartialEq, Debug)] struct Common { /// This is an arg. Arg means "argument". Command line argument. - #[clap(value_parser)] arg: i32, } @@ -227,7 +220,7 @@ fn docstrings_ordering_with_multiple_clap() { /// This is the docstring for Flattened #[derive(Args)] struct Flattened { - #[clap(long, action)] + #[clap(long)] foo: bool, } @@ -248,7 +241,7 @@ fn docstrings_ordering_with_multiple_clap_partial() { /// This is the docstring for Flattened #[derive(Args)] struct Flattened { - #[clap(long, action)] + #[clap(long)] foo: bool, } diff --git a/tests/derive/next/generic.rs b/tests/derive/next/generic.rs index cea94729083..5d484295148 100644 --- a/tests/derive/next/generic.rs +++ b/tests/derive/next/generic.rs @@ -4,7 +4,6 @@ use clap::{Args, Parser}; fn generic_struct_flatten() { #[derive(Args, PartialEq, Debug)] struct Inner { - #[clap(value_parser)] pub answer: isize, } @@ -26,7 +25,6 @@ fn generic_struct_flatten() { fn generic_struct_flatten_w_where_clause() { #[derive(Args, PartialEq, Debug)] struct Inner { - #[clap(value_parser)] pub answer: isize, } @@ -51,7 +49,6 @@ fn generic_struct_flatten_w_where_clause() { fn generic_enum() { #[derive(Args, PartialEq, Debug)] struct Inner { - #[clap(value_parser)] pub answer: isize, } @@ -71,7 +68,6 @@ fn generic_enum() { fn generic_enum_w_where_clause() { #[derive(Args, PartialEq, Debug)] struct Inner { - #[clap(value_parser)] pub answer: isize, } @@ -100,7 +96,6 @@ fn generic_w_fromstr_trait_bound() { T: FromStr + Send + Sync + Clone + 'static, ::Err: std::error::Error + Sync + Send + 'static, { - #[clap(value_parser)] answer: T, } @@ -116,7 +111,6 @@ fn generic_wo_trait_bound() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_parser)] answer: isize, #[clap(skip)] took: Option, @@ -141,7 +135,6 @@ fn generic_where_clause_w_trailing_comma() { T: FromStr + Send + Sync + Clone + 'static, ::Err: std::error::Error + Sync + Send + 'static, { - #[clap(value_parser)] pub answer: T, } diff --git a/tests/derive/next/help.rs b/tests/derive/next/help.rs index 8496248949b..14b6a876d77 100644 --- a/tests/derive/next/help.rs +++ b/tests/derive/next/help.rs @@ -4,11 +4,11 @@ use clap::{AppSettings, Args, CommandFactory, Parser, Subcommand}; fn arg_help_heading_applied() { #[derive(Debug, Clone, Parser)] struct CliOptions { - #[clap(long, value_parser)] + #[clap(long)] #[clap(help_heading = Some("HEADING A"))] should_be_in_section_a: u32, - #[clap(long, value_parser)] + #[clap(long)] no_section: u32, } @@ -42,11 +42,11 @@ fn app_help_heading_applied() { #[derive(Debug, Clone, Parser)] #[clap(next_help_heading = "DEFAULT")] struct CliOptions { - #[clap(long, value_parser)] + #[clap(long)] #[clap(help_heading = Some("HEADING A"))] should_be_in_section_a: u32, - #[clap(long, value_parser)] + #[clap(long)] should_be_in_default_section: u32, } @@ -94,21 +94,21 @@ fn app_help_heading_flattened() { #[clap(subcommand)] sub_a: SubA, - #[clap(long, value_parser)] + #[clap(long)] should_be_in_default_section: u32, } #[derive(Debug, Clone, Args)] #[clap(next_help_heading = "HEADING A")] struct OptionsA { - #[clap(long, value_parser)] + #[clap(long)] should_be_in_section_a: u32, } #[derive(Debug, Clone, Args)] #[clap(next_help_heading = "HEADING B")] struct OptionsB { - #[clap(long, value_parser)] + #[clap(long)] should_be_in_section_b: u32, } @@ -121,7 +121,6 @@ fn app_help_heading_flattened() { SubAOne, #[clap(next_help_heading = "SUB A")] SubATwo { - #[clap(value_parser)] should_be_in_sub_a: u32, }, } @@ -129,19 +128,13 @@ fn app_help_heading_flattened() { #[derive(Debug, Clone, Subcommand)] enum SubB { #[clap(next_help_heading = "SUB B")] - SubBOne { - #[clap(value_parser)] - should_be_in_sub_b: u32, - }, + SubBOne { should_be_in_sub_b: u32 }, } #[derive(Debug, Clone, Subcommand)] enum SubC { #[clap(next_help_heading = "SUB C")] - SubCOne { - #[clap(value_parser)] - should_be_in_sub_c: u32, - }, + SubCOne { should_be_in_sub_c: u32 }, } let cmd = CliOptions::command(); @@ -237,7 +230,7 @@ fn flatten_field_with_help_heading() { #[derive(Debug, Clone, Args)] struct OptionsA { - #[clap(long, value_parser)] + #[clap(long)] should_be_in_section_a: u32, } @@ -264,7 +257,7 @@ fn derive_generated_error_has_full_context() { #[derive(Debug, Parser)] #[clap(subcommand_negates_reqs = true)] struct Opts { - #[clap(long, value_parser)] + #[clap(long)] req_str: String, #[clap(subcommand)] @@ -339,10 +332,10 @@ OPTIONS: #[clap(next_display_order = 10000)] struct A { /// second flag - #[clap(long, action)] + #[clap(long)] flag_a: bool, /// second option - #[clap(long, value_parser)] + #[clap(long)] option_a: Option, } @@ -350,10 +343,10 @@ OPTIONS: #[clap(next_display_order = 10)] struct B { /// first flag - #[clap(long, action)] + #[clap(long)] flag_b: bool, /// first option - #[clap(long, value_parser)] + #[clap(long)] option_b: Option, } @@ -397,20 +390,20 @@ OPTIONS: #[derive(Args, Debug)] struct A { /// second flag - #[clap(long, action)] + #[clap(long)] flag_a: bool, /// second option - #[clap(long, value_parser)] + #[clap(long)] option_a: Option, } #[derive(Args, Debug)] struct B { /// first flag - #[clap(long, action)] + #[clap(long)] flag_b: bool, /// first option - #[clap(long, value_parser)] + #[clap(long)] option_b: Option, } @@ -453,20 +446,20 @@ OPTIONS: #[derive(Args, Debug)] struct A { /// first flag - #[clap(long, action)] + #[clap(long)] flag_a: bool, /// first option - #[clap(long, value_parser)] + #[clap(long)] option_a: Option, } #[derive(Args, Debug)] struct B { /// second flag - #[clap(long, action)] + #[clap(long)] flag_b: bool, /// second option - #[clap(long, value_parser)] + #[clap(long)] option_b: Option, } @@ -505,7 +498,7 @@ OPTIONS: #[derive(Parser, PartialEq, Debug)] struct Args { /// Argument help - #[clap(value_enum, value_parser)] + #[clap(value_enum)] arg: ArgChoice, } diff --git a/tests/derive/next/issues.rs b/tests/derive/next/issues.rs index 22c93c87172..d2c01363b14 100644 --- a/tests/derive/next/issues.rs +++ b/tests/derive/next/issues.rs @@ -9,9 +9,9 @@ fn issue_151_groups_within_subcommands() { #[derive(Args, Debug)] #[clap(group = ArgGroup::new("verb").required(true).multiple(true))] struct Opt { - #[clap(long, group = "verb", value_parser)] + #[clap(long, group = "verb")] foo: Option, - #[clap(long, group = "verb", value_parser)] + #[clap(long, group = "verb")] bar: Option, } @@ -89,7 +89,6 @@ fn issue_418() { #[clap(visible_alias = "ret")] Reticulate { /// How many splines - #[clap(value_parser)] num_splines: u8, }, /// Frobnicate the rest @@ -122,9 +121,8 @@ fn issue_490() { #[derive(Parser, Debug)] struct Opt { - #[clap(value_parser)] opt_vec: Vec, - #[clap(long, value_parser)] + #[clap(long)] opt_opt_vec: Option>, } diff --git a/tests/derive/next/macros.rs b/tests/derive/next/macros.rs index 5dbec71a39c..a2e671b4abc 100644 --- a/tests/derive/next/macros.rs +++ b/tests/derive/next/macros.rs @@ -22,7 +22,7 @@ fn use_option() { ($name:ident: $ty:ty) => { #[derive(Parser)] struct Outer { - #[clap(short, long, value_parser)] + #[clap(short, long)] #[allow(dead_code)] $name: $ty, } diff --git a/tests/derive/next/naming.rs b/tests/derive/next/naming.rs index d0b897f7e89..e3018a02f7c 100644 --- a/tests/derive/next/naming.rs +++ b/tests/derive/next/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, action)] + #[clap(long)] 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", action)] + #[clap(long = "foo")] foo_option: bool, } @@ -33,7 +33,7 @@ fn test_custom_long_overwrites_default_name() { fn test_standalone_long_uses_previous_defined_custom_name() { #[derive(Parser, Debug, PartialEq)] struct Opt { - #[clap(name = "foo", long, action)] + #[clap(name = "foo", long)] foo_option: bool, } @@ -47,7 +47,7 @@ fn test_standalone_long_uses_previous_defined_custom_name() { fn test_standalone_long_ignores_afterwards_defined_custom_name() { #[derive(Parser, Debug, PartialEq)] struct Opt { - #[clap(long, name = "foo", action)] + #[clap(long, name = "foo")] foo_option: bool, } @@ -62,7 +62,7 @@ fn test_standalone_short_generates_kebab_case() { #[derive(Parser, Debug, PartialEq)] #[allow(non_snake_case)] struct Opt { - #[clap(short, action)] + #[clap(short)] FOO_OPTION: bool, } @@ -76,7 +76,7 @@ fn test_standalone_short_generates_kebab_case() { fn test_custom_short_overwrites_default_name() { #[derive(Parser, Debug, PartialEq)] struct Opt { - #[clap(short = 'o', action)] + #[clap(short = 'o')] foo_option: bool, } @@ -90,7 +90,7 @@ fn test_custom_short_overwrites_default_name() { fn test_standalone_short_uses_previous_defined_custom_name() { #[derive(Parser, Debug, PartialEq)] struct Opt { - #[clap(name = "option", short, action)] + #[clap(name = "option", short)] foo_option: bool, } @@ -104,7 +104,7 @@ fn test_standalone_short_uses_previous_defined_custom_name() { fn test_standalone_short_ignores_afterwards_defined_custom_name() { #[derive(Parser, Debug, PartialEq)] struct Opt { - #[clap(short, name = "option", action)] + #[clap(short, name = "option")] foo_option: bool, } @@ -118,7 +118,7 @@ fn test_standalone_short_ignores_afterwards_defined_custom_name() { fn test_standalone_long_uses_previous_defined_casing() { #[derive(Parser, Debug, PartialEq)] struct Opt { - #[clap(rename_all = "screaming_snake", long, action)] + #[clap(rename_all = "screaming_snake", long)] foo_option: bool, } @@ -132,7 +132,7 @@ fn test_standalone_long_uses_previous_defined_casing() { fn test_standalone_short_uses_previous_defined_casing() { #[derive(Parser, Debug, PartialEq)] struct Opt { - #[clap(rename_all = "screaming_snake", short, action)] + #[clap(rename_all = "screaming_snake", short)] foo_option: bool, } @@ -147,7 +147,7 @@ fn test_standalone_long_works_with_verbatim_casing() { #[derive(Parser, Debug, PartialEq)] #[allow(non_snake_case)] struct Opt { - #[clap(rename_all = "verbatim", long, action)] + #[clap(rename_all = "verbatim", long)] _fOO_oPtiON: bool, } @@ -161,7 +161,7 @@ fn test_standalone_long_works_with_verbatim_casing() { fn test_standalone_short_works_with_verbatim_casing() { #[derive(Parser, Debug, PartialEq)] struct Opt { - #[clap(rename_all = "verbatim", short, action)] + #[clap(rename_all = "verbatim", short)] _foo: bool, } @@ -176,7 +176,7 @@ fn test_rename_all_is_propagated_from_struct_to_fields() { #[derive(Parser, Debug, PartialEq)] #[clap(rename_all = "screaming_snake")] struct Opt { - #[clap(long, action)] + #[clap(long)] foo: bool, } @@ -197,7 +197,7 @@ fn test_rename_all_is_not_propagated_from_struct_into_flattened() { #[derive(Parser, Debug, PartialEq)] struct Foo { - #[clap(long, action)] + #[clap(long)] foo: bool, } @@ -213,7 +213,7 @@ fn test_rename_all_is_not_propagated_from_struct_into_flattened() { fn test_lower_is_renamed() { #[derive(Parser, Debug, PartialEq)] struct Opt { - #[clap(rename_all = "lower", long, action)] + #[clap(rename_all = "lower", long)] foo_option: bool, } @@ -227,7 +227,7 @@ fn test_lower_is_renamed() { fn test_upper_is_renamed() { #[derive(Parser, Debug, PartialEq)] struct Opt { - #[clap(rename_all = "upper", long, action)] + #[clap(rename_all = "upper", long)] foo_option: bool, } @@ -241,10 +241,7 @@ fn test_upper_is_renamed() { fn test_single_word_enum_variant_is_default_renamed_into_kebab_case() { #[derive(Parser, Debug, PartialEq)] enum Opt { - Command { - #[clap(value_parser)] - foo: u32, - }, + Command { foo: u32 }, } assert_eq!( @@ -257,10 +254,7 @@ fn test_single_word_enum_variant_is_default_renamed_into_kebab_case() { fn test_multi_word_enum_variant_is_renamed() { #[derive(Parser, Debug, PartialEq)] enum Opt { - FirstCommand { - #[clap(value_parser)] - foo: u32, - }, + FirstCommand { foo: u32 }, } assert_eq!( @@ -281,7 +275,7 @@ fn test_rename_all_is_not_propagated_from_struct_into_subcommand() { #[derive(Parser, Debug, PartialEq)] enum Foo { Command { - #[clap(long, action)] + #[clap(long)] foo: bool, }, } @@ -301,7 +295,7 @@ fn test_rename_all_is_propagated_from_enum_to_variants() { enum Opt { FirstVariant, SecondVariant { - #[clap(long, value_parser)] + #[clap(long)] foo: String, }, } @@ -319,7 +313,7 @@ fn test_rename_all_is_propagated_from_enum_to_variant_fields() { enum Opt { FirstVariant, SecondVariant { - #[clap(long, value_parser)] + #[clap(long)] foo: String, }, } @@ -339,11 +333,11 @@ fn test_rename_all_is_propagation_can_be_overridden() { enum Opt { #[clap(rename_all = "kebab_case")] FirstVariant { - #[clap(long, action)] + #[clap(long)] foo_option: bool, }, SecondVariant { - #[clap(rename_all = "kebab_case", long, action)] + #[clap(rename_all = "kebab_case", long)] foo_option: bool, }, } diff --git a/tests/derive/next/nested_subcommands.rs b/tests/derive/next/nested_subcommands.rs index 59a5ad57053..e5e32bfff7a 100644 --- a/tests/derive/next/nested_subcommands.rs +++ b/tests/derive/next/nested_subcommands.rs @@ -16,7 +16,7 @@ use clap::{Parser, Subcommand}; #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, long, action)] + #[clap(short, long)] force: bool, #[clap(short, long, action = clap::ArgAction::Count)] verbose: u8, @@ -32,7 +32,7 @@ enum Sub { #[derive(Parser, PartialEq, Debug)] struct Opt2 { - #[clap(short, long, action)] + #[clap(short, long)] force: bool, #[clap(short, long, action = clap::ArgAction::Count)] verbose: u8, @@ -109,7 +109,7 @@ fn test_badinput() { #[derive(Parser, PartialEq, Debug)] struct Opt3 { - #[clap(short, long, action)] + #[clap(short, long)] all: bool, #[clap(subcommand)] cmd: Sub2, @@ -118,7 +118,6 @@ struct Opt3 { #[derive(Subcommand, PartialEq, Debug)] enum Sub2 { Foo { - #[clap(value_parser)] file: String, #[clap(subcommand)] cmd: Sub3, @@ -159,16 +158,8 @@ enum SubSubCmdWithOption { } #[derive(Subcommand, PartialEq, Debug)] enum Remote { - Add { - #[clap(value_parser)] - name: String, - #[clap(value_parser)] - url: String, - }, - Remove { - #[clap(value_parser)] - name: String, - }, + Add { name: String, url: String }, + Remove { name: String }, } #[derive(Subcommand, PartialEq, Debug)] diff --git a/tests/derive/next/non_literal_attributes.rs b/tests/derive/next/non_literal_attributes.rs index bf950fb7baa..97879994418 100644 --- a/tests/derive/next/non_literal_attributes.rs +++ b/tests/derive/next/non_literal_attributes.rs @@ -27,17 +27,16 @@ struct Opt { next_line_help = true, default_value = "0", require_equals = true, - value_parser )] x: i32, - #[clap(short = 'l', long = "level", value_parser, aliases = &["set-level", "lvl"])] + #[clap(short = 'l', long = "level", aliases = &["set-level", "lvl"])] level: String, - #[clap(long("values"), value_parser)] + #[clap(long("values"))] values: Vec, - #[clap(name = "FILE", value_parser, requires_if("FILE", "values"))] + #[clap(name = "FILE", requires_if("FILE", "values"))] files: Vec, } diff --git a/tests/derive/next/options.rs b/tests/derive/next/options.rs index f55dd8805e9..c15cf95057c 100644 --- a/tests/derive/next/options.rs +++ b/tests/derive/next/options.rs @@ -22,7 +22,7 @@ use clap::{Parser, Subcommand}; fn required_option() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, long, value_parser)] + #[clap(short, long)] arg: i32, } assert_eq!( @@ -48,7 +48,7 @@ fn required_option() { fn option_with_default() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, value_parser, default_value = "42")] + #[clap(short, default_value = "42")] arg: i32, } assert_eq!( @@ -66,7 +66,7 @@ fn option_with_default() { fn option_with_raw_default() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, value_parser, default_value = "42")] + #[clap(short, default_value = "42")] arg: i32, } assert_eq!( @@ -95,7 +95,6 @@ fn option_from_str() { #[derive(Debug, Parser, PartialEq)] struct Opt { - #[clap(value_parser)] a: Option, } @@ -121,7 +120,6 @@ fn vec_from_str() { #[derive(Debug, Parser, PartialEq)] struct Opt { - #[clap(value_parser)] a: Vec, } @@ -150,7 +148,7 @@ fn option_vec_from_str() { #[derive(Debug, Parser, PartialEq)] struct Opt { - #[clap(short, value_parser)] + #[clap(short)] a: Option>, } @@ -165,7 +163,7 @@ fn option_vec_from_str() { fn option_type_is_optional() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, value_parser)] + #[clap(short)] arg: Option, } assert_eq!( @@ -184,7 +182,7 @@ fn required_with_option_type() { #[derive(Debug, PartialEq, Eq, Parser)] #[clap(subcommand_negates_reqs = true)] struct Opt { - #[clap(value_parser, required = true)] + #[clap(required = true)] req_str: Option, #[clap(subcommand)] @@ -242,7 +240,7 @@ fn ignore_qualified_option_type() { fn option_option_type_is_optional_value() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, value_parser)] + #[clap(short)] #[allow(clippy::option_option)] arg: Option>, } @@ -269,7 +267,7 @@ fn option_option_type_is_optional_value() { fn option_option_type_help() { #[derive(Parser, Debug)] struct Opt { - #[clap(long, value_name = "val", value_parser)] + #[clap(long, value_name = "val")] arg: Option>, } let help = utils::get_help::(); @@ -281,10 +279,10 @@ fn option_option_type_help() { fn two_option_option_types() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, value_parser)] + #[clap(short)] arg: Option>, - #[clap(long, value_parser)] + #[clap(long)] field: Option>, } assert_eq!( @@ -335,7 +333,7 @@ fn two_option_option_types() { fn vec_type_is_multiple_occurrences() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, long, value_parser)] + #[clap(short, long)] arg: Vec, } assert_eq!( @@ -353,7 +351,7 @@ fn vec_type_is_multiple_occurrences() { fn vec_type_with_required() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, long, required = true, value_parser)] + #[clap(short, long, required = true)] arg: Vec, } assert_eq!( @@ -371,7 +369,7 @@ fn vec_type_with_required() { fn vec_type_with_multiple_values_only() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, long, multiple_values(true), value_parser)] + #[clap(short, long, multiple_values(true))] arg: Vec, } assert_eq!( @@ -409,7 +407,7 @@ fn ignore_qualified_vec_type() { fn option_vec_type() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, value_parser)] + #[clap(short)] arg: Option>, } assert_eq!( @@ -431,7 +429,7 @@ fn option_vec_type() { fn option_vec_type_structopt_behavior() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, long, multiple_values(true), min_values(0), value_parser)] + #[clap(short, long, multiple_values(true), min_values(0))] arg: Option>, } assert_eq!( @@ -458,10 +456,10 @@ fn option_vec_type_structopt_behavior() { fn two_option_vec_types() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(short, value_parser)] + #[clap(short)] arg: Option>, - #[clap(short, value_parser)] + #[clap(short)] b: Option>, } @@ -512,7 +510,7 @@ fn explicit_value_parser() { fn implicit_value_parser() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(long, value_parser)] + #[clap(long)] arg: i32, } assert_eq!( diff --git a/tests/derive/next/privacy.rs b/tests/derive/next/privacy.rs index 224a23a4cc1..c8f66127b2d 100644 --- a/tests/derive/next/privacy.rs +++ b/tests/derive/next/privacy.rs @@ -30,7 +30,6 @@ mod subcommands { /// foo Foo { /// foo - #[clap(value_parser)] bars: String, }, } diff --git a/tests/derive/next/raw_bool_literal.rs b/tests/derive/next/raw_bool_literal.rs index 7dd3618a7d0..0e572aa8c67 100644 --- a/tests/derive/next/raw_bool_literal.rs +++ b/tests/derive/next/raw_bool_literal.rs @@ -13,9 +13,9 @@ fn raw_bool_literal() { #[derive(Parser, Debug, PartialEq)] #[clap(name = "raw_bool")] struct Opt { - #[clap(raw(false), value_parser)] + #[clap(raw(false))] a: String, - #[clap(raw(true), value_parser)] + #[clap(raw(true))] b: String, } diff --git a/tests/derive/next/raw_idents.rs b/tests/derive/next/raw_idents.rs index 25762c0e6bb..12c5d1658f0 100644 --- a/tests/derive/next/raw_idents.rs +++ b/tests/derive/next/raw_idents.rs @@ -4,7 +4,7 @@ use clap::Parser; fn raw_idents() { #[derive(Parser, Debug, PartialEq)] struct Opt { - #[clap(short, long, value_parser)] + #[clap(short, long)] r#type: String, } diff --git a/tests/derive/next/rename_all_env.rs b/tests/derive/next/rename_all_env.rs index 46327c12ff0..20d4f40c728 100644 --- a/tests/derive/next/rename_all_env.rs +++ b/tests/derive/next/rename_all_env.rs @@ -9,7 +9,7 @@ fn it_works() { #[derive(Debug, PartialEq, Parser)] #[clap(rename_all_env = "kebab")] struct BehaviorModel { - #[clap(env, value_parser)] + #[clap(env)] be_nice: String, } @@ -21,7 +21,7 @@ fn it_works() { fn default_is_screaming() { #[derive(Debug, PartialEq, Parser)] struct BehaviorModel { - #[clap(env, value_parser)] + #[clap(env)] be_nice: String, } @@ -34,10 +34,10 @@ fn overridable() { #[derive(Debug, PartialEq, Parser)] #[clap(rename_all_env = "kebab")] struct BehaviorModel { - #[clap(env, value_parser)] + #[clap(env)] be_nice: String, - #[clap(rename_all_env = "pascal", env, value_parser)] + #[clap(rename_all_env = "pascal", env)] be_aggressive: String, } diff --git a/tests/derive/next/skip.rs b/tests/derive/next/skip.rs index d2e3b7ec086..160b0578f72 100644 --- a/tests/derive/next/skip.rs +++ b/tests/derive/next/skip.rs @@ -12,7 +12,7 @@ use clap::Parser; fn skip_1() { #[derive(Parser, Debug, PartialEq)] struct Opt { - #[clap(short, value_parser)] + #[clap(short)] x: u32, #[clap(skip)] s: u32, @@ -39,17 +39,17 @@ fn skip_1() { fn skip_2() { #[derive(Parser, Debug, PartialEq)] struct Opt { - #[clap(short, value_parser)] + #[clap(short)] x: u32, #[clap(skip)] ss: String, #[clap(skip)] sn: u8, - #[clap(value_parser)] + y: u32, #[clap(skip)] sz: u16, - #[clap(value_parser)] + t: u32, } @@ -83,7 +83,7 @@ fn skip_enum() { #[derive(Parser, Debug, PartialEq)] pub struct Opt { - #[clap(long, short, value_parser)] + #[clap(long, short)] number: u32, #[clap(skip)] k: Kind, @@ -117,7 +117,7 @@ fn skip_help_doc_comments() { #[clap(skip)] c: u32, - #[clap(short, value_parser)] + #[clap(short)] n: u32, } @@ -136,7 +136,7 @@ fn skip_help_doc_comments() { fn skip_val() { #[derive(Parser, Debug, PartialEq)] pub struct Opt { - #[clap(long, short, value_parser)] + #[clap(long, short)] number: u32, #[clap(skip = "key")] diff --git a/tests/derive/next/subcommands.rs b/tests/derive/next/subcommands.rs index 2dcb897643f..9b3a69bef18 100644 --- a/tests/derive/next/subcommands.rs +++ b/tests/derive/next/subcommands.rs @@ -20,19 +20,19 @@ use clap::{Args, Parser, Subcommand}; enum Opt { /// Fetch stuff from GitHub Fetch { - #[clap(long, action)] + #[clap(long)] all: bool, /// Overwrite local branches. - #[clap(short, long, action)] + #[clap(short, long)] force: bool, - #[clap(value_parser)] + repo: String, }, Add { - #[clap(short, long, action)] + #[clap(short, long)] interactive: bool, - #[clap(short, long, action)] + #[clap(short, long)] verbose: bool, }, } @@ -89,10 +89,7 @@ fn test_no_parse() { #[derive(Parser, PartialEq, Debug)] enum Opt2 { - DoSomething { - #[clap(value_parser)] - arg: String, - }, + DoSomething { arg: String }, } #[test] @@ -127,13 +124,11 @@ fn test_null_commands() { #[derive(Parser, PartialEq, Debug)] #[clap(about = "Not shown")] struct Add { - #[clap(value_parser)] file: String, } /// Not shown #[derive(Parser, PartialEq, Debug)] struct Fetch { - #[clap(value_parser)] remote: String, } #[derive(Parser, PartialEq, Debug)] @@ -173,7 +168,7 @@ fn test_tuple_commands() { fn global_passed_down() { #[derive(Debug, PartialEq, Parser)] struct Opt { - #[clap(global = true, long, action)] + #[clap(global = true, long)] other: bool, #[clap(subcommand)] sub: Subcommands, @@ -187,7 +182,7 @@ fn global_passed_down() { #[derive(Debug, PartialEq, Args)] struct GlobalCmd { - #[clap(from_global, action)] + #[clap(from_global)] other: bool, } @@ -343,15 +338,13 @@ fn update_subcommands() { #[derive(Parser, PartialEq, Debug)] struct Command1 { - #[clap(value_parser)] arg1: i32, - #[clap(value_parser)] + arg2: i32, } #[derive(Parser, PartialEq, Debug)] struct Command2 { - #[clap(value_parser)] arg2: i32, } @@ -405,15 +398,13 @@ fn update_sub_subcommands() { #[derive(Args, PartialEq, Debug)] struct Command1 { - #[clap(value_parser)] arg1: i32, - #[clap(value_parser)] + arg2: i32, } #[derive(Args, PartialEq, Debug)] struct Command2 { - #[clap(value_parser)] arg2: i32, } @@ -466,15 +457,13 @@ fn update_ext_subcommand() { #[derive(Args, PartialEq, Debug)] struct Command1 { - #[clap(value_parser)] arg1: i32, - #[clap(value_parser)] + arg2: i32, } #[derive(Args, PartialEq, Debug)] struct Command2 { - #[clap(value_parser)] arg2: i32, } @@ -569,7 +558,6 @@ fn built_in_subcommand_escaped() { #[derive(Debug, PartialEq, Parser)] enum Command { Install { - #[clap(value_parser)] arg: Option, }, #[clap(external_subcommand)] @@ -598,7 +586,6 @@ fn built_in_subcommand_escaped() { #[derive(Debug, PartialEq, Parser)] enum Command { Install { - #[clap(value_parser)] arg: Option, }, #[clap(external_subcommand)] diff --git a/tests/derive/next/type_alias_regressions.rs b/tests/derive/next/type_alias_regressions.rs index c418644f413..a66f4fa8bae 100644 --- a/tests/derive/next/type_alias_regressions.rs +++ b/tests/derive/next/type_alias_regressions.rs @@ -11,11 +11,10 @@ type Option = std::option::Option; #[derive(Parser)] pub struct Opts { - #[clap(value_parser)] another_string: String, #[clap(subcommand)] command: Command, - #[clap(short, long, value_enum, value_parser)] + #[clap(short, long, value_enum)] choice: ArgChoice, } diff --git a/tests/derive/next/utf8.rs b/tests/derive/next/utf8.rs index 6389ef67168..dac3b45f903 100644 --- a/tests/derive/next/utf8.rs +++ b/tests/derive/next/utf8.rs @@ -6,13 +6,12 @@ use std::os::unix::ffi::OsStringExt; #[derive(Parser, Debug, PartialEq, Eq)] struct Positional { - #[clap(value_parser)] arg: String, } #[derive(Parser, Debug, PartialEq, Eq)] struct Named { - #[clap(short, long, value_parser)] + #[clap(short, long)] arg: String, } @@ -77,13 +76,12 @@ fn invalid_utf8_strict_option_long_equals() { #[derive(Parser, Debug, PartialEq, Eq)] struct PositionalOs { - #[clap(value_parser)] arg: OsString, } #[derive(Parser, Debug, PartialEq, Eq)] struct NamedOs { - #[clap(short, long, value_parser)] + #[clap(short, long)] arg: OsString, } diff --git a/tests/derive/next/value_enum.rs b/tests/derive/next/value_enum.rs index 4f93f57d1ba..ca9faaa52ca 100644 --- a/tests/derive/next/value_enum.rs +++ b/tests/derive/next/value_enum.rs @@ -19,7 +19,7 @@ fn basic() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_enum, value_parser)] + #[clap(value_enum)] arg: ArgChoice, } @@ -54,7 +54,7 @@ fn default_value() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_enum, value_parser, default_value_t)] + #[clap(value_enum, default_value_t)] arg: ArgChoice, } @@ -89,7 +89,7 @@ fn multi_word_is_renamed_kebab() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_enum, value_parser)] + #[clap(value_enum)] arg: ArgChoice, } @@ -118,7 +118,7 @@ fn variant_with_defined_casing() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_enum, value_parser)] + #[clap(value_enum)] arg: ArgChoice, } @@ -141,7 +141,7 @@ fn casing_is_propagated_from_parent() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_enum, value_parser)] + #[clap(value_enum)] arg: ArgChoice, } @@ -165,7 +165,7 @@ fn casing_propagation_is_overridden() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_enum, value_parser)] + #[clap(value_enum)] arg: ArgChoice, } @@ -188,7 +188,7 @@ fn ignore_case() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_enum, value_parser, ignore_case(true))] + #[clap(value_enum, ignore_case(true))] arg: ArgChoice, } @@ -215,7 +215,7 @@ fn ignore_case_set_to_false() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_enum, ignore_case(false), value_parser)] + #[clap(value_enum, ignore_case(false))] arg: ArgChoice, } @@ -238,7 +238,7 @@ fn alias() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_enum, ignore_case(false), value_parser)] + #[clap(value_enum, ignore_case(false))] arg: ArgChoice, } @@ -266,7 +266,7 @@ fn multiple_alias() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_enum, ignore_case(false), value_parser)] + #[clap(value_enum, ignore_case(false))] arg: ArgChoice, } @@ -375,7 +375,7 @@ fn option_type() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_enum, value_parser)] + #[clap(value_enum)] arg: Option, } @@ -405,7 +405,7 @@ fn option_option_type() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_enum, long, value_parser)] + #[clap(value_enum, long)] arg: Option>, } @@ -439,7 +439,7 @@ fn vec_type() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_enum, short, long, value_parser)] + #[clap(value_enum, short, long)] arg: Vec, } @@ -469,7 +469,7 @@ fn option_vec_type() { #[derive(Parser, PartialEq, Debug)] struct Opt { - #[clap(value_enum, short, long, value_parser)] + #[clap(value_enum, short, long)] arg: Option>, } @@ -505,8 +505,7 @@ fn vec_type_default_value() { short, long, default_value = "foo,bar", - value_delimiter = ',', - value_parser + value_delimiter = ',' )] arg: Vec, } diff --git a/tests/derive_ui/next/bool_value_enum.rs b/tests/derive_ui/next/bool_value_enum.rs index f98d507bd49..6ee48e5212c 100644 --- a/tests/derive_ui/next/bool_value_enum.rs +++ b/tests/derive_ui/next/bool_value_enum.rs @@ -3,7 +3,7 @@ use clap::Parser; #[derive(Parser, Debug)] #[clap(name = "basic")] struct Opt { - #[clap(short, value_enum)] + #[clap(short, value_enum, default_value_t)] opts: bool, } diff --git a/tests/derive_ui/next/bool_value_enum.stderr b/tests/derive_ui/next/bool_value_enum.stderr index 4ec347dd34b..d6dd2073ecc 100644 --- a/tests/derive_ui/next/bool_value_enum.stderr +++ b/tests/derive_ui/next/bool_value_enum.stderr @@ -1,79 +1,11 @@ -warning: use of deprecated variant `clap::ArgAction::IncOccurrence`: Replaced with `ArgAction::SetTrue` or `ArgAction::Count` - --> tests/derive_ui/next/bool_value_enum.rs:7:5 - | -7 | opts: bool, - | ^^^^ - | - = note: `#[warn(deprecated)]` on by default - -warning: use of deprecated variant `clap::ArgAction::IncOccurrence`: Replaced with `ArgAction::SetTrue` or `ArgAction::Count` - --> tests/derive_ui/next/bool_value_enum.rs:7:5 - | -7 | opts: bool, - | ^^^^ - -error[E0277]: the trait bound `bool: ArgEnum` is not satisfied - --> tests/derive_ui/next/bool_value_enum.rs:7:11 +error[E0277]: the trait bound `bool: ValueEnum` is not satisfied + --> tests/derive_ui/next/bool_value_enum.rs:6:31 | -7 | opts: bool, - | ^^^^ the trait `ArgEnum` is not implemented for `bool` +6 | #[clap(short, value_enum, default_value_t)] + | ^^^^^^^^^^^^^^^ the trait `ValueEnum` is not implemented for `bool` | -note: required by `clap::ValueEnum::from_str` +note: required by `to_possible_value` --> src/derive.rs | - | fn from_str(input: &str, ignore_case: bool) -> Result { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error[E0618]: expected function, found enum variant `bool` - --> tests/derive_ui/next/bool_value_enum.rs:7:11 - | -7 | opts: bool, - | ^^^^ call expression requires function - | -help: `bool` is a unit variant, you need to write it without the parenthesis - | -7 | opts: bool, - | ~~~~ - -warning: use of deprecated associated function `clap::ArgMatches::is_present`: Replaced with either `ArgAction::SetTrue` or `ArgMatches::contains_id(...)` - --> tests/derive_ui/next/bool_value_enum.rs:7:11 - | -7 | opts: bool, - | ^^^^ - -warning: use of deprecated associated function `clap::Arg::<'help>::possible_values`: Replaced with `Arg::value_parser(PossibleValuesParser::new(...)).takes_value(true)` - --> tests/derive_ui/next/bool_value_enum.rs:7:11 - | -7 | opts: bool, - | ^^^^ - -error[E0277]: the trait bound `bool: ArgEnum` is not satisfied - --> tests/derive_ui/next/bool_value_enum.rs:7:11 - | -7 | opts: bool, - | ^^^^ the trait `ArgEnum` is not implemented for `bool` - | -note: required by `value_variants` - --> src/derive.rs - | - | fn value_variants<'a>() -> &'a [Self]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error[E0277]: the trait bound `bool: ArgEnum` is not satisfied - --> tests/derive_ui/next/bool_value_enum.rs:6:5 - | -6 | / #[clap(short, value_enum)] -7 | | opts: bool, - | |______________^ the trait `ArgEnum` is not implemented for `bool` - | -note: required by a bound in `ArgEnum` - --> src/derive.rs - | - | / pub trait ValueEnum: Sized + Clone { - | | /// All possible argument values, in display order. - | | fn value_variants<'a>() -> &'a [Self]; - | | -... | - | | fn to_possible_value<'a>(&self) -> Option>; - | | } - | |_^ required by this bound in `ArgEnum` + | fn to_possible_value<'a>(&self) -> Option>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/derive_ui/non_existent_attr.rs b/tests/derive_ui/next/non_existent_attr.rs similarity index 100% rename from tests/derive_ui/non_existent_attr.rs rename to tests/derive_ui/next/non_existent_attr.rs diff --git a/tests/derive_ui/next/non_existent_attr.stderr b/tests/derive_ui/next/non_existent_attr.stderr new file mode 100644 index 00000000000..881d15a372c --- /dev/null +++ b/tests/derive_ui/next/non_existent_attr.stderr @@ -0,0 +1,5 @@ +error[E0599]: no method named `non_existing_attribute` found for struct `Arg` in the current scope + --> tests/derive_ui/next/non_existent_attr.rs:14:19 + | +14 | #[clap(short, non_existing_attribute = 1)] + | ^^^^^^^^^^^^^^^^^^^^^^ method not found in `Arg<'_>` diff --git a/tests/derive_ui/stable/non_existent_attr.rs b/tests/derive_ui/stable/non_existent_attr.rs new file mode 100644 index 00000000000..5275b446cac --- /dev/null +++ b/tests/derive_ui/stable/non_existent_attr.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use clap::Parser; + +#[derive(Parser, Debug)] +#[clap(name = "basic")] +struct Opt { + #[clap(short, non_existing_attribute = 1)] + debug: bool, +} + +fn main() { + let opt = Opt::parse(); + println!("{:?}", opt); +} diff --git a/tests/derive_ui/non_existent_attr.stderr b/tests/derive_ui/stable/non_existent_attr.stderr similarity index 80% rename from tests/derive_ui/non_existent_attr.stderr rename to tests/derive_ui/stable/non_existent_attr.stderr index c970b909c8f..99b85be70bd 100644 --- a/tests/derive_ui/non_existent_attr.stderr +++ b/tests/derive_ui/stable/non_existent_attr.stderr @@ -1,5 +1,5 @@ warning: use of deprecated associated function `clap::ArgMatches::is_present`: Replaced with either `ArgAction::SetTrue` or `ArgMatches::contains_id(...)` - --> tests/derive_ui/non_existent_attr.rs:15:12 + --> tests/derive_ui/stable/non_existent_attr.rs:15:12 | 15 | debug: bool, | ^^^^ @@ -7,13 +7,13 @@ warning: use of deprecated associated function `clap::ArgMatches::is_present`: R = note: `#[warn(deprecated)]` on by default warning: use of deprecated associated function `clap::ArgMatches::is_present`: Replaced with either `ArgAction::SetTrue` or `ArgMatches::contains_id(...)` - --> tests/derive_ui/non_existent_attr.rs:15:12 + --> tests/derive_ui/stable/non_existent_attr.rs:15:12 | 15 | debug: bool, | ^^^^ error[E0599]: no method named `non_existing_attribute` found for struct `Arg` in the current scope - --> tests/derive_ui/non_existent_attr.rs:14:19 + --> tests/derive_ui/stable/non_existent_attr.rs:14:19 | 14 | #[clap(short, non_existing_attribute = 1)] | ^^^^^^^^^^^^^^^^^^^^^^ method not found in `Arg<'_>`