// Copyright (c) 2020 Google LLC All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Deny a bunch of uncommon clippy lints to make sure the generated code won't trigger a warning.
#![deny(
    clippy::indexing_slicing,
    clippy::panic_in_result_fn,
    clippy::str_to_string,
    clippy::unreachable,
    clippy::unwrap_in_result
)]

use {argh::FromArgs, std::fmt::Debug};

#[test]
fn basic_example() {
    #[derive(FromArgs, PartialEq, Debug)]
    /// Reach new heights.
    struct GoUp {
        /// whether or not to jump
        #[argh(switch, short = 'j')]
        jump: bool,

        /// how high to go
        #[argh(option)]
        height: usize,

        /// an optional nickname for the pilot
        #[argh(option)]
        pilot_nickname: Option<String>,
    }

    let up = GoUp::from_args(&["cmdname"], &["--height", "5"]).expect("failed go_up");
    assert_eq!(up, GoUp { jump: false, height: 5, pilot_nickname: None });
}

#[test]
fn generic_example() {
    use std::fmt::Display;
    use std::str::FromStr;

    #[derive(FromArgs, PartialEq, Debug)]
    /// Reach new heights.
    struct GoUp<S: FromStr>
    where
        <S as FromStr>::Err: Display,
    {
        /// whether or not to jump
        #[argh(switch, short = 'j')]
        jump: bool,

        /// how high to go
        #[argh(option)]
        height: usize,

        /// an optional nickname for the pilot
        #[argh(option)]
        pilot_nickname: Option<S>,
    }

    let up = GoUp::<String>::from_args(&["cmdname"], &["--height", "5"]).expect("failed go_up");
    assert_eq!(up, GoUp::<String> { jump: false, height: 5, pilot_nickname: None });
}

#[test]
fn custom_from_str_example() {
    #[derive(FromArgs)]
    /// Goofy thing.
    struct FiveStruct {
        /// always five
        #[argh(option, from_str_fn(always_five))]
        five: usize,
    }

    fn always_five(_value: &str) -> Result<usize, String> {
        Ok(5)
    }

    let f = FiveStruct::from_args(&["cmdname"], &["--five", "woot"]).expect("failed to five");
    assert_eq!(f.five, 5);
}

#[test]
fn nested_from_str_example() {
    #[derive(FromArgs)]
    /// Goofy thing.
    struct FiveStruct {
        /// always five
        #[argh(option, from_str_fn(nested::always_five))]
        five: usize,
    }

    pub mod nested {
        pub fn always_five(_value: &str) -> Result<usize, String> {
            Ok(5)
        }
    }

    let f = FiveStruct::from_args(&["cmdname"], &["--five", "woot"]).expect("failed to five");
    assert_eq!(f.five, 5);
}

#[test]
fn method_from_str_example() {
    #[derive(FromArgs)]
    /// Goofy thing.
    struct FiveStruct {
        /// always five
        #[argh(option, from_str_fn(AlwaysFive::<usize>::always_five))]
        five: usize,
    }

    struct AlwaysFive<T>(T);

    impl AlwaysFive<usize> {
        fn always_five(_value: &str) -> Result<usize, String> {
            Ok(5)
        }
    }

    let f = FiveStruct::from_args(&["cmdname"], &["--five", "woot"]).expect("failed to five");
    assert_eq!(f.five, 5);
}

#[test]
fn subcommand_example() {
    #[derive(FromArgs, PartialEq, Debug)]
    /// Top-level command.
    struct TopLevel {
        #[argh(subcommand)]
        nested: MySubCommandEnum,
    }

    #[derive(FromArgs, PartialEq, Debug)]
    #[argh(subcommand)]
    enum MySubCommandEnum {
        One(SubCommandOne),
        Two(SubCommandTwo),
    }

    #[derive(FromArgs, PartialEq, Debug)]
    /// First subcommand.
    #[argh(subcommand, name = "one")]
    struct SubCommandOne {
        #[argh(option)]
        /// how many x
        x: usize,
    }

    #[derive(FromArgs, PartialEq, Debug)]
    /// Second subcommand.
    #[argh(subcommand, name = "two")]
    struct SubCommandTwo {
        #[argh(switch)]
        /// whether to fooey
        fooey: bool,
    }

    let one = TopLevel::from_args(&["cmdname"], &["one", "--x", "2"]).expect("sc 1");
    assert_eq!(one, TopLevel { nested: MySubCommandEnum::One(SubCommandOne { x: 2 }) },);

    let two = TopLevel::from_args(&["cmdname"], &["two", "--fooey"]).expect("sc 2");
    assert_eq!(two, TopLevel { nested: MySubCommandEnum::Two(SubCommandTwo { fooey: true }) },);
}

#[test]
fn dynamic_subcommand_example() {
    #[derive(PartialEq, Debug)]
    struct DynamicSubCommandImpl {
        got: String,
    }

    impl argh::DynamicSubCommand for DynamicSubCommandImpl {
        fn commands() -> &'static [&'static argh::CommandInfo] {
            &[
                &argh::CommandInfo { name: "three", description: "Third command" },
                &argh::CommandInfo { name: "four", description: "Fourth command" },
                &argh::CommandInfo { name: "five", description: "Fifth command" },
            ]
        }

        fn try_redact_arg_values(
            _command_name: &[&str],
            _args: &[&str],
        ) -> Option<Result<Vec<String>, argh::EarlyExit>> {
            Some(Err(argh::EarlyExit::from("Test should not redact".to_owned())))
        }

        fn try_from_args(
            command_name: &[&str],
            args: &[&str],
        ) -> Option<Result<DynamicSubCommandImpl, argh::EarlyExit>> {
            let command_name = match command_name.last() {
                Some(x) => *x,
                None => return Some(Err(argh::EarlyExit::from("No command".to_owned()))),
            };
            let description = Self::commands().iter().find(|x| x.name == command_name)?.description;
            if args.len() > 1 {
                Some(Err(argh::EarlyExit::from("Too many arguments".to_owned())))
            } else if let Some(arg) = args.first() {
                Some(Ok(DynamicSubCommandImpl { got: format!("{} got {:?}", description, arg) }))
            } else {
                Some(Err(argh::EarlyExit::from("Not enough arguments".to_owned())))
            }
        }
    }

    #[derive(FromArgs, PartialEq, Debug)]
    /// Top-level command.
    struct TopLevel {
        #[argh(subcommand)]
        nested: MySubCommandEnum,
    }

    #[derive(FromArgs, PartialEq, Debug)]
    #[argh(subcommand)]
    enum MySubCommandEnum {
        One(SubCommandOne),
        Two(SubCommandTwo),
        #[argh(dynamic)]
        ThreeFourFive(DynamicSubCommandImpl),
    }

    #[derive(FromArgs, PartialEq, Debug)]
    /// First subcommand.
    #[argh(subcommand, name = "one")]
    struct SubCommandOne {
        #[argh(option)]
        /// how many x
        x: usize,
    }

    #[derive(FromArgs, PartialEq, Debug)]
    /// Second subcommand.
    #[argh(subcommand, name = "two")]
    struct SubCommandTwo {
        #[argh(switch)]
        /// whether to fooey
        fooey: bool,
    }

    let one = TopLevel::from_args(&["cmdname"], &["one", "--x", "2"]).expect("sc 1");
    assert_eq!(one, TopLevel { nested: MySubCommandEnum::One(SubCommandOne { x: 2 }) },);

    let two = TopLevel::from_args(&["cmdname"], &["two", "--fooey"]).expect("sc 2");
    assert_eq!(two, TopLevel { nested: MySubCommandEnum::Two(SubCommandTwo { fooey: true }) },);

    let three = TopLevel::from_args(&["cmdname"], &["three", "beans"]).expect("sc 3");
    assert_eq!(
        three,
        TopLevel {
            nested: MySubCommandEnum::ThreeFourFive(DynamicSubCommandImpl {
                got: "Third command got \"beans\"".to_owned()
            })
        },
    );

    let four = TopLevel::from_args(&["cmdname"], &["four", "boulders"]).expect("sc 4");
    assert_eq!(
        four,
        TopLevel {
            nested: MySubCommandEnum::ThreeFourFive(DynamicSubCommandImpl {
                got: "Fourth command got \"boulders\"".to_owned()
            })
        },
    );

    let five = TopLevel::from_args(&["cmdname"], &["five", "gold rings"]).expect("sc 5");
    assert_eq!(
        five,
        TopLevel {
            nested: MySubCommandEnum::ThreeFourFive(DynamicSubCommandImpl {
                got: "Fifth command got \"gold rings\"".to_owned()
            })
        },
    );
}

#[test]
fn multiline_doc_comment_description() {
    #[derive(FromArgs)]
    /// Short description
    struct Cmd {
        #[argh(switch)]
        /// a switch with a description
        /// that is spread across
        /// a number of
        /// lines of comments.
        _s: bool,
    }

    assert_help_string::<Cmd>(
        r###"Usage: test_arg_0 [--s]

Short description

Options:
  --s               a switch with a description that is spread across a number
                    of lines of comments.
  --help            display usage information
"###,
    );
}

#[test]
fn explicit_long_value_for_option() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(option, long = "foo")]
        /// bar bar
        x: u8,
    }

    let cmd = Cmd::from_args(&["cmdname"], &["--foo", "5"]).unwrap();
    assert_eq!(cmd.x, 5);
}

/// Test that descriptions can start with an initialism despite
/// usually being required to start with a lowercase letter.
#[derive(FromArgs)]
#[allow(unused)]
struct DescriptionStartsWithInitialism {
    /// URL fooey
    #[argh(option)]
    x: u8,
}

#[test]
fn default_number() {
    #[derive(FromArgs)]
    /// Short description
    struct Cmd {
        #[argh(option, default = "5")]
        /// fooey
        x: u8,
    }

    let cmd = Cmd::from_args(&["cmdname"], &[]).unwrap();
    assert_eq!(cmd.x, 5);
}

#[test]
fn default_function() {
    const MSG: &str = "hey I just met you";
    fn call_me_maybe() -> String {
        MSG.to_owned()
    }

    #[derive(FromArgs)]
    /// Short description
    struct Cmd {
        #[argh(option, default = "call_me_maybe()")]
        /// fooey
        msg: String,
    }

    let cmd = Cmd::from_args(&["cmdname"], &[]).unwrap();
    assert_eq!(cmd.msg, MSG);
}

#[test]
fn missing_option_value() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(option)]
        /// fooey
        _msg: String,
    }

    let e = Cmd::from_args(&["cmdname"], &["--msg"])
        .expect_err("Parsing missing option value should fail");
    assert_eq!(e.output, "No value provided for option \'--msg\'.\n");
    assert!(e.status.is_err());
}

fn assert_help_string<T: FromArgs>(help_str: &str) {
    match T::from_args(&["test_arg_0"], &["--help"]) {
        Ok(_) => panic!("help was parsed as args"),
        Err(e) => {
            assert_eq!(help_str, e.output);
            e.status.expect("help returned an error");
        }
    }
}

fn assert_output<T: FromArgs + Debug + PartialEq>(args: &[&str], expected: T) {
    let t = T::from_args(&["cmd"], args).expect("failed to parse");
    assert_eq!(t, expected);
}

fn assert_error<T: FromArgs + Debug>(args: &[&str], err_msg: &str) {
    let e = T::from_args(&["cmd"], args).expect_err("unexpectedly succeeded parsing");
    assert_eq!(err_msg, e.output);
    e.status.expect_err("error had a positive status");
}

mod options {
    use super::*;

    #[derive(argh::FromArgs, Debug, PartialEq)]
    /// Woot
    struct Parsed {
        #[argh(option, short = 'n')]
        /// fooey
        n: usize,
    }

    #[test]
    fn parsed() {
        assert_output(&["-n", "5"], Parsed { n: 5 });
        assert_error::<Parsed>(
            &["-n", "x"],
            r###"Error parsing option '-n' with value 'x': invalid digit found in string
"###,
        );
    }

    #[derive(argh::FromArgs, Debug, PartialEq)]
    /// Woot
    struct Repeating {
        #[argh(option, short = 'n')]
        /// fooey
        n: Vec<String>,
    }

    #[test]
    fn repeating() {
        assert_help_string::<Repeating>(
            r###"Usage: test_arg_0 [-n <n...>]

Woot

Options:
  -n, --n           fooey
  --help            display usage information
"###,
        );
    }

    #[derive(argh::FromArgs, Debug, PartialEq)]
    /// Woot
    struct WithArgName {
        #[argh(option, arg_name = "name")]
        /// fooey
        option_name: Option<String>,
    }

    #[test]
    fn with_arg_name() {
        assert_help_string::<WithArgName>(
            r###"Usage: test_arg_0 [--option-name <name>]

Woot

Options:
  --option-name     fooey
  --help            display usage information
"###,
        );
    }
}

mod positional {
    use super::*;

    #[derive(FromArgs, Debug, PartialEq)]
    /// Woot
    struct LastRepeating {
        #[argh(positional)]
        /// fooey
        a: u32,
        #[argh(positional)]
        /// fooey
        b: Vec<String>,
    }

    #[test]
    fn repeating() {
        assert_output(&["5"], LastRepeating { a: 5, b: vec![] });
        assert_output(&["5", "foo"], LastRepeating { a: 5, b: vec!["foo".into()] });
        assert_output(
            &["5", "foo", "bar"],
            LastRepeating { a: 5, b: vec!["foo".into(), "bar".into()] },
        );
        assert_help_string::<LastRepeating>(
            r###"Usage: test_arg_0 <a> [<b...>]

Woot

Positional Arguments:
  a                 fooey
  b                 fooey

Options:
  --help            display usage information
"###,
        );
    }

    #[derive(FromArgs, Debug, PartialEq)]
    /// Woot
    struct LastRepeatingGreedy {
        #[argh(positional)]
        /// fooey
        a: u32,
        #[argh(switch)]
        /// woo
        b: bool,
        #[argh(option)]
        /// stuff
        c: Option<String>,
        #[argh(positional, greedy)]
        /// fooey
        d: Vec<String>,
    }

    #[test]
    fn positional_greedy() {
        assert_output(&["5"], LastRepeatingGreedy { a: 5, b: false, c: None, d: vec![] });
        assert_output(
            &["5", "foo"],
            LastRepeatingGreedy { a: 5, b: false, c: None, d: vec!["foo".into()] },
        );
        assert_output(
            &["5", "foo", "bar"],
            LastRepeatingGreedy { a: 5, b: false, c: None, d: vec!["foo".into(), "bar".into()] },
        );
        assert_output(
            &["5", "--b", "foo", "bar"],
            LastRepeatingGreedy { a: 5, b: true, c: None, d: vec!["foo".into(), "bar".into()] },
        );
        assert_output(
            &["5", "foo", "bar", "--b"],
            LastRepeatingGreedy {
                a: 5,
                b: false,
                c: None,
                d: vec!["foo".into(), "bar".into(), "--b".into()],
            },
        );
        assert_output(
            &["5", "--c", "hi", "foo", "bar"],
            LastRepeatingGreedy {
                a: 5,
                b: false,
                c: Some("hi".into()),
                d: vec!["foo".into(), "bar".into()],
            },
        );
        assert_output(
            &["5", "foo", "bar", "--c", "hi"],
            LastRepeatingGreedy {
                a: 5,
                b: false,
                c: None,
                d: vec!["foo".into(), "bar".into(), "--c".into(), "hi".into()],
            },
        );
        assert_output(
            &["5", "foo", "bar", "--", "hi"],
            LastRepeatingGreedy {
                a: 5,
                b: false,
                c: None,
                d: vec!["foo".into(), "bar".into(), "--".into(), "hi".into()],
            },
        );
        assert_help_string::<LastRepeatingGreedy>(
            r###"Usage: test_arg_0 <a> [--b] [--c <c>] [d...]

Woot

Positional Arguments:
  a                 fooey

Options:
  --b               woo
  --c               stuff
  --help            display usage information
"###,
        );
    }

    #[derive(FromArgs, Debug, PartialEq)]
    /// Woot
    struct LastOptional {
        #[argh(positional)]
        /// fooey
        a: u32,
        #[argh(positional)]
        /// fooey
        b: Option<String>,
    }

    #[test]
    fn optional() {
        assert_output(&["5"], LastOptional { a: 5, b: None });
        assert_output(&["5", "6"], LastOptional { a: 5, b: Some("6".into()) });
        assert_error::<LastOptional>(&["5", "6", "7"], "Unrecognized argument: 7\n");
    }

    #[derive(FromArgs, Debug, PartialEq)]
    /// Woot
    struct LastDefaulted {
        #[argh(positional)]
        /// fooey
        a: u32,
        #[argh(positional, default = "5")]
        /// fooey
        b: u32,
    }

    #[test]
    fn defaulted() {
        assert_output(&["5"], LastDefaulted { a: 5, b: 5 });
        assert_output(&["5", "6"], LastDefaulted { a: 5, b: 6 });
        assert_error::<LastDefaulted>(&["5", "6", "7"], "Unrecognized argument: 7\n");
    }

    #[derive(FromArgs, Debug, PartialEq)]
    /// Woot
    struct LastRequired {
        #[argh(positional)]
        /// fooey
        a: u32,
        #[argh(positional)]
        /// fooey
        b: u32,
    }

    #[test]
    fn required() {
        assert_output(&["5", "6"], LastRequired { a: 5, b: 6 });
        assert_error::<LastRequired>(
            &[],
            r###"Required positional arguments not provided:
    a
    b
"###,
        );
        assert_error::<LastRequired>(
            &["5"],
            r###"Required positional arguments not provided:
    b
"###,
        );
    }

    #[derive(argh::FromArgs, Debug, PartialEq)]
    /// Woot
    struct Parsed {
        #[argh(positional)]
        /// fooey
        n: usize,
    }

    #[test]
    fn parsed() {
        assert_output(&["5"], Parsed { n: 5 });
        assert_error::<Parsed>(
            &["x"],
            r###"Error parsing positional argument 'n' with value 'x': invalid digit found in string
"###,
        );
    }

    #[derive(FromArgs, Debug, PartialEq)]
    /// Woot
    struct WithOption {
        #[argh(positional)]
        /// fooey
        a: String,
        #[argh(option)]
        /// fooey
        b: String,
    }

    #[test]
    fn mixed_with_option() {
        assert_output(&["first", "--b", "foo"], WithOption { a: "first".into(), b: "foo".into() });

        assert_error::<WithOption>(
            &[],
            r###"Required positional arguments not provided:
    a
Required options not provided:
    --b
"###,
        );
    }

    #[derive(FromArgs, Debug, PartialEq)]
    /// Woot
    struct WithSubcommand {
        #[argh(positional)]
        /// fooey
        a: String,
        #[argh(subcommand)]
        /// fooey
        b: Subcommand,
        #[argh(positional)]
        /// fooey
        c: Vec<String>,
    }

    #[derive(FromArgs, Debug, PartialEq)]
    #[argh(subcommand, name = "a")]
    /// Subcommand of positional::WithSubcommand.
    struct Subcommand {
        #[argh(positional)]
        /// fooey
        a: String,
        #[argh(positional)]
        /// fooey
        b: Vec<String>,
    }

    #[test]
    fn mixed_with_subcommand() {
        assert_output(
            &["first", "a", "a"],
            WithSubcommand {
                a: "first".into(),
                b: Subcommand { a: "a".into(), b: vec![] },
                c: vec![],
            },
        );

        assert_error::<WithSubcommand>(
            &["a", "a", "a"],
            r###"Required positional arguments not provided:
    a
"###,
        );

        assert_output(
            &["1", "2", "3", "a", "b", "c"],
            WithSubcommand {
                a: "1".into(),
                b: Subcommand { a: "b".into(), b: vec!["c".into()] },
                c: vec!["2".into(), "3".into()],
            },
        );
    }

    #[derive(FromArgs, Debug, PartialEq)]
    /// Woot
    struct Underscores {
        #[argh(positional)]
        /// fooey
        a_: String,
    }

    #[test]
    fn positional_name_with_underscores() {
        assert_output(&["first"], Underscores { a_: "first".into() });

        assert_error::<Underscores>(
            &[],
            r###"Required positional arguments not provided:
    a
"###,
        );
    }
}

/// Tests derived from
/// https://fuchsia.dev/fuchsia-src/development/api/cli and
/// https://fuchsia.dev/fuchsia-src/development/api/cli_help
mod fuchsia_commandline_tools_rubric {
    use super::*;

    /// Tests for the three required command line argument types:
    /// - exact text
    /// - arguments
    /// - options (i.e. switches and keys)
    #[test]
    fn three_command_line_argument_types() {
        // TODO(cramertj) add support for exact text and positional arguments
    }

    /// A piece of exact text may be required or optional
    #[test]
    fn exact_text_required_and_optional() {
        // TODO(cramertj) add support for exact text
    }

    /// Arguments are like function parameters or slots for data.
    /// The order often matters.
    #[test]
    fn arguments_ordered() {
        // TODO(cramertj) add support for ordered positional arguments
    }

    /// If a single argument is repeated, order may not matter, e.g. `<files>...`
    #[test]
    fn arguments_unordered() {
        // TODO(cramertj) add support for repeated positional arguments
    }

    // Short argument names must use one dash and a single letter.
    // TODO(cramertj): this should be a compile-fail test

    // Short argument names are optional, but all choices are required to have a `--` option.
    // TODO(cramertj): this should be a compile-fail test

    // Numeric options, such as `-1` and `-2`, are not allowed.
    // TODO(cramertj): this should be a compile-fail test

    #[derive(FromArgs)]
    /// One switch.
    struct OneSwitch {
        #[argh(switch, short = 's')]
        /// just a switch
        switchy: bool,
    }

    /// The presence of a switch means the feature it represents is "on",
    /// while its absence means that it is "off".
    #[test]
    fn switch_on_when_present() {
        let on = OneSwitch::from_args(&["cmdname"], &["-s"]).expect("parsing on");
        assert!(on.switchy);

        let off = OneSwitch::from_args(&["cmdname"], &[]).expect("parsing off");
        assert!(!off.switchy);
    }

    #[derive(FromArgs, Debug)]
    /// Two Switches
    struct TwoSwitches {
        #[argh(switch, short = 'a')]
        /// a
        _a: bool,
        #[argh(switch, short = 'b')]
        /// b
        _b: bool,
    }

    /// Running switches together is not allowed
    #[test]
    fn switches_cannot_run_together() {
        TwoSwitches::from_args(&["cmdname"], &["-a", "-b"])
            .expect("parsing separate should succeed");
        TwoSwitches::from_args(&["cmdname"], &["-ab"]).expect_err("parsing together should fail");
    }

    #[derive(FromArgs, Debug)]
    /// One keyed option
    struct OneOption {
        #[argh(option)]
        /// some description
        _foo: String,
    }

    /// Do not use an equals punctuation or similar to separate the key and value.
    #[test]
    fn keyed_no_equals() {
        OneOption::from_args(&["cmdname"], &["--foo", "bar"])
            .expect("Parsing option value as separate arg should succeed");

        let e = OneOption::from_args(&["cmdname"], &["--foo=bar"])
            .expect_err("Parsing option value using `=` should fail");
        assert_eq!(e.output, "Unrecognized argument: --foo=bar\n");
        assert!(e.status.is_err());
    }

    // Two dashes on their own indicates the end of options.
    // Subsequent values are given to the tool as-is.
    //
    // It's unclear exactly what "are given to the tool as-is" in means in this
    // context, so we provide a few options for handling `--`, with it being
    // an error by default.
    //
    // TODO(cramertj) implement some behavior for `--`

    /// Double-dash is treated as an error by default.
    #[test]
    fn double_dash_default_error() {}

    /// Double-dash can be ignored for later manual parsing.
    #[test]
    fn double_dash_ignore() {}

    /// Double-dash should be treated as the end of flags and optional arguments,
    /// and the remainder of the values should be treated purely as positional arguments,
    /// even when their syntax matches that of options. e.g. `foo -- -e` should be parsed
    /// as passing a single positional argument with the value `-e`.
    #[test]
    fn double_dash_positional() {
        #[derive(FromArgs, Debug, PartialEq)]
        /// Positional arguments list
        struct StringList {
            #[argh(positional)]
            /// a list of strings
            strs: Vec<String>,

            #[argh(switch)]
            /// some flag
            flag: bool,
        }

        assert_output(
            &["--", "a", "-b", "--flag"],
            StringList { strs: vec!["a".into(), "-b".into(), "--flag".into()], flag: false },
        );
        assert_output(
            &["--flag", "--", "-a", "b"],
            StringList { strs: vec!["-a".into(), "b".into()], flag: true },
        );
        assert_output(&["--", "--help"], StringList { strs: vec!["--help".into()], flag: false });
        assert_output(
            &["--", "-a", "--help"],
            StringList { strs: vec!["-a".into(), "--help".into()], flag: false },
        );
    }

    /// Double-dash can be parsed into an optional field using a provided
    /// `fn(&[&str]) -> Result<T, EarlyExit>`.
    #[test]
    fn double_dash_custom() {}

    /// Repeating switches may be used to apply more emphasis.
    /// A common example is increasing verbosity by passing more `-v` switches.
    #[test]
    fn switches_repeating() {
        #[derive(FromArgs, Debug)]
        /// A type for testing repeating `-v`
        struct CountVerbose {
            #[argh(switch, short = 'v')]
            /// increase the verbosity of the command.
            verbose: i128,
        }

        let cv = CountVerbose::from_args(&["cmdname"], &["-v", "-v", "-v"])
            .expect("Parsing verbose flags should succeed");
        assert_eq!(cv.verbose, 3);
    }

    // When a tool has many subcommands, it should also have a help subcommand
    // that displays help about the subcommands, e.g. `fx help build`.
    //
    // Elsewhere in the docs, it says the syntax `--help` is required, so we
    // interpret that to mean:
    //
    // - `help` should always be accepted as a "keyword" in place of the first
    //   positional argument for both the main command and subcommands.
    //
    // - If followed by the name of a subcommand it should forward to the
    //   `--help` of said subcommand, otherwise it will fall back to the
    //   help of the righmost command / subcommand.
    //
    // - `--help` will always consider itself the only meaningful argument to
    //   the rightmost command / subcommand, and any following arguments will
    //   be treated as an error.

    #[derive(FromArgs, Debug)]
    /// A type for testing `--help`/`help`
    struct HelpTopLevel {
        #[argh(subcommand)]
        _sub: HelpFirstSub,
    }

    #[derive(FromArgs, Debug)]
    #[argh(subcommand, name = "first")]
    /// First subcommmand for testing `help`.
    struct HelpFirstSub {
        #[argh(subcommand)]
        _sub: HelpSecondSub,
    }

    #[derive(FromArgs, Debug)]
    #[argh(subcommand, name = "second")]
    /// Second subcommand for testing `help`.
    struct HelpSecondSub {}

    fn expect_help(args: &[&str], expected_help_string: &str) {
        let e = HelpTopLevel::from_args(&["cmdname"], args).expect_err("should exit early");
        assert_eq!(expected_help_string, e.output);
        e.status.expect("help returned an error");
    }

    const MAIN_HELP_STRING: &str = r###"Usage: cmdname <command> [<args>]

A type for testing `--help`/`help`

Options:
  --help            display usage information

Commands:
  first             First subcommmand for testing `help`.
"###;

    const FIRST_HELP_STRING: &str = r###"Usage: cmdname first <command> [<args>]

First subcommmand for testing `help`.

Options:
  --help            display usage information

Commands:
  second            Second subcommand for testing `help`.
"###;

    const SECOND_HELP_STRING: &str = r###"Usage: cmdname first second

Second subcommand for testing `help`.

Options:
  --help            display usage information
"###;

    #[test]
    fn help_keyword_main() {
        expect_help(&["help"], MAIN_HELP_STRING)
    }

    #[test]
    fn help_keyword_with_following_subcommand() {
        expect_help(&["help", "first"], FIRST_HELP_STRING);
    }

    #[test]
    fn help_keyword_between_subcommands() {
        expect_help(&["first", "help", "second"], SECOND_HELP_STRING);
    }

    #[test]
    fn help_keyword_with_two_trailing_subcommands() {
        expect_help(&["help", "first", "second"], SECOND_HELP_STRING);
    }

    #[test]
    fn help_flag_main() {
        expect_help(&["--help"], MAIN_HELP_STRING);
    }

    #[test]
    fn help_flag_subcommand() {
        expect_help(&["first", "--help"], FIRST_HELP_STRING);
    }

    #[test]
    fn help_flag_trailing_arguments_are_an_error() {
        let e = OneOption::from_args(&["cmdname"], &["--help", "--foo", "bar"])
            .expect_err("should exit early");
        assert_eq!("Trailing arguments are not allowed after `help`.", e.output);
        e.status.expect_err("should be an error");
    }

    #[derive(FromArgs, PartialEq, Debug)]
    #[argh(
        description = "Destroy the contents of <file>.",
        example = "Scribble 'abc' and then run |grind|.\n$ {command_name} -s 'abc' grind old.txt taxes.cp",
        note = "Use `{command_name} help <command>` for details on [<args>] for a subcommand.",
        error_code(2, "The blade is too dull."),
        error_code(3, "Out of fuel.")
    )]
    struct HelpExample {
        /// force, ignore minor errors. This description is so long that it wraps to the next line.
        #[argh(switch, short = 'f')]
        force: bool,

        /// documentation
        #[argh(switch)]
        really_really_really_long_name_for_pat: bool,

        /// write <scribble> repeatedly
        #[argh(option, short = 's')]
        scribble: String,

        /// say more. Defaults to $BLAST_VERBOSE.
        #[argh(switch, short = 'v')]
        verbose: bool,

        #[argh(subcommand)]
        command: HelpExampleSubCommands,
    }

    #[derive(FromArgs, PartialEq, Debug)]
    #[argh(subcommand)]
    enum HelpExampleSubCommands {
        BlowUp(BlowUp),
        Grind(GrindCommand),
        #[argh(dynamic)]
        Plugin(HelpExamplePlugin),
    }

    #[derive(FromArgs, PartialEq, Debug)]
    #[argh(subcommand, name = "blow-up")]
    /// explosively separate
    struct BlowUp {
        /// blow up bombs safely
        #[argh(switch)]
        safely: bool,
    }

    #[derive(FromArgs, PartialEq, Debug)]
    #[argh(subcommand, name = "grind", description = "make smaller by many small cuts")]
    struct GrindCommand {
        /// wear a visor while grinding
        #[argh(switch)]
        safely: bool,
    }

    #[derive(PartialEq, Debug)]
    struct HelpExamplePlugin {
        got: String,
    }

    impl argh::DynamicSubCommand for HelpExamplePlugin {
        fn commands() -> &'static [&'static argh::CommandInfo] {
            &[&argh::CommandInfo { name: "plugin", description: "Example dynamic command" }]
        }

        fn try_redact_arg_values(
            _command_name: &[&str],
            _args: &[&str],
        ) -> Option<Result<Vec<String>, argh::EarlyExit>> {
            Some(Err(argh::EarlyExit::from("Test should not redact".to_owned())))
        }

        fn try_from_args(
            command_name: &[&str],
            args: &[&str],
        ) -> Option<Result<HelpExamplePlugin, argh::EarlyExit>> {
            if command_name.last() != Some(&"plugin") {
                None
            } else if args.len() > 1 {
                Some(Err(argh::EarlyExit::from("Too many arguments".to_owned())))
            } else if let Some(arg) = args.first() {
                Some(Ok(HelpExamplePlugin { got: format!("plugin got {:?}", arg) }))
            } else {
                Some(Ok(HelpExamplePlugin { got: "plugin got no argument".to_owned() }))
            }
        }
    }

    #[test]
    fn example_parses_correctly() {
        let help_example = HelpExample::from_args(
            &["program-name"],
            &["-f", "--scribble", "fooey", "blow-up", "--safely"],
        )
        .unwrap();

        assert_eq!(
            help_example,
            HelpExample {
                force: true,
                scribble: "fooey".to_owned(),
                really_really_really_long_name_for_pat: false,
                verbose: false,
                command: HelpExampleSubCommands::BlowUp(BlowUp { safely: true }),
            },
        );
    }

    #[test]
    fn example_errors_on_missing_required_option_and_missing_required_subcommand() {
        let exit = HelpExample::from_args(&["program-name"], &[]).unwrap_err();
        exit.status.unwrap_err();
        assert_eq!(
            exit.output,
            concat!(
                "Required options not provided:\n",
                "    --scribble\n",
                "One of the following subcommands must be present:\n",
                "    help\n",
                "    blow-up\n",
                "    grind\n",
                "    plugin\n",
            ),
        );
    }

    #[test]
    fn help_example() {
        assert_help_string::<HelpExample>(
            r###"Usage: test_arg_0 [-f] [--really-really-really-long-name-for-pat] -s <scribble> [-v] <command> [<args>]

Destroy the contents of <file>.

Options:
  -f, --force       force, ignore minor errors. This description is so long that
                    it wraps to the next line.
  --really-really-really-long-name-for-pat
                    documentation
  -s, --scribble    write <scribble> repeatedly
  -v, --verbose     say more. Defaults to $BLAST_VERBOSE.
  --help            display usage information

Commands:
  blow-up           explosively separate
  grind             make smaller by many small cuts
  plugin            Example dynamic command

Examples:
  Scribble 'abc' and then run |grind|.
  $ test_arg_0 -s 'abc' grind old.txt taxes.cp

Notes:
  Use `test_arg_0 help <command>` for details on [<args>] for a subcommand.

Error codes:
  2 The blade is too dull.
  3 Out of fuel.
"###,
        );
    }

    #[allow(dead_code)]
    #[derive(argh::FromArgs)]
    /// Destroy the contents of <file>.
    struct WithArgName {
        #[argh(positional, arg_name = "name")]
        username: String,
    }

    #[test]
    fn with_arg_name() {
        assert_help_string::<WithArgName>(
            r###"Usage: test_arg_0 <name>

Destroy the contents of <file>.

Positional Arguments:
  name

Options:
  --help            display usage information
"###,
        );
    }

    #[test]
    fn hidden_help_attribute() {
        #[derive(FromArgs)]
        /// Short description
        struct Cmd {
            /// this one should be hidden
            #[argh(positional, hidden_help)]
            _one: String,
            #[argh(positional)]
            /// this one is real
            _two: String,
            /// this one should be hidden
            #[argh(option, hidden_help)]
            _three: String,
        }

        assert_help_string::<Cmd>(
            r###"Usage: test_arg_0 <two>

Short description

Positional Arguments:
  two               this one is real

Options:
  --help            display usage information
"###,
        );
    }
}

#[test]
fn redact_arg_values_no_args() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(option)]
        /// a msg param
        _msg: Option<String>,
    }

    let actual = Cmd::redact_arg_values(&["program-name"], &[]).unwrap();
    assert_eq!(actual, &["program-name"]);
}

#[test]
fn redact_arg_values_optional_arg() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(option)]
        /// a msg param
        _msg: Option<String>,
    }

    let actual = Cmd::redact_arg_values(&["program-name"], &["--msg", "hello"]).unwrap();
    assert_eq!(actual, &["program-name", "--msg"]);
}

#[test]
fn redact_arg_values_optional_arg_short() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(option, short = 'm')]
        /// a msg param
        _msg: Option<String>,
    }

    let actual = Cmd::redact_arg_values(&["program-name"], &["-m", "hello"]).unwrap();
    assert_eq!(actual, &["program-name", "-m"]);
}

#[test]
fn redact_arg_values_optional_arg_long() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(option, long = "my-msg")]
        /// a msg param
        _msg: Option<String>,
    }

    let actual = Cmd::redact_arg_values(&["program-name"], &["--my-msg", "hello"]).unwrap();
    assert_eq!(actual, &["program-name", "--my-msg"]);
}

#[test]
fn redact_arg_values_two_option_args() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(option)]
        /// a msg param
        _msg: String,

        #[argh(option)]
        /// a delivery param
        _delivery: String,
    }

    let actual =
        Cmd::redact_arg_values(&["program-name"], &["--msg", "hello", "--delivery", "next day"])
            .unwrap();
    assert_eq!(actual, &["program-name", "--msg", "--delivery"]);
}

#[test]
fn redact_arg_values_option_one_optional_args() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(option)]
        /// a msg param
        _msg: String,

        #[argh(option)]
        /// a delivery param
        _delivery: Option<String>,
    }

    let actual =
        Cmd::redact_arg_values(&["program-name"], &["--msg", "hello", "--delivery", "next day"])
            .unwrap();
    assert_eq!(actual, &["program-name", "--msg", "--delivery"]);

    let actual = Cmd::redact_arg_values(&["program-name"], &["--msg", "hello"]).unwrap();
    assert_eq!(actual, &["program-name", "--msg"]);
}

#[test]
fn redact_arg_values_option_repeating() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(option)]
        /// fooey
        _msg: Vec<String>,
    }

    let actual = Cmd::redact_arg_values(&["program-name"], &[]).unwrap();
    assert_eq!(actual, &["program-name"]);

    let actual =
        Cmd::redact_arg_values(&["program-name"], &["--msg", "abc", "--msg", "xyz"]).unwrap();
    assert_eq!(actual, &["program-name", "--msg", "--msg"]);
}

#[test]
fn redact_arg_values_switch() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(switch, short = 'f')]
        /// speed of cmd
        _faster: bool,
    }

    let actual = Cmd::redact_arg_values(&["program-name"], &["--faster"]).unwrap();
    assert_eq!(actual, &["program-name", "--faster"]);

    let actual = Cmd::redact_arg_values(&["program-name"], &["-f"]).unwrap();
    assert_eq!(actual, &["program-name", "-f"]);
}

#[test]
fn redact_arg_values_positional() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[allow(unused)]
        #[argh(positional)]
        /// speed of cmd
        speed: u8,
    }

    let actual = Cmd::redact_arg_values(&["program-name"], &["5"]).unwrap();
    assert_eq!(actual, &["program-name", "speed"]);
}

#[test]
fn redact_arg_values_positional_arg_name() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(positional, arg_name = "speed")]
        /// speed of cmd
        _speed: u8,
    }

    let actual = Cmd::redact_arg_values(&["program-name"], &["5"]).unwrap();
    assert_eq!(actual, &["program-name", "speed"]);
}

#[test]
fn redact_arg_values_positional_repeating() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(positional, arg_name = "speed")]
        /// speed of cmd
        _speed: Vec<u8>,
    }

    let actual = Cmd::redact_arg_values(&["program-name"], &["5", "6"]).unwrap();
    assert_eq!(actual, &["program-name", "speed", "speed"]);
}

#[test]
fn redact_arg_values_positional_err() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(positional, arg_name = "speed")]
        /// speed of cmd
        _speed: u8,
    }

    let actual = Cmd::redact_arg_values(&["program-name"], &[]).unwrap_err();
    assert_eq!(
        actual,
        argh::EarlyExit {
            output: "Required positional arguments not provided:\n    speed\n".into(),
            status: Err(()),
        }
    );
}

#[test]
fn redact_arg_values_two_positional() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(positional, arg_name = "speed")]
        /// speed of cmd
        _speed: u8,

        #[argh(positional, arg_name = "direction")]
        /// direction
        _direction: String,
    }

    let actual = Cmd::redact_arg_values(&["program-name"], &["5", "north"]).unwrap();
    assert_eq!(actual, &["program-name", "speed", "direction"]);
}

#[test]
fn redact_arg_values_positional_option() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(positional, arg_name = "speed")]
        /// speed of cmd
        _speed: u8,

        #[argh(option)]
        /// direction
        _direction: String,
    }

    let actual = Cmd::redact_arg_values(&["program-name"], &["5", "--direction", "north"]).unwrap();
    assert_eq!(actual, &["program-name", "speed", "--direction"]);
}

#[test]
fn redact_arg_values_positional_optional_option() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(positional, arg_name = "speed")]
        /// speed of cmd
        _speed: u8,

        #[argh(option)]
        /// direction
        _direction: Option<String>,
    }

    let actual = Cmd::redact_arg_values(&["program-name"], &["5"]).unwrap();
    assert_eq!(actual, &["program-name", "speed"]);
}

#[test]
fn redact_arg_values_subcommand() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(positional, arg_name = "speed")]
        /// speed of cmd
        _speed: u8,

        #[argh(subcommand)]
        /// means of transportation
        _means: MeansSubcommand,
    }

    #[derive(FromArgs, Debug)]
    /// Short description
    #[argh(subcommand)]
    enum MeansSubcommand {
        Walking(WalkingSubcommand),
        Biking(BikingSubcommand),
        Driving(DrivingSubcommand),
    }

    #[derive(FromArgs, Debug)]
    #[argh(subcommand, name = "walking")]
    /// Short description
    struct WalkingSubcommand {
        #[argh(option)]
        /// a song to listen to
        _music: String,
    }

    #[derive(FromArgs, Debug)]
    #[argh(subcommand, name = "biking")]
    /// Short description
    struct BikingSubcommand {}
    #[derive(FromArgs, Debug)]
    #[argh(subcommand, name = "driving")]
    /// short description
    struct DrivingSubcommand {}

    let actual =
        Cmd::redact_arg_values(&["program-name"], &["5", "walking", "--music", "Bach"]).unwrap();
    assert_eq!(actual, &["program-name", "speed", "walking", "--music"]);
}

#[test]
fn redact_arg_values_subcommand_with_space_in_name() {
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(positional, arg_name = "speed")]
        /// speed of cmd
        _speed: u8,

        #[argh(subcommand)]
        /// means of transportation
        _means: MeansSubcommand,
    }

    #[derive(FromArgs, Debug)]
    /// Short description
    #[argh(subcommand)]
    enum MeansSubcommand {
        Walking(WalkingSubcommand),
        Biking(BikingSubcommand),
    }

    #[derive(FromArgs, Debug)]
    #[argh(subcommand, name = "has space")]
    /// Short description
    struct WalkingSubcommand {
        #[argh(option)]
        /// a song to listen to
        _music: String,
    }

    #[derive(FromArgs, Debug)]
    #[argh(subcommand, name = "biking")]
    /// Short description
    struct BikingSubcommand {}

    let actual =
        Cmd::redact_arg_values(&["program-name"], &["5", "has space", "--music", "Bach"]).unwrap();
    assert_eq!(actual, &["program-name", "speed", "has space", "--music"]);
}

#[test]
fn redact_arg_values_produces_help() {
    #[derive(argh::FromArgs, Debug, PartialEq)]
    /// Woot
    struct Repeating {
        #[argh(option, short = 'n')]
        /// fooey
        n: Vec<String>,
    }

    assert_eq!(
        Repeating::redact_arg_values(&["program-name"], &["--help"]),
        Err(argh::EarlyExit {
            output: r###"Usage: program-name [-n <n...>]

Woot

Options:
  -n, --n           fooey
  --help            display usage information
"###
            .to_owned(),
            status: Ok(()),
        }),
    );
}

#[test]
fn redact_arg_values_produces_errors_with_bad_arguments() {
    #[derive(argh::FromArgs, Debug, PartialEq)]
    /// Woot
    struct Cmd {
        #[argh(option, short = 'n')]
        /// fooey
        n: String,
    }

    assert_eq!(
        Cmd::redact_arg_values(&["program-name"], &["--n"]),
        Err(argh::EarlyExit {
            output: "No value provided for option '--n'.\n".to_owned(),
            status: Err(()),
        }),
    );
}

#[test]
fn redact_arg_values_does_not_warn_if_used() {
    #[forbid(unused)]
    #[derive(FromArgs, Debug)]
    /// Short description
    struct Cmd {
        #[argh(positional)]
        /// speed of cmd
        speed: u8,
    }

    let cmd = Cmd::from_args(&["program-name"], &["5"]).unwrap();
    assert_eq!(cmd.speed, 5);

    let actual = Cmd::redact_arg_values(&["program-name"], &["5"]).unwrap();
    assert_eq!(actual, &["program-name", "speed"]);
}

#[test]
fn subcommand_does_not_panic() {
    #[derive(FromArgs, PartialEq, Debug)]
    #[argh(subcommand)]
    enum SubCommandEnum {
        Cmd(SubCommand),
    }

    #[derive(FromArgs, PartialEq, Debug)]
    /// First subcommand.
    #[argh(subcommand, name = "one")]
    struct SubCommand {
        #[argh(positional)]
        /// how many x
        x: usize,
    }

    #[derive(FromArgs, PartialEq, Debug)]
    /// Second subcommand.
    #[argh(subcommand, name = "two")]
    struct SubCommandTwo {
        #[argh(switch)]
        /// whether to fooey
        fooey: bool,
    }

    // Passing no subcommand name to an emum
    assert_eq!(
        SubCommandEnum::from_args(&[], &["5"]).unwrap_err(),
        argh::EarlyExit { output: "no subcommand name".into(), status: Err(()) },
    );

    assert_eq!(
        SubCommandEnum::redact_arg_values(&[], &["5"]).unwrap_err(),
        argh::EarlyExit { output: "no subcommand name".into(), status: Err(()) },
    );

    // Passing unknown subcommand name to an emum
    assert_eq!(
        SubCommandEnum::from_args(&["fooey"], &["5"]).unwrap_err(),
        argh::EarlyExit { output: "no subcommand matched".into(), status: Err(()) },
    );

    assert_eq!(
        SubCommandEnum::redact_arg_values(&["fooey"], &["5"]).unwrap_err(),
        argh::EarlyExit { output: "no subcommand matched".into(), status: Err(()) },
    );

    // Passing unknown subcommand name to a struct
    assert_eq!(
        SubCommand::redact_arg_values(&[], &["5"]).unwrap_err(),
        argh::EarlyExit { output: "no subcommand name".into(), status: Err(()) },
    );
}

#[test]
fn long_alphanumeric() {
    #[derive(FromArgs)]
    /// Short description
    struct Cmd {
        #[argh(option, long = "ac97")]
        /// fooey
        ac97: String,
    }

    let cmd = Cmd::from_args(&["cmdname"], &["--ac97", "bar"]).unwrap();
    assert_eq!(cmd.ac97, "bar");
}
