| #![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() | |
| }) | |
| } |