aboutsummaryrefslogtreecommitdiff
path: root/tests/args_info_tests.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/args_info_tests.rs')
-rw-r--r--tests/args_info_tests.rs1029
1 files changed, 1029 insertions, 0 deletions
diff --git a/tests/args_info_tests.rs b/tests/args_info_tests.rs
new file mode 100644
index 0000000..1fc0730
--- /dev/null
+++ b/tests/args_info_tests.rs
@@ -0,0 +1,1029 @@
+#![cfg(test)]
+// Copyright (c) 2023 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.
+
+use argh::{
+ ArgsInfo, CommandInfoWithArgs, ErrorCodeInfo, FlagInfo, FlagInfoKind, FromArgs, Optionality,
+ PositionalInfo, SubCommandInfo,
+};
+
+fn assert_args_info<T: ArgsInfo>(expected: &CommandInfoWithArgs) {
+ let actual_value = T::get_args_info();
+ assert_eq!(expected, &actual_value)
+}
+
+const HELP_FLAG: FlagInfo<'_> = FlagInfo {
+ kind: FlagInfoKind::Switch,
+ optionality: Optionality::Optional,
+ long: "--help",
+ short: None,
+ description: "display usage information",
+ hidden: false,
+};
+
+/// Tests that exercise the JSON output for help text.
+#[test]
+fn args_info_test_subcommand() {
+ #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
+ /// Top-level command.
+ struct TopLevel {
+ #[argh(subcommand)]
+ nested: MySubCommandEnum,
+ }
+
+ #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
+ #[argh(subcommand)]
+ enum MySubCommandEnum {
+ One(SubCommandOne),
+ Two(SubCommandTwo),
+ }
+
+ #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
+ /// First subcommand.
+ #[argh(subcommand, name = "one")]
+ struct SubCommandOne {
+ #[argh(option)]
+ /// how many x
+ x: usize,
+ }
+
+ #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
+ /// Second subcommand.
+ #[argh(subcommand, name = "two")]
+ struct SubCommandTwo {
+ #[argh(switch)]
+ /// whether to fooey
+ fooey: bool,
+ }
+
+ let command_one = CommandInfoWithArgs {
+ name: "one",
+ description: "First subcommand.",
+ flags: &[
+ HELP_FLAG,
+ FlagInfo {
+ kind: FlagInfoKind::Option { arg_name: "x" },
+ optionality: Optionality::Required,
+ long: "--x",
+ short: None,
+ description: "how many x",
+ hidden: false,
+ },
+ ],
+ ..Default::default()
+ };
+
+ assert_args_info::<TopLevel>(&CommandInfoWithArgs {
+ name: "TopLevel",
+ description: "Top-level command.",
+ examples: &[],
+ flags: &[HELP_FLAG],
+ notes: &[],
+ positionals: &[],
+ error_codes: &[],
+ commands: vec![
+ SubCommandInfo { name: "one", command: command_one.clone() },
+ SubCommandInfo {
+ name: "two",
+ command: CommandInfoWithArgs {
+ name: "two",
+ description: "Second subcommand.",
+ flags: &[
+ HELP_FLAG,
+ FlagInfo {
+ kind: FlagInfoKind::Switch,
+ optionality: Optionality::Optional,
+ long: "--fooey",
+ short: None,
+ description: "whether to fooey",
+ hidden: false,
+ },
+ ],
+ ..Default::default()
+ },
+ },
+ ],
+ });
+
+ assert_args_info::<SubCommandOne>(&command_one);
+}
+
+#[test]
+fn args_info_test_multiline_doc_comment() {
+ #[derive(FromArgs, ArgsInfo)]
+ /// Short description
+ struct Cmd {
+ #[argh(switch)]
+ /// a switch with a description
+ /// that is spread across
+ /// a number of
+ /// lines of comments.
+ _s: bool,
+ }
+ assert_args_info::<Cmd>(
+ &CommandInfoWithArgs {
+ name: "Cmd",
+ description: "Short description",
+ flags: &[HELP_FLAG,
+ FlagInfo {
+ kind: FlagInfoKind::Switch,
+ optionality: Optionality::Optional,
+ long: "--s",
+ short: None,
+ description: "a switch with a description that is spread across a number of lines of comments.",
+ hidden:false
+ }
+ ],
+ ..Default::default()
+ });
+}
+
+#[test]
+fn args_info_test_basic_args() {
+ #[allow(dead_code)]
+ #[derive(FromArgs, ArgsInfo)]
+ /// Basic command args demonstrating multiple types and cardinality. "With quotes"
+ struct Basic {
+ /// should the power be on. "Quoted value" should work too.
+ #[argh(switch)]
+ power: bool,
+
+ /// option that is required because of no default and not Option<>.
+ #[argh(option, long = "required")]
+ required_flag: String,
+
+ /// optional speed if not specified it is None.
+ #[argh(option, short = 's')]
+ speed: Option<u8>,
+
+ /// repeatable option.
+ #[argh(option, arg_name = "url")]
+ link: Vec<String>,
+ }
+ assert_args_info::<Basic>(&CommandInfoWithArgs {
+ name: "Basic",
+ description:
+ "Basic command args demonstrating multiple types and cardinality. \"With quotes\"",
+ flags: &[
+ FlagInfo {
+ kind: FlagInfoKind::Switch,
+ optionality: Optionality::Optional,
+ long: "--help",
+ short: None,
+ description: "display usage information",
+ hidden: false,
+ },
+ FlagInfo {
+ kind: FlagInfoKind::Switch,
+ optionality: Optionality::Optional,
+ long: "--power",
+ short: None,
+ description: "should the power be on. \"Quoted value\" should work too.",
+ hidden: false,
+ },
+ FlagInfo {
+ kind: FlagInfoKind::Option { arg_name: "required" },
+ optionality: Optionality::Required,
+ long: "--required",
+ short: None,
+ description: "option that is required because of no default and not Option<>.",
+ hidden: false,
+ },
+ FlagInfo {
+ kind: FlagInfoKind::Option { arg_name: "speed" },
+ optionality: Optionality::Optional,
+ long: "--speed",
+ short: Some('s'),
+ description: "optional speed if not specified it is None.",
+ hidden: false,
+ },
+ FlagInfo {
+ kind: FlagInfoKind::Option { arg_name: "url" },
+ optionality: Optionality::Repeating,
+ long: "--link",
+ short: None,
+ description: "repeatable option.",
+ hidden: false,
+ },
+ ],
+ ..Default::default()
+ });
+}
+
+#[test]
+fn args_info_test_positional_args() {
+ #[allow(dead_code)]
+ #[derive(FromArgs, ArgsInfo)]
+ /// Command with positional args demonstrating. "With quotes"
+ struct Positional {
+ /// the "root" position.
+ #[argh(positional, arg_name = "root")]
+ root_value: String,
+
+ /// trunk value
+ #[argh(positional)]
+ trunk: String,
+
+ /// leaves. There can be many leaves.
+ #[argh(positional)]
+ leaves: Vec<String>,
+ }
+ assert_args_info::<Positional>(&CommandInfoWithArgs {
+ name: "Positional",
+ description: "Command with positional args demonstrating. \"With quotes\"",
+ flags: &[HELP_FLAG],
+ positionals: &[
+ PositionalInfo {
+ name: "root",
+ description: "the \"root\" position.",
+ optionality: Optionality::Required,
+ hidden: false,
+ },
+ PositionalInfo {
+ name: "trunk",
+ description: "trunk value",
+ optionality: Optionality::Required,
+ hidden: false,
+ },
+ PositionalInfo {
+ name: "leaves",
+ description: "leaves. There can be many leaves.",
+ optionality: Optionality::Repeating,
+ hidden: false,
+ },
+ ],
+
+ ..Default::default()
+ });
+}
+
+#[test]
+fn args_info_test_optional_positional_args() {
+ #[allow(dead_code)]
+ #[derive(FromArgs, ArgsInfo)]
+ /// Command with positional args demonstrating last value is optional
+ struct Positional {
+ /// the "root" position.
+ #[argh(positional, arg_name = "root")]
+ root_value: String,
+
+ /// trunk value
+ #[argh(positional)]
+ trunk: String,
+
+ /// leaves. There can be an optional leaves.
+ #[argh(positional)]
+ leaves: Option<String>,
+ }
+ assert_args_info::<Positional>(&CommandInfoWithArgs {
+ name: "Positional",
+ description: "Command with positional args demonstrating last value is optional",
+ flags: &[FlagInfo {
+ kind: FlagInfoKind::Switch,
+ optionality: Optionality::Optional,
+ long: "--help",
+ short: None,
+ description: "display usage information",
+ hidden: false,
+ }],
+ positionals: &[
+ PositionalInfo {
+ name: "root",
+ description: "the \"root\" position.",
+ optionality: Optionality::Required,
+ hidden: false,
+ },
+ PositionalInfo {
+ name: "trunk",
+ description: "trunk value",
+ optionality: Optionality::Required,
+ hidden: false,
+ },
+ PositionalInfo {
+ name: "leaves",
+ description: "leaves. There can be an optional leaves.",
+ optionality: Optionality::Optional,
+ hidden: false,
+ },
+ ],
+
+ ..Default::default()
+ });
+}
+
+#[test]
+fn args_info_test_default_positional_args() {
+ #[allow(dead_code)]
+ #[derive(FromArgs, ArgsInfo)]
+ /// Command with positional args demonstrating last value is defaulted.
+ struct Positional {
+ /// the "root" position.
+ #[argh(positional, arg_name = "root")]
+ root_value: String,
+
+ /// trunk value
+ #[argh(positional)]
+ trunk: String,
+
+ /// leaves. There can be one leaf, defaults to hello.
+ #[argh(positional, default = "String::from(\"hello\")")]
+ leaves: String,
+ }
+ assert_args_info::<Positional>(&CommandInfoWithArgs {
+ name: "Positional",
+ description: "Command with positional args demonstrating last value is defaulted.",
+ flags: &[HELP_FLAG],
+ positionals: &[
+ PositionalInfo {
+ name: "root",
+ description: "the \"root\" position.",
+ optionality: Optionality::Required,
+ hidden: false,
+ },
+ PositionalInfo {
+ name: "trunk",
+ description: "trunk value",
+ optionality: Optionality::Required,
+ hidden: false,
+ },
+ PositionalInfo {
+ name: "leaves",
+ description: "leaves. There can be one leaf, defaults to hello.",
+ optionality: Optionality::Optional,
+ hidden: false,
+ },
+ ],
+
+ ..Default::default()
+ });
+}
+
+#[test]
+fn args_info_test_notes_examples_errors() {
+ #[allow(dead_code)]
+ #[derive(FromArgs, ArgsInfo)]
+ /// Command with Examples and usage Notes, including error codes.
+ #[argh(
+ note = r##"
+ These usage notes appear for {command_name} and how to best use it.
+ The formatting should be preserved.
+ one
+ two
+ three then a blank
+
+ and one last line with "quoted text"."##,
+ example = r##"
+ Use the command with 1 file:
+ `{command_name} /path/to/file`
+ Use it with a "wildcard":
+ `{command_name} /path/to/*`
+ a blank line
+
+ and one last line with "quoted text"."##,
+ error_code(0, "Success"),
+ error_code(1, "General Error"),
+ error_code(2, "Some error with \"quotes\"")
+ )]
+ struct NotesExamplesErrors {
+ /// the "root" position.
+ #[argh(positional, arg_name = "files")]
+ fields: Vec<std::path::PathBuf>,
+ }
+ assert_args_info::<NotesExamplesErrors>(
+ &CommandInfoWithArgs {
+ name: "NotesExamplesErrors",
+ description: "Command with Examples and usage Notes, including error codes.",
+ examples: &["\n Use the command with 1 file:\n `{command_name} /path/to/file`\n Use it with a \"wildcard\":\n `{command_name} /path/to/*`\n a blank line\n \n and one last line with \"quoted text\"."],
+ flags: &[HELP_FLAG
+ ],
+ positionals: &[
+ PositionalInfo{
+ name: "files",
+ description: "the \"root\" position.",
+ optionality: Optionality::Repeating,
+ hidden:false
+ }
+ ],
+ notes: &["\n These usage notes appear for {command_name} and how to best use it.\n The formatting should be preserved.\n one\n two\n three then a blank\n \n and one last line with \"quoted text\"."],
+ error_codes: & [ErrorCodeInfo { code: 0, description: "Success" }, ErrorCodeInfo { code: 1, description: "General Error" }, ErrorCodeInfo { code: 2, description: "Some error with \"quotes\"" }],
+ ..Default::default()
+ });
+}
+
+#[test]
+fn args_info_test_subcommands() {
+ #[allow(dead_code)]
+ #[derive(FromArgs, ArgsInfo)]
+ ///Top level command with "subcommands".
+ struct TopLevel {
+ /// show verbose output
+ #[argh(switch)]
+ verbose: bool,
+
+ /// this doc comment does not appear anywhere.
+ #[argh(subcommand)]
+ cmd: SubcommandEnum,
+ }
+
+ #[derive(FromArgs, ArgsInfo)]
+ #[argh(subcommand)]
+ /// Doc comments for subcommand enums does not appear in the help text.
+ enum SubcommandEnum {
+ Command1(Command1Args),
+ Command2(Command2Args),
+ Command3(Command3Args),
+ }
+
+ /// Command1 args are used for Command1.
+ #[allow(dead_code)]
+ #[derive(FromArgs, ArgsInfo)]
+ #[argh(subcommand, name = "one")]
+ struct Command1Args {
+ /// the "root" position.
+ #[argh(positional, arg_name = "root")]
+ root_value: String,
+
+ /// trunk value
+ #[argh(positional)]
+ trunk: String,
+
+ /// leaves. There can be zero leaves, defaults to hello.
+ #[argh(positional, default = "String::from(\"hello\")")]
+ leaves: String,
+ }
+ /// Command2 args are used for Command2.
+ #[allow(dead_code)]
+ #[derive(FromArgs, ArgsInfo)]
+ #[argh(subcommand, name = "two")]
+ struct Command2Args {
+ /// should the power be on. "Quoted value" should work too.
+ #[argh(switch)]
+ power: bool,
+
+ /// option that is required because of no default and not Option<>.
+ #[argh(option, long = "required")]
+ required_flag: String,
+
+ /// optional speed if not specified it is None.
+ #[argh(option, short = 's')]
+ speed: Option<u8>,
+
+ /// repeatable option.
+ #[argh(option, arg_name = "url")]
+ link: Vec<String>,
+ }
+ /// Command3 args are used for Command3 which has no options or arguments.
+ #[derive(FromArgs, ArgsInfo)]
+ #[argh(subcommand, name = "three")]
+ struct Command3Args {}
+
+ assert_args_info::<TopLevel>(&CommandInfoWithArgs {
+ name: "TopLevel",
+ description: "Top level command with \"subcommands\".",
+ flags: &[
+ HELP_FLAG,
+ FlagInfo {
+ kind: FlagInfoKind::Switch,
+ optionality: Optionality::Optional,
+ long: "--verbose",
+ short: None,
+ description: "show verbose output",
+ hidden: false,
+ },
+ ],
+ positionals: &[],
+ commands: vec![
+ SubCommandInfo {
+ name: "one",
+ command: CommandInfoWithArgs {
+ name: "one",
+ description: "Command1 args are used for Command1.",
+ flags: &[HELP_FLAG],
+ positionals: &[
+ PositionalInfo {
+ name: "root",
+ description: "the \"root\" position.",
+ optionality: Optionality::Required,
+ hidden: false,
+ },
+ PositionalInfo {
+ name: "trunk",
+ description: "trunk value",
+ optionality: Optionality::Required,
+ hidden: false,
+ },
+ PositionalInfo {
+ name: "leaves",
+ description: "leaves. There can be zero leaves, defaults to hello.",
+ optionality: Optionality::Optional,
+ hidden: false,
+ },
+ ],
+ ..Default::default()
+ },
+ },
+ SubCommandInfo {
+ name: "two",
+ command: CommandInfoWithArgs {
+ name: "two",
+ description: "Command2 args are used for Command2.",
+ flags: &[
+ HELP_FLAG,
+ FlagInfo {
+ kind: FlagInfoKind::Switch,
+ optionality: Optionality::Optional,
+ long: "--power",
+ short: None,
+ description:
+ "should the power be on. \"Quoted value\" should work too.",
+ hidden: false,
+ },
+ FlagInfo {
+ kind: FlagInfoKind::Option { arg_name: "required" },
+ optionality: Optionality::Required,
+ long: "--required",
+ short: None,
+ description:
+ "option that is required because of no default and not Option<>.",
+ hidden: false,
+ },
+ FlagInfo {
+ kind: FlagInfoKind::Option { arg_name: "speed" },
+ optionality: Optionality::Optional,
+ long: "--speed",
+ short: Some('s'),
+ description: "optional speed if not specified it is None.",
+ hidden: false,
+ },
+ FlagInfo {
+ kind: FlagInfoKind::Option { arg_name: "url" },
+ optionality: Optionality::Repeating,
+ long: "--link",
+ short: None,
+ description: "repeatable option.",
+ hidden: false,
+ },
+ ],
+ ..Default::default()
+ },
+ },
+ SubCommandInfo {
+ name: "three",
+ command: CommandInfoWithArgs {
+ name: "three",
+ description:
+ "Command3 args are used for Command3 which has no options or arguments.",
+ flags: &[HELP_FLAG],
+ positionals: &[],
+ ..Default::default()
+ },
+ },
+ ],
+ ..Default::default()
+ });
+}
+
+#[test]
+fn args_info_test_subcommand_notes_examples() {
+ #[allow(dead_code)]
+ #[derive(FromArgs, ArgsInfo)]
+ ///Top level command with "subcommands".
+ #[argh(
+ note = "Top level note",
+ example = "Top level example",
+ error_code(0, "Top level success")
+ )]
+ struct TopLevel {
+ /// this doc comment does not appear anywhere.
+ #[argh(subcommand)]
+ cmd: SubcommandEnum,
+ }
+
+ #[derive(FromArgs, ArgsInfo)]
+ #[argh(subcommand)]
+ /// Doc comments for subcommand enums does not appear in the help text.
+ enum SubcommandEnum {
+ Command1(Command1Args),
+ }
+
+ /// Command1 args are used for subcommand one.
+ #[allow(dead_code)]
+ #[derive(FromArgs, ArgsInfo)]
+ #[argh(
+ subcommand,
+ name = "one",
+ note = "{command_name} is used as a subcommand of \"Top level\"",
+ example = "\"Typical\" usage is `{command_name}`.",
+ error_code(0, "one level success")
+ )]
+ struct Command1Args {
+ /// the "root" position.
+ #[argh(positional, arg_name = "root")]
+ root_value: String,
+
+ /// trunk value
+ #[argh(positional)]
+ trunk: String,
+
+ /// leaves. There can be many leaves.
+ #[argh(positional)]
+ leaves: Vec<String>,
+ }
+
+ let command_one = CommandInfoWithArgs {
+ name: "one",
+ description: "Command1 args are used for subcommand one.",
+ error_codes: &[ErrorCodeInfo { code: 0, description: "one level success" }],
+ examples: &["\"Typical\" usage is `{command_name}`."],
+ flags: &[HELP_FLAG],
+ notes: &["{command_name} is used as a subcommand of \"Top level\""],
+ positionals: &[
+ PositionalInfo {
+ name: "root",
+ description: "the \"root\" position.",
+ optionality: Optionality::Required,
+ hidden: false,
+ },
+ PositionalInfo {
+ name: "trunk",
+ description: "trunk value",
+ optionality: Optionality::Required,
+ hidden: false,
+ },
+ PositionalInfo {
+ name: "leaves",
+ description: "leaves. There can be many leaves.",
+ optionality: Optionality::Repeating,
+ hidden: false,
+ },
+ ],
+ ..Default::default()
+ };
+
+ assert_args_info::<TopLevel>(&CommandInfoWithArgs {
+ name: "TopLevel",
+ description: "Top level command with \"subcommands\".",
+ error_codes: &[ErrorCodeInfo { code: 0, description: "Top level success" }],
+ examples: &["Top level example"],
+ flags: &[HELP_FLAG],
+ notes: &["Top level note"],
+ commands: vec![SubCommandInfo { name: "one", command: command_one.clone() }],
+ ..Default::default()
+ });
+
+ assert_args_info::<Command1Args>(&command_one);
+}
+
+#[test]
+fn args_info_test_example() {
+ #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
+ #[argh(
+ description = "Destroy the contents of <file> with a specific \"method of destruction\".",
+ 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, ArgsInfo, PartialEq, Debug)]
+ #[argh(subcommand)]
+ enum HelpExampleSubCommands {
+ BlowUp(BlowUp),
+ Grind(GrindCommand),
+ }
+
+ #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
+ #[argh(subcommand, name = "blow-up")]
+ /// explosively separate
+ struct BlowUp {
+ /// blow up bombs safely
+ #[argh(switch)]
+ safely: bool,
+ }
+
+ #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
+ #[argh(subcommand, name = "grind", description = "make smaller by many small cuts")]
+ struct GrindCommand {
+ /// wear a visor while grinding
+ #[argh(switch)]
+ safely: bool,
+ }
+
+ assert_args_info::<HelpExample>(
+ &CommandInfoWithArgs {
+ name: "HelpExample",
+ description: "Destroy the contents of <file> with a specific \"method of destruction\".",
+ examples: &["Scribble 'abc' and then run |grind|.\n$ {command_name} -s 'abc' grind old.txt taxes.cp"],
+ flags: &[HELP_FLAG,
+ FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--force", short: Some('f'), description: "force, ignore minor errors. This description is so long that it wraps to the next line.",
+ hidden:false },
+ FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--really-really-really-long-name-for-pat", short: None, description: "documentation",
+ hidden:false },
+ FlagInfo { kind: FlagInfoKind::Option { arg_name: "scribble"},
+ optionality: Optionality::Required, long: "--scribble", short: Some('s'), description: "write <scribble> repeatedly",
+ hidden:false },
+ FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--verbose", short: Some('v'), description: "say more. Defaults to $BLAST_VERBOSE.",
+ hidden:false }
+ ],
+ notes: &["Use `{command_name} help <command>` for details on [<args>] for a subcommand."],
+ commands: vec![
+ SubCommandInfo { name: "blow-up",
+ command: CommandInfoWithArgs { name: "blow-up",
+ description: "explosively separate",
+ flags:& [HELP_FLAG,
+ FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--safely", short: None, description: "blow up bombs safely",
+ hidden:false }
+ ],
+ ..Default::default()
+ } },
+ SubCommandInfo {
+ name: "grind",
+ command: CommandInfoWithArgs {
+ name: "grind",
+ description: "make smaller by many small cuts",
+ flags: &[HELP_FLAG,
+ FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--safely", short: None, description: "wear a visor while grinding" ,hidden:false}],
+ ..Default::default()
+ }
+ }],
+ error_codes: &[ErrorCodeInfo { code: 2, description: "The blade is too dull." }, ErrorCodeInfo { code: 3, description: "Out of fuel." }],
+ ..Default::default()
+ }
+ );
+}
+
+#[test]
+fn positional_greedy() {
+ #[allow(dead_code)]
+ #[derive(FromArgs, ArgsInfo)]
+ /// Woot
+ struct LastRepeatingGreedy {
+ #[argh(positional)]
+ /// fooey
+ pub a: u32,
+ #[argh(switch)]
+ /// woo
+ pub b: bool,
+ #[argh(option)]
+ /// stuff
+ pub c: Option<String>,
+ #[argh(positional, greedy)]
+ /// fooey
+ pub d: Vec<String>,
+ }
+ assert_args_info::<LastRepeatingGreedy>(&CommandInfoWithArgs {
+ name: "LastRepeatingGreedy",
+ description: "Woot",
+ flags: &[
+ HELP_FLAG,
+ FlagInfo {
+ kind: FlagInfoKind::Switch,
+ optionality: Optionality::Optional,
+ long: "--b",
+ short: None,
+ description: "woo",
+ hidden: false,
+ },
+ FlagInfo {
+ kind: FlagInfoKind::Option { arg_name: "c" },
+ optionality: Optionality::Optional,
+ long: "--c",
+ short: None,
+ description: "stuff",
+ hidden: false,
+ },
+ ],
+ positionals: &[
+ PositionalInfo {
+ name: "a",
+ description: "fooey",
+ optionality: Optionality::Required,
+ hidden: false,
+ },
+ PositionalInfo {
+ name: "d",
+ description: "fooey",
+ optionality: Optionality::Greedy,
+ hidden: false,
+ },
+ ],
+ ..Default::default()
+ });
+}
+
+#[test]
+fn hidden_help_attribute() {
+ #[derive(FromArgs, ArgsInfo)]
+ /// 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_args_info::<Cmd>(&CommandInfoWithArgs {
+ name: "Cmd",
+ description: "Short description",
+ flags: &[
+ HELP_FLAG,
+ FlagInfo {
+ kind: FlagInfoKind::Option { arg_name: "three" },
+ optionality: Optionality::Required,
+ long: "--three",
+ short: None,
+ description: "this one should be hidden",
+ hidden: true,
+ },
+ ],
+ positionals: &[
+ PositionalInfo {
+ name: "one",
+ description: "this one should be hidden",
+ optionality: Optionality::Required,
+ hidden: true,
+ },
+ PositionalInfo {
+ name: "two",
+ description: "this one is real",
+ optionality: Optionality::Required,
+ hidden: false,
+ },
+ ],
+ ..Default::default()
+ });
+}
+
+#[test]
+fn test_dynamic_subcommand() {
+ #[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, ArgsInfo, PartialEq, Debug)]
+ /// Top-level command.
+ struct TopLevel {
+ #[argh(subcommand)]
+ nested: MySubCommandEnum,
+ }
+
+ #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
+ #[argh(subcommand)]
+ enum MySubCommandEnum {
+ One(SubCommandOne),
+ Two(SubCommandTwo),
+ #[argh(dynamic)]
+ ThreeFourFive(DynamicSubCommandImpl),
+ }
+
+ #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
+ /// First subcommand.
+ #[argh(subcommand, name = "one")]
+ struct SubCommandOne {
+ #[argh(option)]
+ /// how many x
+ x: usize,
+ }
+
+ #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
+ /// Second subcommand.
+ #[argh(subcommand, name = "two")]
+ struct SubCommandTwo {
+ #[argh(switch)]
+ /// whether to fooey
+ fooey: bool,
+ }
+
+ assert_args_info::<TopLevel>(&CommandInfoWithArgs {
+ name: "TopLevel",
+ description: "Top-level command.",
+ flags: &[HELP_FLAG],
+ commands: vec![
+ SubCommandInfo {
+ name: "one",
+ command: CommandInfoWithArgs {
+ name: "one",
+ description: "First subcommand.",
+ flags: &[
+ HELP_FLAG,
+ FlagInfo {
+ kind: FlagInfoKind::Option { arg_name: "x" },
+ optionality: Optionality::Required,
+ long: "--x",
+ short: None,
+ description: "how many x",
+ hidden: false,
+ },
+ ],
+ ..Default::default()
+ },
+ },
+ SubCommandInfo {
+ name: "two",
+ command: CommandInfoWithArgs {
+ name: "two",
+ description: "Second subcommand.",
+ flags: &[
+ HELP_FLAG,
+ FlagInfo {
+ kind: FlagInfoKind::Switch,
+ optionality: Optionality::Optional,
+ long: "--fooey",
+ short: None,
+ description: "whether to fooey",
+ hidden: false,
+ },
+ ],
+ ..Default::default()
+ },
+ },
+ SubCommandInfo {
+ name: "three",
+ command: CommandInfoWithArgs {
+ name: "three",
+ description: "Third command",
+ ..Default::default()
+ },
+ },
+ SubCommandInfo {
+ name: "four",
+ command: CommandInfoWithArgs {
+ name: "four",
+ description: "Fourth command",
+ ..Default::default()
+ },
+ },
+ SubCommandInfo {
+ name: "five",
+ command: CommandInfoWithArgs {
+ name: "five",
+ description: "Fifth command",
+ ..Default::default()
+ },
+ },
+ ],
+ ..Default::default()
+ })
+}