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