aboutsummaryrefslogtreecommitdiff
path: root/structopt/tests
diff options
context:
space:
mode:
Diffstat (limited to 'structopt/tests')
-rw-r--r--structopt/tests/argument_naming.rs311
-rw-r--r--structopt/tests/arguments.rs86
-rw-r--r--structopt/tests/author_version_about.rs46
-rw-r--r--structopt/tests/custom-string-parsers.rs306
-rw-r--r--structopt/tests/deny-warnings.rs47
-rw-r--r--structopt/tests/doc-comments-help.rs162
-rw-r--r--structopt/tests/explicit_name_no_renaming.rs32
-rw-r--r--structopt/tests/flags.rs162
-rw-r--r--structopt/tests/flatten.rs95
-rw-r--r--structopt/tests/issues.rs67
-rw-r--r--structopt/tests/macro-errors.rs13
-rw-r--r--structopt/tests/nested-subcommands.rs193
-rw-r--r--structopt/tests/non_literal_attributes.rs147
-rw-r--r--structopt/tests/options.rs336
-rw-r--r--structopt/tests/privacy.rs32
-rw-r--r--structopt/tests/raw_bool_literal.rs29
-rw-r--r--structopt/tests/raw_idents.rs17
-rw-r--r--structopt/tests/rename_all_env.rs46
-rw-r--r--structopt/tests/skip.rs148
-rw-r--r--structopt/tests/special_types.rs73
-rw-r--r--structopt/tests/subcommands.rs213
-rw-r--r--structopt/tests/ui/bool_default_value.rs21
-rw-r--r--structopt/tests/ui/bool_default_value.stderr5
-rw-r--r--structopt/tests/ui/bool_required.rs21
-rw-r--r--structopt/tests/ui/bool_required.stderr5
-rw-r--r--structopt/tests/ui/flatten_and_doc.rs30
-rw-r--r--structopt/tests/ui/flatten_and_doc.stderr5
-rw-r--r--structopt/tests/ui/flatten_and_methods.rs29
-rw-r--r--structopt/tests/ui/flatten_and_methods.stderr5
-rw-r--r--structopt/tests/ui/flatten_and_parse.rs29
-rw-r--r--structopt/tests/ui/flatten_and_parse.stderr5
-rw-r--r--structopt/tests/ui/non_existent_attr.rs21
-rw-r--r--structopt/tests/ui/non_existent_attr.stderr5
-rw-r--r--structopt/tests/ui/opt_opt_nonpositional.rs20
-rw-r--r--structopt/tests/ui/opt_opt_nonpositional.stderr5
-rw-r--r--structopt/tests/ui/opt_vec_nonpositional.rs20
-rw-r--r--structopt/tests/ui/opt_vec_nonpositional.stderr5
-rw-r--r--structopt/tests/ui/option_default_value.rs21
-rw-r--r--structopt/tests/ui/option_default_value.stderr5
-rw-r--r--structopt/tests/ui/option_required.rs21
-rw-r--r--structopt/tests/ui/option_required.stderr5
-rw-r--r--structopt/tests/ui/parse_empty_try_from_os.rs21
-rw-r--r--structopt/tests/ui/parse_empty_try_from_os.stderr5
-rw-r--r--structopt/tests/ui/parse_function_is_not_path.rs21
-rw-r--r--structopt/tests/ui/parse_function_is_not_path.stderr5
-rw-r--r--structopt/tests/ui/parse_literal_spec.rs21
-rw-r--r--structopt/tests/ui/parse_literal_spec.stderr5
-rw-r--r--structopt/tests/ui/parse_not_zero_args.rs21
-rw-r--r--structopt/tests/ui/parse_not_zero_args.stderr5
-rw-r--r--structopt/tests/ui/positional_bool.rs10
-rw-r--r--structopt/tests/ui/positional_bool.stderr10
-rw-r--r--structopt/tests/ui/raw.rs25
-rw-r--r--structopt/tests/ui/raw.stderr19
-rw-r--r--structopt/tests/ui/rename_all_wrong_casing.rs21
-rw-r--r--structopt/tests/ui/rename_all_wrong_casing.stderr5
-rw-r--r--structopt/tests/ui/skip_flatten.rs42
-rw-r--r--structopt/tests/ui/skip_flatten.stderr5
-rw-r--r--structopt/tests/ui/skip_subcommand.rs42
-rw-r--r--structopt/tests/ui/skip_subcommand.stderr5
-rw-r--r--structopt/tests/ui/skip_with_other_options.rs15
-rw-r--r--structopt/tests/ui/skip_with_other_options.stderr5
-rw-r--r--structopt/tests/ui/skip_without_default.rs29
-rw-r--r--structopt/tests/ui/skip_without_default.stderr9
-rw-r--r--structopt/tests/ui/struct_flatten.rs21
-rw-r--r--structopt/tests/ui/struct_flatten.stderr5
-rw-r--r--structopt/tests/ui/struct_parse.rs21
-rw-r--r--structopt/tests/ui/struct_parse.stderr5
-rw-r--r--structopt/tests/ui/struct_subcommand.rs21
-rw-r--r--structopt/tests/ui/struct_subcommand.stderr5
-rw-r--r--structopt/tests/ui/structopt_empty_attr.rs22
-rw-r--r--structopt/tests/ui/structopt_empty_attr.stderr5
-rw-r--r--structopt/tests/ui/structopt_name_value_attr.rs22
-rw-r--r--structopt/tests/ui/structopt_name_value_attr.stderr5
-rw-r--r--structopt/tests/ui/subcommand_and_flatten.rs36
-rw-r--r--structopt/tests/ui/subcommand_and_flatten.stderr5
-rw-r--r--structopt/tests/ui/subcommand_and_methods.rs36
-rw-r--r--structopt/tests/ui/subcommand_and_methods.stderr5
-rw-r--r--structopt/tests/ui/subcommand_and_parse.rs36
-rw-r--r--structopt/tests/ui/subcommand_and_parse.stderr5
-rw-r--r--structopt/tests/ui/subcommand_opt_opt.rs36
-rw-r--r--structopt/tests/ui/subcommand_opt_opt.stderr5
-rw-r--r--structopt/tests/ui/subcommand_opt_vec.rs36
-rw-r--r--structopt/tests/ui/subcommand_opt_vec.stderr5
-rw-r--r--structopt/tests/ui/tuple_struct.rs18
-rw-r--r--structopt/tests/ui/tuple_struct.stderr5
-rw-r--r--structopt/tests/utils.rs45
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
+}