diff options
author | Robin Krahl <robin.krahl@ireas.org> | 2020-01-07 11:18:04 +0000 |
---|---|---|
committer | Daniel Mueller <deso@posteo.net> | 2020-01-08 09:20:25 -0800 |
commit | 5e20a29b4fdc8a2d442d1093681b396dcb4b816b (patch) | |
tree | 55ab083fa8999d2ccbb5e921c1ffe52560dca152 /structopt/tests | |
parent | 203e691f46d591a2cc8acdfd850fa9f5b0fb8a98 (diff) | |
download | nitrocli-5e20a29b4fdc8a2d442d1093681b396dcb4b816b.tar.gz nitrocli-5e20a29b4fdc8a2d442d1093681b396dcb4b816b.tar.bz2 |
Add structopt dependency in version 0.3.7
This patch series replaces argparse with structopt in the argument
handling code. As a first step, we need structopt as a dependency.
Import subrepo structopt/:structopt at efbdda4753592e27bc430fb01f7b9650b2f3174d
Import subrepo bitflags/:bitflags at 30668016aca6bd3b02c766e8347e0b4080d4c296
Import subrepo clap/:clap at 784524f7eb193e35f81082cc69454c8c21b948f7
Import subrepo heck/:heck at 093d56fbf001e1506e56dbfa38631d99b1066df1
Import subrepo proc-macro-error/:proc-macro-error at 6c4cfe79a622c5de8ae68557993542be46eacae2
Import subrepo proc-macro2/:proc-macro2 at d5d48eddca4566e5438e8a2cbed4a74e049544de
Import subrepo quote/:quote at 727436c6c137b20f0f34dde5d8fda2679b9747ad
Import subrepo rustversion/:rustversion at 0c5663313516263059ce9059ef81fc7a1cf655ca
Import subrepo syn-mid/:syn-mid at 5d3d85414a9e6674e1857ec22a87b96e04a6851a
Import subrepo syn/:syn at e87c27e87f6f4ef8919d0372bdb056d53ef0d8f3
Import subrepo textwrap/:textwrap at abcd618beae3f74841032aa5b53c1086b0a57ca2
Import subrepo unicode-segmentation/:unicode-segmentation at 637c9874c4fe0c205ff27787faf150a40295c6c3
Import subrepo unicode-width/:unicode-width at 3033826f8bf05e82724140a981d5941e48fce393
Import subrepo unicode-xid/:unicode-xid at 4baae9fffb156ba229665b972a9cd5991787ceb7
Diffstat (limited to 'structopt/tests')
86 files changed, 3595 insertions, 0 deletions
diff --git a/structopt/tests/argument_naming.rs b/structopt/tests/argument_naming.rs new file mode 100644 index 0000000..e7fe3d5 --- /dev/null +++ b/structopt/tests/argument_naming.rs @@ -0,0 +1,311 @@ +use structopt::StructOpt; + +#[test] +fn test_single_word_enum_variant_is_default_renamed_into_kebab_case() { + #[derive(StructOpt, Debug, PartialEq)] + enum Opt { + Command { foo: u32 }, + } + + assert_eq!( + Opt::Command { foo: 0 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "command", "0"])) + ); +} + +#[test] +fn test_multi_word_enum_variant_is_renamed() { + #[derive(StructOpt, Debug, PartialEq)] + enum Opt { + FirstCommand { foo: u32 }, + } + + assert_eq!( + Opt::FirstCommand { foo: 0 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "first-command", "0"])) + ); +} + +#[test] +fn test_standalone_long_generates_kebab_case() { + #[derive(StructOpt, Debug, PartialEq)] + #[allow(non_snake_case)] + struct Opt { + #[structopt(long)] + FOO_OPTION: bool, + } + + assert_eq!( + Opt { FOO_OPTION: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--foo-option"])) + ); +} + +#[test] +fn test_custom_long_overwrites_default_name() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(long = "foo")] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--foo"])) + ); +} + +#[test] +fn test_standalone_long_uses_previous_defined_custom_name() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(name = "foo", long)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--foo"])) + ); +} + +#[test] +fn test_standalone_long_ignores_afterwards_defined_custom_name() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(long, name = "foo")] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--foo-option"])) + ); +} + +#[test] +fn test_standalone_short_generates_kebab_case() { + #[derive(StructOpt, Debug, PartialEq)] + #[allow(non_snake_case)] + struct Opt { + #[structopt(short)] + FOO_OPTION: bool, + } + + assert_eq!( + Opt { FOO_OPTION: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-f"])) + ); +} + +#[test] +fn test_custom_short_overwrites_default_name() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(short = "o")] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-o"])) + ); +} + +#[test] +fn test_standalone_short_uses_previous_defined_custom_name() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(name = "option", short)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-o"])) + ); +} + +#[test] +fn test_standalone_short_ignores_afterwards_defined_custom_name() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(short, name = "option")] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-f"])) + ); +} + +#[test] +fn test_standalone_long_uses_previous_defined_casing() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(rename_all = "screaming_snake", long)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--FOO_OPTION"])) + ); +} + +#[test] +fn test_standalone_short_uses_previous_defined_casing() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(rename_all = "screaming_snake", short)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-F"])) + ); +} + +#[test] +fn test_standalone_long_works_with_verbatim_casing() { + #[derive(StructOpt, Debug, PartialEq)] + #[allow(non_snake_case)] + struct Opt { + #[structopt(rename_all = "verbatim", long)] + _fOO_oPtiON: bool, + } + + assert_eq!( + Opt { _fOO_oPtiON: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--_fOO_oPtiON"])) + ); +} + +#[test] +fn test_standalone_short_works_with_verbatim_casing() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(rename_all = "verbatim", short)] + _foo: bool, + } + + assert_eq!( + Opt { _foo: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-_"])) + ); +} + +#[test] +fn test_rename_all_is_propagated_from_struct_to_fields() { + #[derive(StructOpt, Debug, PartialEq)] + #[structopt(rename_all = "screaming_snake")] + struct Opt { + #[structopt(long)] + foo: bool, + } + + assert_eq!( + Opt { foo: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--FOO"])) + ); +} + +#[test] +fn test_rename_all_is_not_propagated_from_struct_into_flattened() { + #[derive(StructOpt, Debug, PartialEq)] + #[structopt(rename_all = "screaming_snake")] + struct Opt { + #[structopt(flatten)] + foo: Foo, + } + + #[derive(StructOpt, Debug, PartialEq)] + struct Foo { + #[structopt(long)] + foo: bool, + } + + assert_eq!( + Opt { + foo: Foo { foo: true } + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--foo"])) + ); +} + +#[test] +fn test_rename_all_is_not_propagated_from_struct_into_subcommand() { + #[derive(StructOpt, Debug, PartialEq)] + #[structopt(rename_all = "screaming_snake")] + struct Opt { + #[structopt(subcommand)] + foo: Foo, + } + + #[derive(StructOpt, Debug, PartialEq)] + enum Foo { + Command { + #[structopt(long)] + foo: bool, + }, + } + + assert_eq!( + Opt { + foo: Foo::Command { foo: true } + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "command", "--foo"])) + ); +} + +#[test] +fn test_rename_all_is_propagated_from_enum_to_variants_and_their_fields() { + #[derive(StructOpt, Debug, PartialEq)] + #[structopt(rename_all = "screaming_snake")] + enum Opt { + FirstVariant, + SecondVariant { + #[structopt(long)] + foo: bool, + }, + } + + assert_eq!( + Opt::FirstVariant, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "FIRST_VARIANT"])) + ); + + assert_eq!( + Opt::SecondVariant { foo: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "SECOND_VARIANT", "--FOO"])) + ); +} + +#[test] +fn test_rename_all_is_propagation_can_be_overridden() { + #[derive(StructOpt, Debug, PartialEq)] + #[structopt(rename_all = "screaming_snake")] + enum Opt { + #[structopt(rename_all = "kebab_case")] + FirstVariant { + #[structopt(long)] + foo_option: bool, + }, + SecondVariant { + #[structopt(rename_all = "kebab_case", long)] + foo_option: bool, + }, + } + + assert_eq!( + Opt::FirstVariant { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "first-variant", "--foo-option"])) + ); + + assert_eq!( + Opt::SecondVariant { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "SECOND_VARIANT", "--foo-option"])) + ); +} diff --git a/structopt/tests/arguments.rs b/structopt/tests/arguments.rs new file mode 100644 index 0000000..96a0938 --- /dev/null +++ b/structopt/tests/arguments.rs @@ -0,0 +1,86 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::clap; +use structopt::StructOpt; + +#[test] +fn required_argument() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + arg: i32, + } + assert_eq!(Opt { arg: 42 }, Opt::from_iter(&["test", "42"])); + assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "42", "24"]) + .is_err()); +} + +#[test] +fn optional_argument() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + arg: Option<i32>, + } + assert_eq!(Opt { arg: Some(42) }, Opt::from_iter(&["test", "42"])); + assert_eq!(Opt { arg: None }, Opt::from_iter(&["test"])); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "42", "24"]) + .is_err()); +} + +#[test] +fn argument_with_default() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(default_value = "42")] + arg: i32, + } + assert_eq!(Opt { arg: 24 }, Opt::from_iter(&["test", "24"])); + assert_eq!(Opt { arg: 42 }, Opt::from_iter(&["test"])); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "42", "24"]) + .is_err()); +} + +#[test] +fn arguments() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + arg: Vec<i32>, + } + assert_eq!(Opt { arg: vec![24] }, Opt::from_iter(&["test", "24"])); + assert_eq!(Opt { arg: vec![] }, Opt::from_iter(&["test"])); + assert_eq!( + Opt { arg: vec![24, 42] }, + Opt::from_iter(&["test", "24", "42"]) + ); +} + +#[test] +fn arguments_safe() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + arg: Vec<i32>, + } + assert_eq!( + Opt { arg: vec![24] }, + Opt::from_iter_safe(&["test", "24"]).unwrap() + ); + assert_eq!(Opt { arg: vec![] }, Opt::from_iter_safe(&["test"]).unwrap()); + assert_eq!( + Opt { arg: vec![24, 42] }, + Opt::from_iter_safe(&["test", "24", "42"]).unwrap() + ); + + assert_eq!( + clap::ErrorKind::ValueValidation, + Opt::from_iter_safe(&["test", "NOPE"]).err().unwrap().kind + ); +} diff --git a/structopt/tests/author_version_about.rs b/structopt/tests/author_version_about.rs new file mode 100644 index 0000000..0c4a4fb --- /dev/null +++ b/structopt/tests/author_version_about.rs @@ -0,0 +1,46 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +mod utils; + +use structopt::StructOpt; +use utils::*; + +#[test] +fn no_author_version_about() { + #[derive(StructOpt, PartialEq, Debug)] + #[structopt(name = "foo", no_version)] + struct Opt {} + + let output = get_long_help::<Opt>(); + assert!(output.starts_with("foo \n\nUSAGE:")); +} + +#[test] +fn use_env() { + #[derive(StructOpt, PartialEq, Debug)] + #[structopt(author, about)] + struct Opt {} + + let output = get_long_help::<Opt>(); + assert!(output.starts_with("structopt 0.")); + assert!(output.contains("Guillaume Pinot <texitoi@texitoi.eu>, others")); + assert!(output.contains("Parse command line argument by defining a struct.")); +} + +#[test] +fn explicit_version_not_str() { + const VERSION: &str = "custom version"; + + #[derive(StructOpt)] + #[structopt(version = VERSION)] + pub struct Opt {} + + let output = get_long_help::<Opt>(); + assert!(output.contains("custom version")); +} diff --git a/structopt/tests/custom-string-parsers.rs b/structopt/tests/custom-string-parsers.rs new file mode 100644 index 0000000..89070ed --- /dev/null +++ b/structopt/tests/custom-string-parsers.rs @@ -0,0 +1,306 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +use std::ffi::{CString, OsStr, OsString}; +use std::num::ParseIntError; +use std::path::PathBuf; + +#[derive(StructOpt, PartialEq, Debug)] +struct PathOpt { + #[structopt(short, long, parse(from_os_str))] + path: PathBuf, + + #[structopt(short, default_value = "../", parse(from_os_str))] + default_path: PathBuf, + + #[structopt(short, parse(from_os_str))] + vector_path: Vec<PathBuf>, + + #[structopt(short, parse(from_os_str))] + option_path_1: Option<PathBuf>, + + #[structopt(short = "q", parse(from_os_str))] + option_path_2: Option<PathBuf>, +} + +#[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::from_clap(&PathOpt::clap().get_matches_from(&[ + "test", "-p", "/usr/bin", "-v", "/a/b/c", "-v", "/d/e/f", "-v", "/g/h/i", "-q", + "j.zip", + ])) + ); +} + +fn parse_hex(input: &str) -> Result<u64, ParseIntError> { + u64::from_str_radix(input, 16) +} + +#[derive(StructOpt, PartialEq, Debug)] +struct HexOpt { + #[structopt(short, parse(try_from_str = parse_hex))] + number: u64, +} + +#[test] +#[allow(clippy::unreadable_literal)] +fn test_parse_hex() { + assert_eq!( + HexOpt { number: 5 }, + HexOpt::from_clap(&HexOpt::clap().get_matches_from(&["test", "-n", "5"])) + ); + assert_eq!( + HexOpt { number: 0xabcdef }, + HexOpt::from_clap(&HexOpt::clap().get_matches_from(&["test", "-n", "abcdef"])) + ); + + let err = HexOpt::clap() + .get_matches_from_safe(&["test", "-n", "gg"]) + .unwrap_err(); + assert!(err.message.contains("invalid digit found in string"), err); +} + +fn custom_parser_1(_: &str) -> &'static str { + "A" +} +fn custom_parser_2(_: &str) -> Result<&'static str, u32> { + Ok("B") +} +fn custom_parser_3(_: &OsStr) -> &'static str { + "C" +} +fn custom_parser_4(_: &OsStr) -> Result<&'static str, OsString> { + Ok("D") +} + +#[derive(StructOpt, PartialEq, Debug)] +struct NoOpOpt { + #[structopt(short, parse(from_str = custom_parser_1))] + a: &'static str, + #[structopt(short, parse(try_from_str = custom_parser_2))] + b: &'static str, + #[structopt(short, parse(from_os_str = custom_parser_3))] + c: &'static str, + #[structopt(short, parse(try_from_os_str = custom_parser_4))] + d: &'static str, +} + +#[test] +fn test_every_custom_parser() { + assert_eq!( + NoOpOpt { + a: "A", + b: "B", + c: "C", + d: "D" + }, + NoOpOpt::from_clap( + &NoOpOpt::clap().get_matches_from(&["test", "-a=?", "-b=?", "-c=?", "-d=?"]) + ) + ); +} + +// Note: can't use `Vec<u8>` directly, as structopt would instead look for +// conversion function from `&str` to `u8`. +type Bytes = Vec<u8>; + +#[derive(StructOpt, PartialEq, Debug)] +struct DefaultedOpt { + #[structopt(short, parse(from_str))] + bytes: Bytes, + + #[structopt(short, parse(try_from_str))] + integer: u64, + + #[structopt(short, parse(from_os_str))] + 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::from_clap(&DefaultedOpt::clap().get_matches_from(&[ + "test", + "-b", + "E²=p²c²+m²c⁴", + "-i", + "9000", + "-p", + "src/lib.rs", + ])) + ); +} + +#[derive(PartialEq, Debug)] +struct Foo(u8); + +fn foo(value: u64) -> Foo { + Foo(value as u8) +} + +#[derive(StructOpt, PartialEq, Debug)] +struct Occurrences { + #[structopt(short, long, parse(from_occurrences))] + signed: i32, + + #[structopt(short, parse(from_occurrences))] + little_signed: i8, + + #[structopt(short, parse(from_occurrences))] + unsigned: usize, + + #[structopt(short = "r", parse(from_occurrences))] + little_unsigned: u8, + + #[structopt(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::from_clap(&Occurrences::clap().get_matches_from(&[ + "test", "-s", "--signed", "--signed", "-l", "-rrrr", "-cccc", "--custom", + ])) + ); +} + +#[test] +fn test_custom_bool() { + fn parse_bool(s: &str) -> Result<bool, String> { + match s { + "true" => Ok(true), + "false" => Ok(false), + _ => Err(format!("invalid bool {}", s)), + } + } + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, parse(try_from_str = parse_bool))] + debug: bool, + #[structopt( + short, + default_value = "false", + parse(try_from_str = parse_bool) + )] + verbose: bool, + #[structopt(short, parse(try_from_str = parse_bool))] + tribool: Option<bool>, + #[structopt(short, parse(try_from_str = parse_bool))] + bitset: Vec<bool>, + } + + assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err()); + assert!(Opt::clap().get_matches_from_safe(&["test", "-d"]).is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-dfoo"]) + .is_err()); + assert_eq!( + Opt { + debug: false, + verbose: false, + tribool: None, + bitset: vec![], + }, + Opt::from_iter(&["test", "-dfalse"]) + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: None, + bitset: vec![], + }, + Opt::from_iter(&["test", "-dtrue"]) + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: None, + bitset: vec![], + }, + Opt::from_iter(&["test", "-dtrue", "-vfalse"]) + ); + assert_eq!( + Opt { + debug: true, + verbose: true, + tribool: None, + bitset: vec![], + }, + Opt::from_iter(&["test", "-dtrue", "-vtrue"]) + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: Some(false), + bitset: vec![], + }, + Opt::from_iter(&["test", "-dtrue", "-tfalse"]) + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: Some(true), + bitset: vec![], + }, + Opt::from_iter(&["test", "-dtrue", "-ttrue"]) + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: None, + bitset: vec![false, true, false, false], + }, + Opt::from_iter(&["test", "-dtrue", "-bfalse", "-btrue", "-bfalse", "-bfalse"]) + ); +} + +#[test] +fn test_cstring() { + #[derive(StructOpt)] + struct Opt { + #[structopt(parse(try_from_str = CString::new))] + c_string: CString, + } + assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err()); + assert_eq!(Opt::from_iter(&["test", "bla"]).c_string.to_bytes(), b"bla"); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "bla\0bla"]) + .is_err()); +} diff --git a/structopt/tests/deny-warnings.rs b/structopt/tests/deny-warnings.rs new file mode 100644 index 0000000..721204a --- /dev/null +++ b/structopt/tests/deny-warnings.rs @@ -0,0 +1,47 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![deny(warnings)] + +use structopt::StructOpt; + +fn try_str(s: &str) -> Result<String, std::convert::Infallible> { + Ok(s.into()) +} + +#[test] +fn warning_never_struct() { + #[derive(Debug, PartialEq, StructOpt)] + struct Opt { + #[structopt(parse(try_from_str = try_str))] + s: String, + } + assert_eq!( + Opt { + s: "foo".to_string() + }, + Opt::from_iter(&["test", "foo"]) + ); +} + +#[test] +fn warning_never_enum() { + #[derive(Debug, PartialEq, StructOpt)] + enum Opt { + Foo { + #[structopt(parse(try_from_str = try_str))] + s: String, + }, + } + assert_eq!( + Opt::Foo { + s: "foo".to_string() + }, + Opt::from_iter(&["test", "foo", "foo"]) + ); +} diff --git a/structopt/tests/doc-comments-help.rs b/structopt/tests/doc-comments-help.rs new file mode 100644 index 0000000..27649b8 --- /dev/null +++ b/structopt/tests/doc-comments-help.rs @@ -0,0 +1,162 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +mod utils; + +use structopt::StructOpt; +use utils::*; + +#[test] +fn doc_comments() { + /// Lorem ipsum + #[derive(StructOpt, PartialEq, Debug)] + struct LoremIpsum { + /// Fooify a bar + /// and a baz + #[structopt(short, long)] + foo: bool, + } + + let help = get_long_help::<LoremIpsum>(); + assert!(help.contains("Lorem ipsum")); + assert!(help.contains("Fooify a bar and a baz")); +} + +#[test] +fn help_is_better_than_comments() { + /// Lorem ipsum + #[derive(StructOpt, PartialEq, Debug)] + #[structopt(name = "lorem-ipsum", about = "Dolor sit amet")] + struct LoremIpsum { + /// Fooify a bar + #[structopt(short, long, help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")] + foo: bool, + } + + let help = get_long_help::<LoremIpsum>(); + 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(StructOpt, PartialEq, Debug)] + #[structopt(name = "lorem-ipsum", no_version)] + struct LoremIpsum {} + + let help = get_long_help::<LoremIpsum>(); + assert!(help.starts_with("lorem-ipsum \nFoo.\n\nBar\n\nUSAGE:")); +} + +#[test] +fn field_long_doc_comment_both_help_long_help() { + /// Lorem ipsumclap + #[derive(StructOpt, PartialEq, Debug)] + #[structopt(name = "lorem-ipsum", about = "Dolor sit amet")] + struct LoremIpsum { + /// Dot is removed from multiline comments. + /// + /// Long help + #[structopt(long)] + foo: bool, + + /// Dot is removed from one short comment. + #[structopt(long)] + bar: bool, + } + + let short_help = get_help::<LoremIpsum>(); + let long_help = get_long_help::<LoremIpsum>(); + + 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(StructOpt, Debug)] + #[structopt(name = "lorem-ipsum", about = "Dolor sit amet")] + struct LoremIpsum { + #[structopt(subcommand)] + foo: SubCommand, + } + + #[derive(StructOpt, Debug)] + pub enum SubCommand { + /// DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES + /// + /// Or something else + Foo { + #[structopt(help = "foo")] + bars: Vec<String>, + }, + } + + let short_help = get_help::<LoremIpsum>(); + let long_help = get_subcommand_long_help::<LoremIpsum>("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(StructOpt, Debug)] + #[structopt(verbatim_doc_comment)] + struct SeeFigure1 { + #[structopt(long)] + foo: bool, + } + + let help = get_long_help::<SeeFigure1>(); + let sample = r#" + () + | + ( () ) + ) ________ // ) + () |\ \ // +( \\__ \ ______\// + \__) | | + | | | + \ | | + \|_______| + // \\ + (( || + \\ || + ( () || + ( () ) )"#; + + assert!(help.contains(sample)) +} diff --git a/structopt/tests/explicit_name_no_renaming.rs b/structopt/tests/explicit_name_no_renaming.rs new file mode 100644 index 0000000..eff7a86 --- /dev/null +++ b/structopt/tests/explicit_name_no_renaming.rs @@ -0,0 +1,32 @@ +mod utils; + +use structopt::StructOpt; +use utils::*; + +#[test] +fn explicit_short_long_no_rename() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short = ".", long = ".foo")] + foo: Vec<String>, + } + + assert_eq!( + Opt { + foo: vec!["short".into(), "long".into()] + }, + Opt::from_iter(&["test", "-.", "short", "--.foo", "long"]) + ); +} + +#[test] +fn explicit_name_no_rename() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(name = ".options")] + foo: Vec<String>, + } + + let help = get_long_help::<Opt>(); + assert!(help.contains("[.options]...")) +} diff --git a/structopt/tests/flags.rs b/structopt/tests/flags.rs new file mode 100644 index 0000000..39a5dc3 --- /dev/null +++ b/structopt/tests/flags.rs @@ -0,0 +1,162 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[test] +fn unique_flag() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, long)] + alice: bool, + } + + assert_eq!( + Opt { alice: false }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert_eq!( + Opt { alice: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])) + ); + assert_eq!( + Opt { alice: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--alice"])) + ); + assert!(Opt::clap().get_matches_from_safe(&["test", "-i"]).is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a", "foo"]) + .is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a", "-a"]) + .is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a", "--alice"]) + .is_err()); +} + +#[test] +fn multiple_flag() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, long, parse(from_occurrences))] + alice: u64, + #[structopt(short, long, parse(from_occurrences))] + bob: u8, + } + + assert_eq!( + Opt { alice: 0, bob: 0 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert_eq!( + Opt { alice: 1, bob: 0 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])) + ); + assert_eq!( + Opt { alice: 2, bob: 0 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "-a"])) + ); + assert_eq!( + Opt { alice: 2, bob: 2 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "--alice", "-bb"])) + ); + assert_eq!( + Opt { alice: 3, bob: 1 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-aaa", "--bob"])) + ); + assert!(Opt::clap().get_matches_from_safe(&["test", "-i"]).is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a", "foo"]) + .is_err()); +} + +fn parse_from_flag(b: bool) -> std::sync::atomic::AtomicBool { + std::sync::atomic::AtomicBool::new(b) +} + +#[test] +fn non_bool_flags() { + #[derive(StructOpt, Debug)] + struct Opt { + #[structopt(short, long, parse(from_flag = parse_from_flag))] + alice: std::sync::atomic::AtomicBool, + #[structopt(short, long, parse(from_flag))] + bob: std::sync::atomic::AtomicBool, + } + + let falsey = Opt::from_clap(&Opt::clap().get_matches_from(&["test"])); + assert!(!falsey.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(!falsey.bob.load(std::sync::atomic::Ordering::Relaxed)); + + let alice = Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])); + assert!(alice.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(!alice.bob.load(std::sync::atomic::Ordering::Relaxed)); + + let bob = Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-b"])); + assert!(!bob.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(bob.bob.load(std::sync::atomic::Ordering::Relaxed)); + + let both = Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-b", "-a"])); + assert!(both.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(both.bob.load(std::sync::atomic::Ordering::Relaxed)); +} + +#[test] +fn combined_flags() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, long)] + alice: bool, + #[structopt(short, long, parse(from_occurrences))] + bob: u64, + } + + assert_eq!( + Opt { + alice: false, + bob: 0 + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert_eq!( + Opt { + alice: true, + bob: 0 + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])) + ); + assert_eq!( + Opt { + alice: true, + bob: 0 + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])) + ); + assert_eq!( + Opt { + alice: false, + bob: 1 + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-b"])) + ); + assert_eq!( + Opt { + alice: true, + bob: 1 + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--alice", "--bob"])) + ); + assert_eq!( + Opt { + alice: true, + bob: 4 + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-bb", "-a", "-bb"])) + ); +} diff --git a/structopt/tests/flatten.rs b/structopt/tests/flatten.rs new file mode 100644 index 0000000..4983d86 --- /dev/null +++ b/structopt/tests/flatten.rs @@ -0,0 +1,95 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[test] +fn flatten() { + #[derive(StructOpt, PartialEq, Debug)] + struct Common { + arg: i32, + } + + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(flatten)] + common: Common, + } + assert_eq!( + Opt { + common: Common { arg: 42 } + }, + Opt::from_iter(&["test", "42"]) + ); + assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "42", "24"]) + .is_err()); +} + +#[test] +#[should_panic] +fn flatten_twice() { + #[derive(StructOpt, PartialEq, Debug)] + struct Common { + arg: i32, + } + + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(flatten)] + c1: Common, + // Defines "arg" twice, so this should not work. + #[structopt(flatten)] + c2: Common, + } + Opt::from_iter(&["test", "42", "43"]); +} + +#[test] +fn flatten_in_subcommand() { + #[derive(StructOpt, PartialEq, Debug)] + struct Common { + arg: i32, + } + + #[derive(StructOpt, PartialEq, Debug)] + struct Add { + #[structopt(short)] + interactive: bool, + #[structopt(flatten)] + common: Common, + } + + #[derive(StructOpt, PartialEq, Debug)] + enum Opt { + Fetch { + #[structopt(short)] + all: bool, + #[structopt(flatten)] + common: Common, + }, + + Add(Add), + } + + assert_eq!( + Opt::Fetch { + all: false, + common: Common { arg: 42 } + }, + Opt::from_iter(&["test", "fetch", "42"]) + ); + assert_eq!( + Opt::Add(Add { + interactive: true, + common: Common { arg: 43 } + }), + Opt::from_iter(&["test", "add", "-i", "43"]) + ); +} diff --git a/structopt/tests/issues.rs b/structopt/tests/issues.rs new file mode 100644 index 0000000..4d250ae --- /dev/null +++ b/structopt/tests/issues.rs @@ -0,0 +1,67 @@ +// https://github.com/TeXitoi/structopt/issues/151 +// https://github.com/TeXitoi/structopt/issues/289 + +#[test] +fn issue_151() { + use structopt::{clap::ArgGroup, StructOpt}; + + #[derive(StructOpt, Debug)] + #[structopt(group = ArgGroup::with_name("verb").required(true).multiple(true))] + struct Opt { + #[structopt(long, group = "verb")] + foo: bool, + #[structopt(long, group = "verb")] + bar: bool, + } + + #[derive(Debug, StructOpt)] + struct Cli { + #[structopt(flatten)] + a: Opt, + } + + assert!(Cli::clap().get_matches_from_safe(&["test"]).is_err()); + assert!(Cli::clap() + .get_matches_from_safe(&["test", "--foo"]) + .is_ok()); + assert!(Cli::clap() + .get_matches_from_safe(&["test", "--bar"]) + .is_ok()); + assert!(Cli::clap() + .get_matches_from_safe(&["test", "--zebra"]) + .is_err()); + assert!(Cli::clap() + .get_matches_from_safe(&["test", "--foo", "--bar"]) + .is_ok()); +} + +#[test] +fn issue_289() { + use structopt::{clap::AppSettings, StructOpt}; + + #[derive(StructOpt)] + #[structopt(setting = AppSettings::InferSubcommands)] + enum Args { + SomeCommand(SubSubCommand), + AnotherCommand, + } + + #[derive(StructOpt)] + #[structopt(setting = AppSettings::InferSubcommands)] + enum SubSubCommand { + TestCommand, + } + + assert!(Args::clap() + .get_matches_from_safe(&["test", "some-command", "test-command"]) + .is_ok()); + assert!(Args::clap() + .get_matches_from_safe(&["test", "some", "test-command"]) + .is_ok()); + assert!(Args::clap() + .get_matches_from_safe(&["test", "some-command", "test"]) + .is_ok()); + assert!(Args::clap() + .get_matches_from_safe(&["test", "some", "test"]) + .is_ok()); +} diff --git a/structopt/tests/macro-errors.rs b/structopt/tests/macro-errors.rs new file mode 100644 index 0000000..ae4f5a2 --- /dev/null +++ b/structopt/tests/macro-errors.rs @@ -0,0 +1,13 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed + +#[rustversion::attr(any(not(stable), before(1.39)), ignore)] +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/*.rs"); +} diff --git a/structopt/tests/nested-subcommands.rs b/structopt/tests/nested-subcommands.rs new file mode 100644 index 0000000..1fbd166 --- /dev/null +++ b/structopt/tests/nested-subcommands.rs @@ -0,0 +1,193 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, PartialEq, Debug)] +struct Opt { + #[structopt(short, long)] + force: bool, + #[structopt(short, long, parse(from_occurrences))] + verbose: u64, + #[structopt(subcommand)] + cmd: Sub, +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Sub { + Fetch {}, + Add {}, +} + +#[derive(StructOpt, PartialEq, Debug)] +struct Opt2 { + #[structopt(short, long)] + force: bool, + #[structopt(short, long, parse(from_occurrences))] + verbose: u64, + #[structopt(subcommand)] + cmd: Option<Sub>, +} + +#[test] +fn test_no_cmd() { + let result = Opt::clap().get_matches_from_safe(&["test"]); + assert!(result.is_err()); + + assert_eq!( + Opt2 { + force: false, + verbose: 0, + cmd: None + }, + Opt2::from_clap(&Opt2::clap().get_matches_from(&["test"])) + ); +} + +#[test] +fn test_fetch() { + assert_eq!( + Opt { + force: false, + verbose: 3, + cmd: Sub::Fetch {} + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-vvv", "fetch"])) + ); + assert_eq!( + Opt { + force: true, + verbose: 0, + cmd: Sub::Fetch {} + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--force", "fetch"])) + ); +} + +#[test] +fn test_add() { + assert_eq!( + Opt { + force: false, + verbose: 0, + cmd: Sub::Add {} + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add"])) + ); + assert_eq!( + Opt { + force: false, + verbose: 2, + cmd: Sub::Add {} + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-vv", "add"])) + ); +} + +#[test] +fn test_badinput() { + let result = Opt::clap().get_matches_from_safe(&["test", "badcmd"]); + assert!(result.is_err()); + let result = Opt::clap().get_matches_from_safe(&["test", "add", "--verbose"]); + assert!(result.is_err()); + let result = Opt::clap().get_matches_from_safe(&["test", "--badopt", "add"]); + assert!(result.is_err()); + let result = Opt::clap().get_matches_from_safe(&["test", "add", "--badopt"]); + assert!(result.is_err()); +} + +#[derive(StructOpt, PartialEq, Debug)] +struct Opt3 { + #[structopt(short, long)] + all: bool, + #[structopt(subcommand)] + cmd: Sub2, +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Sub2 { + Foo { + file: String, + #[structopt(subcommand)] + cmd: Sub3, + }, + Bar {}, +} + +#[derive(StructOpt, 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::from_clap( + &Opt3::clap().get_matches_from(&["test", "--all", "foo", "lib.rs", "quux"]) + ) + ); +} + +#[derive(StructOpt, PartialEq, Debug)] +enum SubSubCmdWithOption { + Remote { + #[structopt(subcommand)] + cmd: Option<Remote>, + }, + Stash { + #[structopt(subcommand)] + cmd: Stash, + }, +} +#[derive(StructOpt, PartialEq, Debug)] +enum Remote { + Add { name: String, url: String }, + Remove { name: String }, +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Stash { + Save, + Pop, +} + +#[test] +fn sub_sub_cmd_with_option() { + fn make(args: &[&str]) -> Option<SubSubCmdWithOption> { + SubSubCmdWithOption::clap() + .get_matches_from_safe(args) + .ok() + .map(|m| SubSubCmdWithOption::from_clap(&m)) + } + 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/structopt/tests/non_literal_attributes.rs b/structopt/tests/non_literal_attributes.rs new file mode 100644 index 0000000..75b6b71 --- /dev/null +++ b/structopt/tests/non_literal_attributes.rs @@ -0,0 +1,147 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::clap::AppSettings; +use structopt::StructOpt; + +pub const DISPLAY_ORDER: usize = 2; + +// Check if the global settings compile +#[derive(StructOpt, Debug, PartialEq, Eq)] +#[structopt(global_settings = &[AppSettings::ColoredHelp])] +struct Opt { + #[structopt( + long = "x", + display_order = DISPLAY_ORDER, + next_line_help = true, + default_value = "0", + require_equals = true + )] + x: i32, + + #[structopt(short = "l", long = "level", aliases = &["set-level", "lvl"])] + level: String, + + #[structopt(long("values"))] + values: Vec<i32>, + + #[structopt(name = "FILE", requires_if("FILE", "values"))] + files: Vec<String>, +} + +#[test] +fn test_slice() { + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: Vec::new(), + values: vec![], + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1"])) + ); + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: Vec::new(), + values: vec![], + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--level", "1"])) + ); + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: Vec::new(), + values: vec![], + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--set-level", "1"])) + ); + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: Vec::new(), + values: vec![], + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--lvl", "1"])) + ); +} + +#[test] +fn test_multi_args() { + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: vec!["file".to_string()], + values: vec![], + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1", "file"])) + ); + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: vec!["FILE".to_string()], + values: vec![1], + }, + Opt::from_clap( + &Opt::clap().get_matches_from(&["test", "-l", "1", "--values", "1", "--", "FILE"]), + ) + ); +} + +#[test] +fn test_multi_args_fail() { + let result = Opt::clap().get_matches_from_safe(&["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::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1", "--x=1"])) + ); + let result = Opt::clap().get_matches_from_safe(&["test", "-l", "1", "--x", "1"]); + assert!(result.is_err()); +} + +fn parse_hex(input: &str) -> Result<u64, std::num::ParseIntError> { + u64::from_str_radix(input, 16) +} + +#[derive(StructOpt, PartialEq, Debug)] +struct HexOpt { + #[structopt(short = "n", parse(try_from_str = parse_hex))] + number: u64, +} + +#[test] +fn test_parse_hex_function_path() { + assert_eq!( + HexOpt { number: 5 }, + HexOpt::from_clap(&HexOpt::clap().get_matches_from(&["test", "-n", "5"])) + ); + assert_eq!( + HexOpt { number: 0xabcdef }, + HexOpt::from_clap(&HexOpt::clap().get_matches_from(&["test", "-n", "abcdef"])) + ); + + let err = HexOpt::clap() + .get_matches_from_safe(&["test", "-n", "gg"]) + .unwrap_err(); + assert!(err.message.contains("invalid digit found in string"), err); +} diff --git a/structopt/tests/options.rs b/structopt/tests/options.rs new file mode 100644 index 0000000..803abb4 --- /dev/null +++ b/structopt/tests/options.rs @@ -0,0 +1,336 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[test] +fn required_option() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, long)] + arg: i32, + } + assert_eq!( + Opt { arg: 42 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42"])) + ); + assert_eq!( + Opt { arg: 42 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "42"])) + ); + assert_eq!( + Opt { arg: 42 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--arg", "42"])) + ); + assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a42", "-a24"]) + .is_err()); +} + +#[test] +fn optional_option() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short)] + arg: Option<i32>, + } + assert_eq!( + Opt { arg: Some(42) }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42"])) + ); + assert_eq!( + Opt { arg: None }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a42", "-a24"]) + .is_err()); +} + +#[test] +fn option_with_default() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, default_value = "42")] + arg: i32, + } + assert_eq!( + Opt { arg: 24 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"])) + ); + assert_eq!( + Opt { arg: 42 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a42", "-a24"]) + .is_err()); +} + +#[test] +fn option_with_raw_default() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, default_value = "42")] + arg: i32, + } + assert_eq!( + Opt { arg: 24 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"])) + ); + assert_eq!( + Opt { arg: 42 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a42", "-a24"]) + .is_err()); +} + +#[test] +fn options() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, long)] + arg: Vec<i32>, + } + assert_eq!( + Opt { arg: vec![24] }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"])) + ); + assert_eq!( + Opt { arg: vec![] }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert_eq!( + Opt { arg: vec![24, 42] }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24", "--arg", "42"])) + ); +} + +#[test] +fn empy_default_value() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, default_value = "")] + arg: String, + } + assert_eq!(Opt { arg: "".into() }, Opt::from_iter(&["test"])); + assert_eq!( + Opt { arg: "foo".into() }, + Opt::from_iter(&["test", "-afoo"]) + ); +} + +#[test] +fn option_from_str() { + #[derive(Debug, PartialEq)] + struct A; + + impl<'a> From<&'a str> for A { + fn from(_: &str) -> A { + A + } + } + + #[derive(Debug, StructOpt, PartialEq)] + struct Opt { + #[structopt(parse(from_str))] + a: Option<A>, + } + + assert_eq!(Opt { a: None }, Opt::from_iter(&["test"])); + assert_eq!(Opt { a: Some(A) }, Opt::from_iter(&["test", "foo"])); +} + +#[test] +fn optional_argument_for_optional_option() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short)] + #[allow(clippy::option_option)] + arg: Option<Option<i32>>, + } + assert_eq!( + Opt { + arg: Some(Some(42)) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42"])) + ); + assert_eq!( + Opt { arg: Some(None) }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])) + ); + assert_eq!( + Opt { arg: None }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a42", "-a24"]) + .is_err()); +} + +#[test] +fn two_option_options() { + #[derive(StructOpt, PartialEq, Debug)] + #[allow(clippy::option_option)] + struct Opt { + #[structopt(short)] + arg: Option<Option<i32>>, + + #[structopt(long)] + field: Option<Option<String>>, + } + assert_eq!( + Opt { + arg: Some(Some(42)), + field: Some(Some("f".into())) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42", "--field", "f"])) + ); + assert_eq!( + Opt { + arg: Some(Some(42)), + field: Some(None) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42", "--field"])) + ); + assert_eq!( + Opt { + arg: Some(None), + field: Some(None) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "--field"])) + ); + assert_eq!( + Opt { + arg: Some(None), + field: Some(Some("f".into())) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "--field", "f"])) + ); + assert_eq!( + Opt { + arg: None, + field: Some(None) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--field"])) + ); + assert_eq!( + Opt { + arg: None, + field: None + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); +} + +#[test] +fn optional_vec() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short)] + arg: Option<Vec<i32>>, + } + assert_eq!( + Opt { arg: Some(vec![1]) }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "1"])) + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a1", "-a2"])) + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a1", "-a2", "-a"])) + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a1", "-a", "-a2"])) + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "1", "2"])) + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2, 3]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "1", "2", "-a", "3"])) + ); + + assert_eq!( + Opt { arg: Some(vec![]) }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])) + ); + + assert_eq!( + Opt { arg: Some(vec![]) }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "-a"])) + ); + + assert_eq!( + Opt { arg: None }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); +} + +#[test] +fn two_optional_vecs() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short)] + arg: Option<Vec<i32>>, + + #[structopt(short)] + b: Option<Vec<i32>>, + } + + assert_eq!( + Opt { + arg: Some(vec![1]), + b: Some(vec![]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "1", "-b"])) + ); + + assert_eq!( + Opt { + arg: Some(vec![1]), + b: Some(vec![]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "-b", "-a1"])) + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2]), + b: Some(vec![1, 2]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a1", "-a2", "-b1", "-b2"])) + ); + + assert_eq!( + Opt { arg: None, b: None }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); +} diff --git a/structopt/tests/privacy.rs b/structopt/tests/privacy.rs new file mode 100644 index 0000000..730bbce --- /dev/null +++ b/structopt/tests/privacy.rs @@ -0,0 +1,32 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +mod options { + use super::StructOpt; + + #[derive(Debug, StructOpt)] + pub struct Options { + #[structopt(subcommand)] + pub subcommand: super::subcommands::SubCommand, + } +} + +mod subcommands { + use super::StructOpt; + + #[derive(Debug, StructOpt)] + pub enum SubCommand { + /// foo + Foo { + /// foo + bars: Vec<String>, + }, + } +} diff --git a/structopt/tests/raw_bool_literal.rs b/structopt/tests/raw_bool_literal.rs new file mode 100644 index 0000000..faf8628 --- /dev/null +++ b/structopt/tests/raw_bool_literal.rs @@ -0,0 +1,29 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[test] +fn raw_bool_literal() { + #[derive(StructOpt, Debug, PartialEq)] + #[structopt(no_version, name = "raw_bool")] + struct Opt { + #[structopt(raw(false))] + a: String, + #[structopt(raw(true))] + b: String, + } + + assert_eq!( + Opt { + a: "one".into(), + b: "--help".into() + }, + Opt::from_iter(&["test", "one", "--", "--help"]) + ); +} diff --git a/structopt/tests/raw_idents.rs b/structopt/tests/raw_idents.rs new file mode 100644 index 0000000..c00ff66 --- /dev/null +++ b/structopt/tests/raw_idents.rs @@ -0,0 +1,17 @@ +use structopt::StructOpt; + +#[test] +fn raw_idents() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(short, long)] + r#type: Vec<String>, + } + + assert_eq!( + Opt { + r#type: vec!["long".into(), "short".into()] + }, + Opt::from_iter(&["test", "--type", "long", "-t", "short"]) + ); +} diff --git a/structopt/tests/rename_all_env.rs b/structopt/tests/rename_all_env.rs new file mode 100644 index 0000000..1979e84 --- /dev/null +++ b/structopt/tests/rename_all_env.rs @@ -0,0 +1,46 @@ +mod utils; + +use structopt::StructOpt; +use utils::*; + +#[test] +fn it_works() { + #[derive(Debug, PartialEq, StructOpt)] + #[structopt(rename_all_env = "kebab")] + struct BehaviorModel { + #[structopt(env)] + be_nice: String, + } + + let help = get_help::<BehaviorModel>(); + assert!(help.contains("[env: be-nice=]")); +} + +#[test] +fn default_is_screaming() { + #[derive(Debug, PartialEq, StructOpt)] + struct BehaviorModel { + #[structopt(env)] + be_nice: String, + } + + let help = get_help::<BehaviorModel>(); + assert!(help.contains("[env: BE_NICE=]")); +} + +#[test] +fn overridable() { + #[derive(Debug, PartialEq, StructOpt)] + #[structopt(rename_all_env = "kebab")] + struct BehaviorModel { + #[structopt(env)] + be_nice: String, + + #[structopt(rename_all_env = "pascal", env)] + be_agressive: String, + } + + let help = get_help::<BehaviorModel>(); + assert!(help.contains("[env: be-nice=]")); + assert!(help.contains("[env: BeAgressive=]")); +} diff --git a/structopt/tests/skip.rs b/structopt/tests/skip.rs new file mode 100644 index 0000000..47682d8 --- /dev/null +++ b/structopt/tests/skip.rs @@ -0,0 +1,148 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[test] +fn skip_1() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(short)] + x: u32, + #[structopt(skip)] + s: u32, + } + + assert!(Opt::from_iter_safe(&["test", "-x", "10", "20"]).is_err()); + assert_eq!( + Opt::from_iter(&["test", "-x", "10"]), + Opt { + x: 10, + s: 0, // default + } + ); +} + +#[test] +fn skip_2() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(short)] + x: u32, + #[structopt(skip)] + ss: String, + #[structopt(skip)] + sn: u8, + y: u32, + #[structopt(skip)] + sz: u16, + t: u32, + } + + assert_eq!( + Opt::from_iter(&["test", "-x", "10", "20", "30"]), + 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 { + return Kind::B; + } + } + + #[derive(StructOpt, Debug, PartialEq)] + pub struct Opt { + #[structopt(long, short)] + number: u32, + #[structopt(skip)] + k: Kind, + #[structopt(skip)] + v: Vec<u32>, + } + + assert_eq!( + Opt::from_iter(&["test", "-n", "10"]), + Opt { + number: 10, + k: Kind::B, + v: vec![], + } + ); +} + +#[test] +fn skip_help_doc_comments() { + #[derive(StructOpt, Debug, PartialEq)] + pub struct Opt { + #[structopt(skip, help = "internal_stuff")] + a: u32, + + #[structopt(skip, long_help = "internal_stuff\ndo not touch")] + b: u32, + + /// Not meant to be used by clap. + /// + /// I want a default here. + #[structopt(skip)] + c: u32, + + #[structopt(short, parse(try_from_str))] + n: u32, + } + + assert_eq!( + Opt::from_iter(&["test", "-n", "10"]), + Opt { + n: 10, + a: 0, + b: 0, + c: 0, + } + ); +} + +#[test] +fn skip_val() { + #[derive(StructOpt, Debug, PartialEq)] + pub struct Opt { + #[structopt(long, short)] + number: u32, + + #[structopt(skip = "key")] + k: String, + + #[structopt(skip = vec![1, 2, 3])] + v: Vec<u32>, + } + + assert_eq!( + Opt::from_iter(&["test", "-n", "10"]), + Opt { + number: 10, + k: "key".into(), + v: vec![1, 2, 3] + } + ); +} diff --git a/structopt/tests/special_types.rs b/structopt/tests/special_types.rs new file mode 100644 index 0000000..ffed5e2 --- /dev/null +++ b/structopt/tests/special_types.rs @@ -0,0 +1,73 @@ +//! Checks that types like `::std::option::Option` are not special + +use structopt::StructOpt; + +#[rustversion::since(1.37)] +#[test] +fn special_types_bool() { + mod inner { + #[allow(non_camel_case_types)] + #[derive(PartialEq, Debug)] + pub struct bool(pub String); + + impl std::str::FromStr for self::bool { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(self::bool(s.into())) + } + } + }; + + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + arg: inner::bool, + } + + assert_eq!( + Opt { + arg: inner::bool("success".into()) + }, + Opt::from_iter(&["test", "success"]) + ); +} + +#[test] +fn special_types_option() { + fn parser(s: &str) -> Option<String> { + Some(s.to_string()) + } + + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(parse(from_str = parser))] + arg: ::std::option::Option<String>, + } + + assert_eq!( + Opt { + arg: Some("success".into()) + }, + Opt::from_iter(&["test", "success"]) + ); +} + +#[test] +fn special_types_vec() { + fn parser(s: &str) -> Vec<String> { + vec![s.to_string()] + } + + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(parse(from_str = parser))] + arg: ::std::vec::Vec<String>, + } + + assert_eq!( + Opt { + arg: vec!["success".into()] + }, + Opt::from_iter(&["test", "success"]) + ); +} diff --git a/structopt/tests/subcommands.rs b/structopt/tests/subcommands.rs new file mode 100644 index 0000000..170c0da --- /dev/null +++ b/structopt/tests/subcommands.rs @@ -0,0 +1,213 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +mod utils; + +use structopt::StructOpt; +use utils::*; + +#[derive(StructOpt, PartialEq, Debug)] +enum Opt { + /// Fetch stuff from GitHub + Fetch { + #[structopt(long)] + all: bool, + #[structopt(short, long)] + /// Overwrite local branches. + force: bool, + repo: String, + }, + + Add { + #[structopt(short, long)] + interactive: bool, + #[structopt(short, long)] + verbose: bool, + }, +} + +#[test] +fn test_fetch() { + assert_eq!( + Opt::Fetch { + all: true, + force: false, + repo: "origin".to_string() + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "fetch", "--all", "origin"])) + ); + assert_eq!( + Opt::Fetch { + all: false, + force: true, + repo: "origin".to_string() + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "fetch", "-f", "origin"])) + ); +} + +#[test] +fn test_add() { + assert_eq!( + Opt::Add { + interactive: false, + verbose: false + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add"])) + ); + assert_eq!( + Opt::Add { + interactive: true, + verbose: true + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add", "-i", "-v"])) + ); +} + +#[test] +fn test_no_parse() { + let result = Opt::clap().get_matches_from_safe(&["test", "badcmd", "-i", "-v"]); + assert!(result.is_err()); + + let result = Opt::clap().get_matches_from_safe(&["test", "add", "--badoption"]); + assert!(result.is_err()); + + let result = Opt::clap().get_matches_from_safe(&["test"]); + assert!(result.is_err()); +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Opt2 { + DoSomething { 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::from_clap(&Opt2::clap().get_matches_from(&["test", "do-something", "blah"])) + ); +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Opt3 { + Add, + Init, + Fetch, +} + +#[test] +fn test_null_commands() { + assert_eq!( + Opt3::Add, + Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "add"])) + ); + assert_eq!( + Opt3::Init, + Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "init"])) + ); + assert_eq!( + Opt3::Fetch, + Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "fetch"])) + ); +} + +#[derive(StructOpt, PartialEq, Debug)] +#[structopt(about = "Not shown")] +struct Add { + file: String, +} +/// Not shown +#[derive(StructOpt, PartialEq, Debug)] +struct Fetch { + remote: String, +} +#[derive(StructOpt, 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::from_clap(&Opt4::clap().get_matches_from(&["test", "add", "f"])) + ); + assert_eq!( + Opt4::Init, + Opt4::from_clap(&Opt4::clap().get_matches_from(&["test", "init"])) + ); + assert_eq!( + Opt4::Fetch(Fetch { + remote: "origin".to_string() + }), + Opt4::from_clap(&Opt4::clap().get_matches_from(&["test", "fetch", "origin"])) + ); + + let output = get_long_help::<Opt4>(); + + assert!(output.contains("download history from remote")); + assert!(output.contains("Add a file")); + assert!(!output.contains("Not shown")); +} + +#[test] +fn enum_in_enum_subsubcommand() { + #[derive(StructOpt, Debug, PartialEq)] + pub enum Opt { + Daemon(DaemonCommand), + } + + #[derive(StructOpt, Debug, PartialEq)] + pub enum DaemonCommand { + Start, + Stop, + } + + let result = Opt::clap().get_matches_from_safe(&["test"]); + assert!(result.is_err()); + + let result = Opt::clap().get_matches_from_safe(&["test", "daemon"]); + assert!(result.is_err()); + + let result = Opt::from_iter(&["test", "daemon", "start"]); + assert_eq!(Opt::Daemon(DaemonCommand::Start), result); +} + +#[test] +fn flatten_enum() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(flatten)] + sub_cmd: SubCmd, + } + #[derive(StructOpt, Debug, PartialEq)] + enum SubCmd { + Foo, + Bar, + } + + assert!(Opt::from_iter_safe(&["test"]).is_err()); + assert_eq!( + Opt::from_iter(&["test", "foo"]), + Opt { + sub_cmd: SubCmd::Foo + } + ); +} diff --git a/structopt/tests/ui/bool_default_value.rs b/structopt/tests/ui/bool_default_value.rs new file mode 100644 index 0000000..9bdb0c9 --- /dev/null +++ b/structopt/tests/ui/bool_default_value.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(short, default_value = true)] + b: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/bool_default_value.stderr b/structopt/tests/ui/bool_default_value.stderr new file mode 100644 index 0000000..1e26a2d --- /dev/null +++ b/structopt/tests/ui/bool_default_value.stderr @@ -0,0 +1,5 @@ +error: default_value is meaningless for bool + --> $DIR/bool_default_value.rs:14:24 + | +14 | #[structopt(short, default_value = true)] + | ^^^^^^^^^^^^^ diff --git a/structopt/tests/ui/bool_required.rs b/structopt/tests/ui/bool_required.rs new file mode 100644 index 0000000..018223c --- /dev/null +++ b/structopt/tests/ui/bool_required.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(short, required = true)] + b: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/bool_required.stderr b/structopt/tests/ui/bool_required.stderr new file mode 100644 index 0000000..0b80d48 --- /dev/null +++ b/structopt/tests/ui/bool_required.stderr @@ -0,0 +1,5 @@ +error: required is meaningless for bool + --> $DIR/bool_required.rs:14:24 + | +14 | #[structopt(short, required = true)] + | ^^^^^^^^ diff --git a/structopt/tests/ui/flatten_and_doc.rs b/structopt/tests/ui/flatten_and_doc.rs new file mode 100644 index 0000000..2dc154d --- /dev/null +++ b/structopt/tests/ui/flatten_and_doc.rs @@ -0,0 +1,30 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +struct DaemonOpts { + #[structopt(short)] + user: String, + #[structopt(short)] + group: String, +} + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + /// Opts. + #[structopt(flatten)] + opts: DaemonOpts, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/flatten_and_doc.stderr b/structopt/tests/ui/flatten_and_doc.stderr new file mode 100644 index 0000000..2724dbb --- /dev/null +++ b/structopt/tests/ui/flatten_and_doc.stderr @@ -0,0 +1,5 @@ +error: methods and doc comments are not allowed for flattened entry + --> $DIR/flatten_and_doc.rs:23:17 + | +23 | #[structopt(flatten)] + | ^^^^^^^ diff --git a/structopt/tests/ui/flatten_and_methods.rs b/structopt/tests/ui/flatten_and_methods.rs new file mode 100644 index 0000000..ff1af2e --- /dev/null +++ b/structopt/tests/ui/flatten_and_methods.rs @@ -0,0 +1,29 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +struct DaemonOpts { + #[structopt(short)] + user: String, + #[structopt(short)] + group: String, +} + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(short, flatten)] + opts: DaemonOpts, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/flatten_and_methods.stderr b/structopt/tests/ui/flatten_and_methods.stderr new file mode 100644 index 0000000..f058eb3 --- /dev/null +++ b/structopt/tests/ui/flatten_and_methods.stderr @@ -0,0 +1,5 @@ +error: methods and doc comments are not allowed for flattened entry + --> $DIR/flatten_and_methods.rs:22:24 + | +22 | #[structopt(short, flatten)] + | ^^^^^^^ diff --git a/structopt/tests/ui/flatten_and_parse.rs b/structopt/tests/ui/flatten_and_parse.rs new file mode 100644 index 0000000..3317272 --- /dev/null +++ b/structopt/tests/ui/flatten_and_parse.rs @@ -0,0 +1,29 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +struct DaemonOpts { + #[structopt(short)] + user: String, + #[structopt(short)] + group: String, +} + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(flatten, parse(from_occurrences))] + opts: DaemonOpts, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/flatten_and_parse.stderr b/structopt/tests/ui/flatten_and_parse.stderr new file mode 100644 index 0000000..e217a84 --- /dev/null +++ b/structopt/tests/ui/flatten_and_parse.stderr @@ -0,0 +1,5 @@ +error: parse attribute is not allowed for flattened entry + --> $DIR/flatten_and_parse.rs:22:26 + | +22 | #[structopt(flatten, parse(from_occurrences))] + | ^^^^^ diff --git a/structopt/tests/ui/non_existent_attr.rs b/structopt/tests/ui/non_existent_attr.rs new file mode 100644 index 0000000..96daf45 --- /dev/null +++ b/structopt/tests/ui/non_existent_attr.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(short, non_existing_attribute = 1)] + debug: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/non_existent_attr.stderr b/structopt/tests/ui/non_existent_attr.stderr new file mode 100644 index 0000000..17bb87f --- /dev/null +++ b/structopt/tests/ui/non_existent_attr.stderr @@ -0,0 +1,5 @@ +error[E0599]: no method named `non_existing_attribute` found for type `clap::args::arg::Arg<'_, '_>` in the current scope + --> $DIR/non_existent_attr.rs:14:24 + | +14 | #[structopt(short, non_existing_attribute = 1)] + | ^^^^^^^^^^^^^^^^^^^^^^ method not found in `clap::args::arg::Arg<'_, '_>` diff --git a/structopt/tests/ui/opt_opt_nonpositional.rs b/structopt/tests/ui/opt_opt_nonpositional.rs new file mode 100644 index 0000000..2a08105 --- /dev/null +++ b/structopt/tests/ui/opt_opt_nonpositional.rs @@ -0,0 +1,20 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + n: Option<Option<u32>>, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/opt_opt_nonpositional.stderr b/structopt/tests/ui/opt_opt_nonpositional.stderr new file mode 100644 index 0000000..586bf7a --- /dev/null +++ b/structopt/tests/ui/opt_opt_nonpositional.stderr @@ -0,0 +1,5 @@ +error: Option<Option<T>> type is meaningless for positional argument + --> $DIR/opt_opt_nonpositional.rs:14:8 + | +14 | n: Option<Option<u32>>, + | ^^^^^^ diff --git a/structopt/tests/ui/opt_vec_nonpositional.rs b/structopt/tests/ui/opt_vec_nonpositional.rs new file mode 100644 index 0000000..0f6f078 --- /dev/null +++ b/structopt/tests/ui/opt_vec_nonpositional.rs @@ -0,0 +1,20 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + n: Option<Vec<u32>>, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/opt_vec_nonpositional.stderr b/structopt/tests/ui/opt_vec_nonpositional.stderr new file mode 100644 index 0000000..6f01de5 --- /dev/null +++ b/structopt/tests/ui/opt_vec_nonpositional.stderr @@ -0,0 +1,5 @@ +error: Option<Vec<T>> type is meaningless for positional argument + --> $DIR/opt_vec_nonpositional.rs:14:8 + | +14 | n: Option<Vec<u32>>, + | ^^^^^^ diff --git a/structopt/tests/ui/option_default_value.rs b/structopt/tests/ui/option_default_value.rs new file mode 100644 index 0000000..a86bc0e --- /dev/null +++ b/structopt/tests/ui/option_default_value.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(short, default_value = 1)] + n: Option<u32>, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/option_default_value.stderr b/structopt/tests/ui/option_default_value.stderr new file mode 100644 index 0000000..2215497 --- /dev/null +++ b/structopt/tests/ui/option_default_value.stderr @@ -0,0 +1,5 @@ +error: default_value is meaningless for Option + --> $DIR/option_default_value.rs:14:24 + | +14 | #[structopt(short, default_value = 1)] + | ^^^^^^^^^^^^^ diff --git a/structopt/tests/ui/option_required.rs b/structopt/tests/ui/option_required.rs new file mode 100644 index 0000000..d91afbf --- /dev/null +++ b/structopt/tests/ui/option_required.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(short, required = true)] + n: Option<u32>, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/option_required.stderr b/structopt/tests/ui/option_required.stderr new file mode 100644 index 0000000..0230d57 --- /dev/null +++ b/structopt/tests/ui/option_required.stderr @@ -0,0 +1,5 @@ +error: required is meaningless for Option + --> $DIR/option_required.rs:14:24 + | +14 | #[structopt(short, required = true)] + | ^^^^^^^^ diff --git a/structopt/tests/ui/parse_empty_try_from_os.rs b/structopt/tests/ui/parse_empty_try_from_os.rs new file mode 100644 index 0000000..acfef0b --- /dev/null +++ b/structopt/tests/ui/parse_empty_try_from_os.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(parse(try_from_os_str))] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/parse_empty_try_from_os.stderr b/structopt/tests/ui/parse_empty_try_from_os.stderr new file mode 100644 index 0000000..3dc9f24 --- /dev/null +++ b/structopt/tests/ui/parse_empty_try_from_os.stderr @@ -0,0 +1,5 @@ +error: you must set parser for `try_from_os_str` explicitly + --> $DIR/parse_empty_try_from_os.rs:14:23 + | +14 | #[structopt(parse(try_from_os_str))] + | ^^^^^^^^^^^^^^^ diff --git a/structopt/tests/ui/parse_function_is_not_path.rs b/structopt/tests/ui/parse_function_is_not_path.rs new file mode 100644 index 0000000..5eebc57 --- /dev/null +++ b/structopt/tests/ui/parse_function_is_not_path.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(parse(from_str = "2"))] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/parse_function_is_not_path.stderr b/structopt/tests/ui/parse_function_is_not_path.stderr new file mode 100644 index 0000000..7cf7444 --- /dev/null +++ b/structopt/tests/ui/parse_function_is_not_path.stderr @@ -0,0 +1,5 @@ +error: `parse` argument must be a function path + --> $DIR/parse_function_is_not_path.rs:14:34 + | +14 | #[structopt(parse(from_str = "2"))] + | ^^^ diff --git a/structopt/tests/ui/parse_literal_spec.rs b/structopt/tests/ui/parse_literal_spec.rs new file mode 100644 index 0000000..b6f125a --- /dev/null +++ b/structopt/tests/ui/parse_literal_spec.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(parse("from_str"))] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/parse_literal_spec.stderr b/structopt/tests/ui/parse_literal_spec.stderr new file mode 100644 index 0000000..6e99e8b --- /dev/null +++ b/structopt/tests/ui/parse_literal_spec.stderr @@ -0,0 +1,5 @@ +error: parser specification must start with identifier + --> $DIR/parse_literal_spec.rs:14:23 + | +14 | #[structopt(parse("from_str"))] + | ^^^^^^^^^^ diff --git a/structopt/tests/ui/parse_not_zero_args.rs b/structopt/tests/ui/parse_not_zero_args.rs new file mode 100644 index 0000000..8729178 --- /dev/null +++ b/structopt/tests/ui/parse_not_zero_args.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(parse(from_str, from_str))] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/parse_not_zero_args.stderr b/structopt/tests/ui/parse_not_zero_args.stderr new file mode 100644 index 0000000..11abe06 --- /dev/null +++ b/structopt/tests/ui/parse_not_zero_args.stderr @@ -0,0 +1,5 @@ +error: parse must have exactly one argument + --> $DIR/parse_not_zero_args.rs:14:17 + | +14 | #[structopt(parse(from_str, from_str))] + | ^^^^^ diff --git a/structopt/tests/ui/positional_bool.rs b/structopt/tests/ui/positional_bool.rs new file mode 100644 index 0000000..4dbf538 --- /dev/null +++ b/structopt/tests/ui/positional_bool.rs @@ -0,0 +1,10 @@ +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +struct Opt { + verbose: bool, +} + +fn main() { + Opt::from_args(); +}
\ No newline at end of file diff --git a/structopt/tests/ui/positional_bool.stderr b/structopt/tests/ui/positional_bool.stderr new file mode 100644 index 0000000..c3ed1ad --- /dev/null +++ b/structopt/tests/ui/positional_bool.stderr @@ -0,0 +1,10 @@ +error: `bool` cannot be used as positional parameter with default parser + + = help: if you want to create a flag add `long` or `short` + = help: If you really want a boolean parameter add an explicit parser, for example `parse(try_from_str)` + = note: see also https://github.com/TeXitoi/structopt/tree/master/examples/true_or_false.rs + + --> $DIR/positional_bool.rs:5:14 + | +5 | verbose: bool, + | ^^^^ diff --git a/structopt/tests/ui/raw.rs b/structopt/tests/ui/raw.rs new file mode 100644 index 0000000..b94f783 --- /dev/null +++ b/structopt/tests/ui/raw.rs @@ -0,0 +1,25 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +struct Opt { + #[structopt(raw(case_insensitive = "true"))] + s: String, +} + +#[derive(StructOpt, Debug)] +struct Opt2 { + #[structopt(raw(requires_if = r#""one", "two""#))] + s: String, +} +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/raw.stderr b/structopt/tests/ui/raw.stderr new file mode 100644 index 0000000..93b5e38 --- /dev/null +++ b/structopt/tests/ui/raw.stderr @@ -0,0 +1,19 @@ +error: `#[structopt(raw(...))` attributes are removed in structopt 0.3, they are replaced with raw methods + + = help: if you meant to call `clap::Arg::raw()` method you should use bool literal, like `raw(true)` or `raw(false)` + = note: if you need to call `clap::Arg/App::case_insensitive` method you can do it like this: #[structopt(case_insensitive = true)] + + --> $DIR/raw.rs:13:17 + | +13 | #[structopt(raw(case_insensitive = "true"))] + | ^^^ + +error: `#[structopt(raw(...))` attributes are removed in structopt 0.3, they are replaced with raw methods + + = help: if you meant to call `clap::Arg::raw()` method you should use bool literal, like `raw(true)` or `raw(false)` + = note: if you need to call `clap::Arg/App::requires_if` method you can do it like this: #[structopt(requires_if("one", "two"))] + + --> $DIR/raw.rs:19:17 + | +19 | #[structopt(raw(requires_if = r#""one", "two""#))] + | ^^^ diff --git a/structopt/tests/ui/rename_all_wrong_casing.rs b/structopt/tests/ui/rename_all_wrong_casing.rs new file mode 100644 index 0000000..4dabe14 --- /dev/null +++ b/structopt/tests/ui/rename_all_wrong_casing.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic", rename_all = "fail")] +struct Opt { + #[structopt(short)] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/rename_all_wrong_casing.stderr b/structopt/tests/ui/rename_all_wrong_casing.stderr new file mode 100644 index 0000000..2a72080 --- /dev/null +++ b/structopt/tests/ui/rename_all_wrong_casing.stderr @@ -0,0 +1,5 @@ +error: unsupported casing: `fail` + --> $DIR/rename_all_wrong_casing.rs:12:42 + | +12 | #[structopt(name = "basic", rename_all = "fail")] + | ^^^^^^ diff --git a/structopt/tests/ui/skip_flatten.rs b/structopt/tests/ui/skip_flatten.rs new file mode 100644 index 0000000..8668ec2 --- /dev/null +++ b/structopt/tests/ui/skip_flatten.rs @@ -0,0 +1,42 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(skip, flatten)] + cmd: Command, +} + +#[derive(StructOpt, Debug)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +impl Default for Command { + fn default() -> Self { + Command::Pound { acorns: 0 } + } +} + +fn main() { + let opt = MakeCookie::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/skip_flatten.stderr b/structopt/tests/ui/skip_flatten.stderr new file mode 100644 index 0000000..76477a3 --- /dev/null +++ b/structopt/tests/ui/skip_flatten.stderr @@ -0,0 +1,5 @@ +error: subcommand, flatten and skip cannot be used together + --> $DIR/skip_flatten.rs:17:23 + | +17 | #[structopt(skip, flatten)] + | ^^^^^^^ diff --git a/structopt/tests/ui/skip_subcommand.rs b/structopt/tests/ui/skip_subcommand.rs new file mode 100644 index 0000000..5d21426 --- /dev/null +++ b/structopt/tests/ui/skip_subcommand.rs @@ -0,0 +1,42 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(subcommand, skip)] + cmd: Command, +} + +#[derive(StructOpt, Debug)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +impl Default for Command { + fn default() -> Self { + Command::Pound { acorns: 0 } + } +} + +fn main() { + let opt = MakeCookie::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/skip_subcommand.stderr b/structopt/tests/ui/skip_subcommand.stderr new file mode 100644 index 0000000..aba2d69 --- /dev/null +++ b/structopt/tests/ui/skip_subcommand.stderr @@ -0,0 +1,5 @@ +error: subcommand, flatten and skip cannot be used together + --> $DIR/skip_subcommand.rs:17:29 + | +17 | #[structopt(subcommand, skip)] + | ^^^^ diff --git a/structopt/tests/ui/skip_with_other_options.rs b/structopt/tests/ui/skip_with_other_options.rs new file mode 100644 index 0000000..73c5342 --- /dev/null +++ b/structopt/tests/ui/skip_with_other_options.rs @@ -0,0 +1,15 @@ +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "test")] +pub struct Opt { + #[structopt(long)] + a: u32, + #[structopt(skip, long)] + b: u32, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/skip_with_other_options.stderr b/structopt/tests/ui/skip_with_other_options.stderr new file mode 100644 index 0000000..3345f92 --- /dev/null +++ b/structopt/tests/ui/skip_with_other_options.stderr @@ -0,0 +1,5 @@ +error: methods are not allowed for skipped fields + --> $DIR/skip_with_other_options.rs:8:17 + | +8 | #[structopt(skip, long)] + | ^^^^ diff --git a/structopt/tests/ui/skip_without_default.rs b/structopt/tests/ui/skip_without_default.rs new file mode 100644 index 0000000..bc47511 --- /dev/null +++ b/structopt/tests/ui/skip_without_default.rs @@ -0,0 +1,29 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(Debug)] +enum Kind { + A, + B, +} + +#[derive(StructOpt, Debug)] +#[structopt(name = "test")] +pub struct Opt { + #[structopt(short)] + number: u32, + #[structopt(skip)] + k: Kind, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/skip_without_default.stderr b/structopt/tests/ui/skip_without_default.stderr new file mode 100644 index 0000000..330898f --- /dev/null +++ b/structopt/tests/ui/skip_without_default.stderr @@ -0,0 +1,9 @@ +error[E0277]: the trait bound `Kind: std::default::Default` is not satisfied + --> $DIR/skip_without_default.rs:22:17 + | +22 | #[structopt(skip)] + | ^^^^ the trait `std::default::Default` is not implemented for `Kind` + | + = note: required by `std::default::Default::default` + +For more information about this error, try `rustc --explain E0277`. diff --git a/structopt/tests/ui/struct_flatten.rs b/structopt/tests/ui/struct_flatten.rs new file mode 100644 index 0000000..2b205f1 --- /dev/null +++ b/structopt/tests/ui/struct_flatten.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic", flatten)] +struct Opt { + #[structopt(short)] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/struct_flatten.stderr b/structopt/tests/ui/struct_flatten.stderr new file mode 100644 index 0000000..7f0fc6d --- /dev/null +++ b/structopt/tests/ui/struct_flatten.stderr @@ -0,0 +1,5 @@ +error: flatten is only allowed on fields + --> $DIR/struct_flatten.rs:12:29 + | +12 | #[structopt(name = "basic", flatten)] + | ^^^^^^^ diff --git a/structopt/tests/ui/struct_parse.rs b/structopt/tests/ui/struct_parse.rs new file mode 100644 index 0000000..e428b23 --- /dev/null +++ b/structopt/tests/ui/struct_parse.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic", parse(from_str))] +struct Opt { + #[structopt(short)] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/struct_parse.stderr b/structopt/tests/ui/struct_parse.stderr new file mode 100644 index 0000000..5518214 --- /dev/null +++ b/structopt/tests/ui/struct_parse.stderr @@ -0,0 +1,5 @@ +error: `parse` attribute is only allowed on fields + --> $DIR/struct_parse.rs:12:29 + | +12 | #[structopt(name = "basic", parse(from_str))] + | ^^^^^ diff --git a/structopt/tests/ui/struct_subcommand.rs b/structopt/tests/ui/struct_subcommand.rs new file mode 100644 index 0000000..ac0b145 --- /dev/null +++ b/structopt/tests/ui/struct_subcommand.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic", subcommand)] +struct Opt { + #[structopt(short)] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/struct_subcommand.stderr b/structopt/tests/ui/struct_subcommand.stderr new file mode 100644 index 0000000..438f6f8 --- /dev/null +++ b/structopt/tests/ui/struct_subcommand.stderr @@ -0,0 +1,5 @@ +error: subcommand is only allowed on fields + --> $DIR/struct_subcommand.rs:12:29 + | +12 | #[structopt(name = "basic", subcommand)] + | ^^^^^^^^^^ diff --git a/structopt/tests/ui/structopt_empty_attr.rs b/structopt/tests/ui/structopt_empty_attr.rs new file mode 100644 index 0000000..a7fc0b9 --- /dev/null +++ b/structopt/tests/ui/structopt_empty_attr.rs @@ -0,0 +1,22 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt] + debug: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} + diff --git a/structopt/tests/ui/structopt_empty_attr.stderr b/structopt/tests/ui/structopt_empty_attr.stderr new file mode 100644 index 0000000..dde3630 --- /dev/null +++ b/structopt/tests/ui/structopt_empty_attr.stderr @@ -0,0 +1,5 @@ +error: expected parentheses after `structopt` + --> $DIR/structopt_empty_attr.rs:14:7 + | +14 | #[structopt] + | ^^^^^^^^^ diff --git a/structopt/tests/ui/structopt_name_value_attr.rs b/structopt/tests/ui/structopt_name_value_attr.rs new file mode 100644 index 0000000..3d9388f --- /dev/null +++ b/structopt/tests/ui/structopt_name_value_attr.rs @@ -0,0 +1,22 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt = "short"] + debug: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} + diff --git a/structopt/tests/ui/structopt_name_value_attr.stderr b/structopt/tests/ui/structopt_name_value_attr.stderr new file mode 100644 index 0000000..f681978 --- /dev/null +++ b/structopt/tests/ui/structopt_name_value_attr.stderr @@ -0,0 +1,5 @@ +error: expected parentheses + --> $DIR/structopt_name_value_attr.rs:14:17 + | +14 | #[structopt = "short"] + | ^ diff --git a/structopt/tests/ui/subcommand_and_flatten.rs b/structopt/tests/ui/subcommand_and_flatten.rs new file mode 100644 index 0000000..742ee6d --- /dev/null +++ b/structopt/tests/ui/subcommand_and_flatten.rs @@ -0,0 +1,36 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(subcommand, flatten)] + cmd: Command, +} + +#[derive(StructOpt, Debug)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +fn main() { + let opt = MakeCookie::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/subcommand_and_flatten.stderr b/structopt/tests/ui/subcommand_and_flatten.stderr new file mode 100644 index 0000000..cacea5e --- /dev/null +++ b/structopt/tests/ui/subcommand_and_flatten.stderr @@ -0,0 +1,5 @@ +error: subcommand, flatten and skip cannot be used together + --> $DIR/subcommand_and_flatten.rs:17:29 + | +17 | #[structopt(subcommand, flatten)] + | ^^^^^^^ diff --git a/structopt/tests/ui/subcommand_and_methods.rs b/structopt/tests/ui/subcommand_and_methods.rs new file mode 100644 index 0000000..890f10c --- /dev/null +++ b/structopt/tests/ui/subcommand_and_methods.rs @@ -0,0 +1,36 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(subcommand, long)] + cmd: Command, +} + +#[derive(StructOpt, Debug)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +fn main() { + let opt = MakeCookie::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/subcommand_and_methods.stderr b/structopt/tests/ui/subcommand_and_methods.stderr new file mode 100644 index 0000000..ccaf28d --- /dev/null +++ b/structopt/tests/ui/subcommand_and_methods.stderr @@ -0,0 +1,5 @@ +error: methods in attributes are not allowed for subcommand + --> $DIR/subcommand_and_methods.rs:17:17 + | +17 | #[structopt(subcommand, long)] + | ^^^^^^^^^^ diff --git a/structopt/tests/ui/subcommand_and_parse.rs b/structopt/tests/ui/subcommand_and_parse.rs new file mode 100644 index 0000000..f24e4bc --- /dev/null +++ b/structopt/tests/ui/subcommand_and_parse.rs @@ -0,0 +1,36 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(subcommand, parse(from_occurrences))] + cmd: Command, +} + +#[derive(StructOpt, Debug)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +fn main() { + let opt = MakeCookie::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/subcommand_and_parse.stderr b/structopt/tests/ui/subcommand_and_parse.stderr new file mode 100644 index 0000000..4070056 --- /dev/null +++ b/structopt/tests/ui/subcommand_and_parse.stderr @@ -0,0 +1,5 @@ +error: parse attribute is not allowed for subcommand + --> $DIR/subcommand_and_parse.rs:17:29 + | +17 | #[structopt(subcommand, parse(from_occurrences))] + | ^^^^^ diff --git a/structopt/tests/ui/subcommand_opt_opt.rs b/structopt/tests/ui/subcommand_opt_opt.rs new file mode 100644 index 0000000..1dd84e5 --- /dev/null +++ b/structopt/tests/ui/subcommand_opt_opt.rs @@ -0,0 +1,36 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(subcommand)] + cmd: Option<Option<Command>>, +} + +#[derive(StructOpt, Debug)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +fn main() { + let opt = MakeCookie::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/subcommand_opt_opt.stderr b/structopt/tests/ui/subcommand_opt_opt.stderr new file mode 100644 index 0000000..daad02f --- /dev/null +++ b/structopt/tests/ui/subcommand_opt_opt.stderr @@ -0,0 +1,5 @@ +error: Option<Option<T>> type is not allowed for subcommand + --> $DIR/subcommand_opt_opt.rs:18:10 + | +18 | cmd: Option<Option<Command>>, + | ^^^^^^ diff --git a/structopt/tests/ui/subcommand_opt_vec.rs b/structopt/tests/ui/subcommand_opt_vec.rs new file mode 100644 index 0000000..17bffbf --- /dev/null +++ b/structopt/tests/ui/subcommand_opt_vec.rs @@ -0,0 +1,36 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(subcommand)] + cmd: Option<Vec<Command>>, +} + +#[derive(StructOpt, Debug)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +fn main() { + let opt = MakeCookie::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/subcommand_opt_vec.stderr b/structopt/tests/ui/subcommand_opt_vec.stderr new file mode 100644 index 0000000..a9833a6 --- /dev/null +++ b/structopt/tests/ui/subcommand_opt_vec.stderr @@ -0,0 +1,5 @@ +error: Option<Vec<T>> type is not allowed for subcommand + --> $DIR/subcommand_opt_vec.rs:18:10 + | +18 | cmd: Option<Vec<Command>>, + | ^^^^^^ diff --git a/structopt/tests/ui/tuple_struct.rs b/structopt/tests/ui/tuple_struct.rs new file mode 100644 index 0000000..af9b1d5 --- /dev/null +++ b/structopt/tests/ui/tuple_struct.rs @@ -0,0 +1,18 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt(u32); + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/tuple_struct.stderr b/structopt/tests/ui/tuple_struct.stderr new file mode 100644 index 0000000..9f2876f --- /dev/null +++ b/structopt/tests/ui/tuple_struct.stderr @@ -0,0 +1,5 @@ +error: structopt only supports non-tuple structs and enums + --> $DIR/tuple_struct.rs:11:10 + | +11 | #[derive(StructOpt, Debug)] + | ^^^^^^^^^ diff --git a/structopt/tests/utils.rs b/structopt/tests/utils.rs new file mode 100644 index 0000000..c0684a2 --- /dev/null +++ b/structopt/tests/utils.rs @@ -0,0 +1,45 @@ +#![allow(unused)] + +use structopt::StructOpt; + +pub fn get_help<T: StructOpt>() -> String { + let mut output = Vec::new(); + <T as StructOpt>::clap().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<T: StructOpt>() -> String { + let mut output = Vec::new(); + <T as StructOpt>::clap() + .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<T: StructOpt>(subcmd: &str) -> String { + let output = <T as StructOpt>::clap() + .get_matches_from_safe(vec!["test", subcmd, "--help"]) + .expect_err("") + .message; + + eprintln!( + "\n%%% SUBCOMMAND `{}` HELP %%%:=====\n{}\n=====\n", + subcmd, output + ); + eprintln!( + "\n%%% SUBCOMMAND `{}` HELP (DEBUG) %%%:=====\n{:?}\n=====\n", + subcmd, output + ); + + output +} |