diff options
Diffstat (limited to 'tests/args_info_tests.rs')
-rw-r--r-- | tests/args_info_tests.rs | 1029 |
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() + }) +} |