// 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, } 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 where ::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, } 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 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 { 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 { 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::::always_five))] five: usize, } struct AlwaysFive(T); impl AlwaysFive { fn always_five(_value: &str) -> Result { 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, argh::EarlyExit>> { Some(Err(argh::EarlyExit::from("Test should not redact".to_owned()))) } fn try_from_args( command_name: &[&str], args: &[&str], ) -> Option> { 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::( 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(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(args: &[&str], expected: T) { let t = T::from_args(&["cmd"], args).expect("failed to parse"); assert_eq!(t, expected); } fn assert_error(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::( &["-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, } #[test] fn repeating() { assert_help_string::( r###"Usage: test_arg_0 [-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, } #[test] fn with_arg_name() { assert_help_string::( r###"Usage: test_arg_0 [--option-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, } #[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::( r###"Usage: test_arg_0 [] 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, #[argh(positional, greedy)] /// fooey d: Vec, } #[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::( r###"Usage: test_arg_0 [--b] [--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, } #[test] fn optional() { assert_output(&["5"], LastOptional { a: 5, b: None }); assert_output(&["5", "6"], LastOptional { a: 5, b: Some("6".into()) }); assert_error::(&["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::(&["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::( &[], r###"Required positional arguments not provided: a b "###, ); assert_error::( &["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::( &["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::( &[], 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, } #[derive(FromArgs, Debug, PartialEq)] #[argh(subcommand, name = "a")] /// Subcommand of positional::WithSubcommand. struct Subcommand { #[argh(positional)] /// fooey a: String, #[argh(positional)] /// fooey b: Vec, } #[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::( &["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::( &[], 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. `...` #[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, #[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`. #[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 [] 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 [] 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 .", example = "Scribble 'abc' and then run |grind|.\n$ {command_name} -s 'abc' grind old.txt taxes.cp", note = "Use `{command_name} help ` for details on [] 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 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, argh::EarlyExit>> { Some(Err(argh::EarlyExit::from("Test should not redact".to_owned()))) } fn try_from_args( command_name: &[&str], args: &[&str], ) -> Option> { 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::( r###"Usage: test_arg_0 [-f] [--really-really-really-long-name-for-pat] -s [-v] [] Destroy the contents of . 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 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 ` for details on [] 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 . struct WithArgName { #[argh(positional, arg_name = "name")] username: String, } #[test] fn with_arg_name() { assert_help_string::( r###"Usage: test_arg_0 Destroy the contents of . 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::( r###"Usage: test_arg_0 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, } 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, } 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, } 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, } 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, } 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, } 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, } 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, } 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, } assert_eq!( Repeating::redact_arg_values(&["program-name"], &["--help"]), Err(argh::EarlyExit { output: r###"Usage: program-name [-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"); }