• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #![cfg(test)]
2 // Copyright (c) 2020 Google LLC All rights reserved.
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file.
5 
6 use {argh::FromArgs, std::fmt::Debug};
7 
8 #[test]
basic_example()9 fn basic_example() {
10     #[derive(FromArgs, PartialEq, Debug)]
11     /// Reach new heights.
12     struct GoUp {
13         /// whether or not to jump
14         #[argh(switch, short = 'j')]
15         jump: bool,
16 
17         /// how high to go
18         #[argh(option)]
19         height: usize,
20 
21         /// an optional nickname for the pilot
22         #[argh(option)]
23         pilot_nickname: Option<String>,
24     }
25 
26     let up = GoUp::from_args(&["cmdname"], &["--height", "5"]).expect("failed go_up");
27     assert_eq!(up, GoUp { jump: false, height: 5, pilot_nickname: None });
28 }
29 
30 #[test]
custom_from_str_example()31 fn custom_from_str_example() {
32     #[derive(FromArgs)]
33     /// Goofy thing.
34     struct FiveStruct {
35         /// always five
36         #[argh(option, from_str_fn(always_five))]
37         five: usize,
38     }
39 
40     fn always_five(_value: &str) -> Result<usize, String> {
41         Ok(5)
42     }
43 
44     let f = FiveStruct::from_args(&["cmdname"], &["--five", "woot"]).expect("failed to five");
45     assert_eq!(f.five, 5);
46 }
47 
48 #[test]
subcommand_example()49 fn subcommand_example() {
50     #[derive(FromArgs, PartialEq, Debug)]
51     /// Top-level command.
52     struct TopLevel {
53         #[argh(subcommand)]
54         nested: MySubCommandEnum,
55     }
56 
57     #[derive(FromArgs, PartialEq, Debug)]
58     #[argh(subcommand)]
59     enum MySubCommandEnum {
60         One(SubCommandOne),
61         Two(SubCommandTwo),
62     }
63 
64     #[derive(FromArgs, PartialEq, Debug)]
65     /// First subcommand.
66     #[argh(subcommand, name = "one")]
67     struct SubCommandOne {
68         #[argh(option)]
69         /// how many x
70         x: usize,
71     }
72 
73     #[derive(FromArgs, PartialEq, Debug)]
74     /// Second subcommand.
75     #[argh(subcommand, name = "two")]
76     struct SubCommandTwo {
77         #[argh(switch)]
78         /// whether to fooey
79         fooey: bool,
80     }
81 
82     let one = TopLevel::from_args(&["cmdname"], &["one", "--x", "2"]).expect("sc 1");
83     assert_eq!(one, TopLevel { nested: MySubCommandEnum::One(SubCommandOne { x: 2 }) },);
84 
85     let two = TopLevel::from_args(&["cmdname"], &["two", "--fooey"]).expect("sc 2");
86     assert_eq!(two, TopLevel { nested: MySubCommandEnum::Two(SubCommandTwo { fooey: true }) },);
87 }
88 
89 #[test]
multiline_doc_comment_description()90 fn multiline_doc_comment_description() {
91     #[derive(FromArgs)]
92     /// Short description
93     struct Cmd {
94         #[argh(switch)]
95         /// a switch with a description
96         /// that is spread across
97         /// a number of
98         /// lines of comments.
99         _s: bool,
100     }
101 
102     assert_help_string::<Cmd>(
103         r###"Usage: test_arg_0 [--s]
104 
105 Short description
106 
107 Options:
108   --s               a switch with a description that is spread across a number
109                     of lines of comments.
110   --help            display usage information
111 "###,
112     );
113 }
114 
115 #[test]
explicit_long_value_for_option()116 fn explicit_long_value_for_option() {
117     #[derive(FromArgs, Debug)]
118     /// Short description
119     struct Cmd {
120         #[argh(option, long = "foo")]
121         /// bar bar
122         x: u8,
123     }
124 
125     let cmd = Cmd::from_args(&["cmdname"], &["--foo", "5"]).unwrap();
126     assert_eq!(cmd.x, 5);
127 }
128 
129 /// Test that descriptions can start with an initialism despite
130 /// usually being required to start with a lowercase letter.
131 #[derive(FromArgs)]
132 #[allow(unused)]
133 struct DescriptionStartsWithInitialism {
134     /// URL fooey
135     #[argh(option)]
136     x: u8,
137 }
138 
139 #[test]
default_number()140 fn default_number() {
141     #[derive(FromArgs)]
142     /// Short description
143     struct Cmd {
144         #[argh(option, default = "5")]
145         /// fooey
146         x: u8,
147     }
148 
149     let cmd = Cmd::from_args(&["cmdname"], &[]).unwrap();
150     assert_eq!(cmd.x, 5);
151 }
152 
153 #[test]
default_function()154 fn default_function() {
155     const MSG: &str = "hey I just met you";
156     fn call_me_maybe() -> String {
157         MSG.to_string()
158     }
159 
160     #[derive(FromArgs)]
161     /// Short description
162     struct Cmd {
163         #[argh(option, default = "call_me_maybe()")]
164         /// fooey
165         msg: String,
166     }
167 
168     let cmd = Cmd::from_args(&["cmdname"], &[]).unwrap();
169     assert_eq!(cmd.msg, MSG);
170 }
171 
172 #[test]
missing_option_value()173 fn missing_option_value() {
174     #[derive(FromArgs, Debug)]
175     /// Short description
176     struct Cmd {
177         #[argh(option)]
178         /// fooey
179         _msg: String,
180     }
181 
182     let e = Cmd::from_args(&["cmdname"], &["--msg"])
183         .expect_err("Parsing missing option value should fail");
184     assert_eq!(e.output, "No value provided for option \'--msg\'.\n");
185     assert!(e.status.is_err());
186 }
187 
assert_help_string<T: FromArgs>(help_str: &str)188 fn assert_help_string<T: FromArgs>(help_str: &str) {
189     match T::from_args(&["test_arg_0"], &["--help"]) {
190         Ok(_) => panic!("help was parsed as args"),
191         Err(e) => {
192             assert_eq!(help_str, e.output);
193             e.status.expect("help returned an error");
194         }
195     }
196 }
197 
assert_output<T: FromArgs + Debug + PartialEq>(args: &[&str], expected: T)198 fn assert_output<T: FromArgs + Debug + PartialEq>(args: &[&str], expected: T) {
199     let t = T::from_args(&["cmd"], args).expect("failed to parse");
200     assert_eq!(t, expected);
201 }
202 
assert_error<T: FromArgs + Debug>(args: &[&str], err_msg: &str)203 fn assert_error<T: FromArgs + Debug>(args: &[&str], err_msg: &str) {
204     let e = T::from_args(&["cmd"], args).expect_err("unexpectedly succeeded parsing");
205     assert_eq!(err_msg, e.output);
206     e.status.expect_err("error had a positive status");
207 }
208 
209 mod options {
210     use super::*;
211 
212     #[derive(argh::FromArgs, Debug, PartialEq)]
213     /// Woot
214     struct Parsed {
215         #[argh(option, short = 'n')]
216         /// fooey
217         n: usize,
218     }
219 
220     #[test]
parsed()221     fn parsed() {
222         assert_output(&["-n", "5"], Parsed { n: 5 });
223         assert_error::<Parsed>(
224             &["-n", "x"],
225             r###"Error parsing option '-n' with value 'x': invalid digit found in string
226 "###,
227         );
228     }
229 
230     #[derive(argh::FromArgs, Debug, PartialEq)]
231     /// Woot
232     struct Repeating {
233         #[argh(option, short = 'n')]
234         /// fooey
235         n: Vec<String>,
236     }
237 
238     #[test]
repeating()239     fn repeating() {
240         assert_help_string::<Repeating>(
241             r###"Usage: test_arg_0 [-n <n...>]
242 
243 Woot
244 
245 Options:
246   -n, --n           fooey
247   --help            display usage information
248 "###,
249         );
250     }
251 
252     #[derive(argh::FromArgs, Debug, PartialEq)]
253     /// Woot
254     struct WithArgName {
255         #[argh(option, arg_name = "name")]
256         /// fooey
257         option_name: Option<String>,
258     }
259 
260     #[test]
with_arg_name()261     fn with_arg_name() {
262         assert_help_string::<WithArgName>(
263             r###"Usage: test_arg_0 [--option-name <name>]
264 
265 Woot
266 
267 Options:
268   --option-name     fooey
269   --help            display usage information
270 "###,
271         );
272     }
273 }
274 
275 mod positional {
276     use super::*;
277 
278     #[derive(FromArgs, Debug, PartialEq)]
279     /// Woot
280     struct LastRepeating {
281         #[argh(positional)]
282         /// fooey
283         a: u32,
284         #[argh(positional)]
285         /// fooey
286         b: Vec<String>,
287     }
288 
289     #[test]
repeating()290     fn repeating() {
291         assert_output(&["5"], LastRepeating { a: 5, b: vec![] });
292         assert_output(&["5", "foo"], LastRepeating { a: 5, b: vec!["foo".into()] });
293         assert_output(
294             &["5", "foo", "bar"],
295             LastRepeating { a: 5, b: vec!["foo".into(), "bar".into()] },
296         );
297         assert_help_string::<LastRepeating>(
298             r###"Usage: test_arg_0 <a> [<b...>]
299 
300 Woot
301 
302 Positional Arguments:
303   a                 fooey
304   b                 fooey
305 
306 Options:
307   --help            display usage information
308 "###,
309         );
310     }
311 
312     #[derive(FromArgs, Debug, PartialEq)]
313     /// Woot
314     struct LastOptional {
315         #[argh(positional)]
316         /// fooey
317         a: u32,
318         #[argh(positional)]
319         /// fooey
320         b: Option<String>,
321     }
322 
323     #[test]
optional()324     fn optional() {
325         assert_output(&["5"], LastOptional { a: 5, b: None });
326         assert_output(&["5", "6"], LastOptional { a: 5, b: Some("6".into()) });
327         assert_error::<LastOptional>(&["5", "6", "7"], "Unrecognized argument: 7\n");
328     }
329 
330     #[derive(FromArgs, Debug, PartialEq)]
331     /// Woot
332     struct LastDefaulted {
333         #[argh(positional)]
334         /// fooey
335         a: u32,
336         #[argh(positional, default = "5")]
337         /// fooey
338         b: u32,
339     }
340 
341     #[test]
defaulted()342     fn defaulted() {
343         assert_output(&["5"], LastDefaulted { a: 5, b: 5 });
344         assert_output(&["5", "6"], LastDefaulted { a: 5, b: 6 });
345         assert_error::<LastDefaulted>(&["5", "6", "7"], "Unrecognized argument: 7\n");
346     }
347 
348     #[derive(FromArgs, Debug, PartialEq)]
349     /// Woot
350     struct LastRequired {
351         #[argh(positional)]
352         /// fooey
353         a: u32,
354         #[argh(positional)]
355         /// fooey
356         b: u32,
357     }
358 
359     #[test]
required()360     fn required() {
361         assert_output(&["5", "6"], LastRequired { a: 5, b: 6 });
362         assert_error::<LastRequired>(
363             &[],
364             r###"Required positional arguments not provided:
365     a
366     b
367 "###,
368         );
369         assert_error::<LastRequired>(
370             &["5"],
371             r###"Required positional arguments not provided:
372     b
373 "###,
374         );
375     }
376 
377     #[derive(argh::FromArgs, Debug, PartialEq)]
378     /// Woot
379     struct Parsed {
380         #[argh(positional)]
381         /// fooey
382         n: usize,
383     }
384 
385     #[test]
parsed()386     fn parsed() {
387         assert_output(&["5"], Parsed { n: 5 });
388         assert_error::<Parsed>(
389             &["x"],
390             r###"Error parsing positional argument 'n' with value 'x': invalid digit found in string
391 "###,
392         );
393     }
394 
395     #[derive(FromArgs, Debug, PartialEq)]
396     /// Woot
397     struct WithOption {
398         #[argh(positional)]
399         /// fooey
400         a: String,
401         #[argh(option)]
402         /// fooey
403         b: String,
404     }
405 
406     #[test]
mixed_with_option()407     fn mixed_with_option() {
408         assert_output(&["first", "--b", "foo"], WithOption { a: "first".into(), b: "foo".into() });
409 
410         assert_error::<WithOption>(
411             &[],
412             r###"Required positional arguments not provided:
413     a
414 Required options not provided:
415     --b
416 "###,
417         );
418     }
419 
420     #[derive(FromArgs, Debug, PartialEq)]
421     /// Woot
422     struct WithSubcommand {
423         #[argh(positional)]
424         /// fooey
425         a: String,
426         #[argh(subcommand)]
427         /// fooey
428         b: Subcommand,
429         #[argh(positional)]
430         /// fooey
431         c: Vec<String>,
432     }
433 
434     #[derive(FromArgs, Debug, PartialEq)]
435     #[argh(subcommand, name = "a")]
436     /// Subcommand of positional::WithSubcommand.
437     struct Subcommand {
438         #[argh(positional)]
439         /// fooey
440         a: String,
441         #[argh(positional)]
442         /// fooey
443         b: Vec<String>,
444     }
445 
446     #[test]
mixed_with_subcommand()447     fn mixed_with_subcommand() {
448         assert_output(
449             &["first", "a", "a"],
450             WithSubcommand {
451                 a: "first".into(),
452                 b: Subcommand { a: "a".into(), b: vec![] },
453                 c: vec![],
454             },
455         );
456 
457         assert_error::<WithSubcommand>(
458             &["a", "a", "a"],
459             r###"Required positional arguments not provided:
460     a
461 "###,
462         );
463 
464         assert_output(
465             &["1", "2", "3", "a", "b", "c"],
466             WithSubcommand {
467                 a: "1".into(),
468                 b: Subcommand { a: "b".into(), b: vec!["c".into()] },
469                 c: vec!["2".into(), "3".into()],
470             },
471         );
472     }
473 }
474 
475 /// Tests derived from
476 /// https://fuchsia.dev/fuchsia-src/development/api/cli and
477 /// https://fuchsia.dev/fuchsia-src/development/api/cli_help
478 mod fuchsia_commandline_tools_rubric {
479     use super::*;
480 
481     /// Tests for the three required command line argument types:
482     /// - exact text
483     /// - arguments
484     /// - options (i.e. switches and keys)
485     #[test]
three_command_line_argument_types()486     fn three_command_line_argument_types() {
487         // TODO(cramertj) add support for exact text and positional arguments
488     }
489 
490     /// A piece of exact text may be required or optional
491     #[test]
exact_text_required_and_optional()492     fn exact_text_required_and_optional() {
493         // TODO(cramertj) add support for exact text
494     }
495 
496     /// Arguments are like function parameters or slots for data.
497     /// The order often matters.
498     #[test]
arguments_ordered()499     fn arguments_ordered() {
500         // TODO(cramertj) add support for ordered positional arguments
501     }
502 
503     /// If a single argument is repeated, order may not matter, e.g. `<files>...`
504     #[test]
arguments_unordered()505     fn arguments_unordered() {
506         // TODO(cramertj) add support for repeated positional arguments
507     }
508 
509     // Short argument names must use one dash and a single letter.
510     // TODO(cramertj): this should be a compile-fail test
511 
512     // Short argument names are optional, but all choices are required to have a `--` option.
513     // TODO(cramertj): this should be a compile-fail test
514 
515     // Numeric options, such as `-1` and `-2`, are not allowed.
516     // TODO(cramertj): this should be a compile-fail test
517 
518     #[derive(FromArgs)]
519     /// One switch.
520     struct OneSwitch {
521         #[argh(switch, short = 's')]
522         /// just a switch
523         switchy: bool,
524     }
525 
526     /// The presence of a switch means the feature it represents is "on",
527     /// while its absence means that it is "off".
528     #[test]
switch_on_when_present()529     fn switch_on_when_present() {
530         let on = OneSwitch::from_args(&["cmdname"], &["-s"]).expect("parsing on");
531         assert!(on.switchy);
532 
533         let off = OneSwitch::from_args(&["cmdname"], &[]).expect("parsing off");
534         assert!(!off.switchy);
535     }
536 
537     #[derive(FromArgs, Debug)]
538     /// Two Switches
539     struct TwoSwitches {
540         #[argh(switch, short = 'a')]
541         /// a
542         _a: bool,
543         #[argh(switch, short = 'b')]
544         /// b
545         _b: bool,
546     }
547 
548     /// Running switches together is not allowed
549     #[test]
switches_cannot_run_together()550     fn switches_cannot_run_together() {
551         TwoSwitches::from_args(&["cmdname"], &["-a", "-b"])
552             .expect("parsing separate should succeed");
553         TwoSwitches::from_args(&["cmdname"], &["-ab"]).expect_err("parsing together should fail");
554     }
555 
556     #[derive(FromArgs, Debug)]
557     /// One keyed option
558     struct OneOption {
559         #[argh(option)]
560         /// some description
561         _foo: String,
562     }
563 
564     /// Do not use an equals punctuation or similar to separate the key and value.
565     #[test]
keyed_no_equals()566     fn keyed_no_equals() {
567         OneOption::from_args(&["cmdname"], &["--foo", "bar"])
568             .expect("Parsing option value as separate arg should succeed");
569 
570         let e = OneOption::from_args(&["cmdname"], &["--foo=bar"])
571             .expect_err("Parsing option value using `=` should fail");
572         assert_eq!(e.output, "Unrecognized argument: --foo=bar\n");
573         assert!(e.status.is_err());
574     }
575 
576     // Two dashes on their own indicates the end of options.
577     // Subsequent values are given to the tool as-is.
578     //
579     // It's unclear exactly what "are given to the tool as-is" in means in this
580     // context, so we provide a few options for handling `--`, with it being
581     // an error by default.
582     //
583     // TODO(cramertj) implement some behavior for `--`
584 
585     /// Double-dash is treated as an error by default.
586     #[test]
double_dash_default_error()587     fn double_dash_default_error() {}
588 
589     /// Double-dash can be ignored for later manual parsing.
590     #[test]
double_dash_ignore()591     fn double_dash_ignore() {}
592 
593     /// Double-dash should be treated as the end of flags and optional arguments,
594     /// and the remainder of the values should be treated purely as positional arguments,
595     /// even when their syntax matches that of options. e.g. `foo -- -e` should be parsed
596     /// as passing a single positional argument with the value `-e`.
597     #[test]
double_dash_positional()598     fn double_dash_positional() {
599         #[derive(FromArgs, Debug, PartialEq)]
600         /// Positional arguments list
601         struct StringList {
602             #[argh(positional)]
603             /// a list of strings
604             strs: Vec<String>,
605 
606             #[argh(switch)]
607             /// some flag
608             flag: bool,
609         }
610 
611         assert_output(
612             &["--", "a", "-b", "--flag"],
613             StringList { strs: vec!["a".into(), "-b".into(), "--flag".into()], flag: false },
614         );
615         assert_output(
616             &["--flag", "--", "-a", "b"],
617             StringList { strs: vec!["-a".into(), "b".into()], flag: true },
618         );
619         assert_output(&["--", "--help"], StringList { strs: vec!["--help".into()], flag: false });
620         assert_output(
621             &["--", "-a", "--help"],
622             StringList { strs: vec!["-a".into(), "--help".into()], flag: false },
623         );
624     }
625 
626     /// Double-dash can be parsed into an optional field using a provided
627     /// `fn(&[&str]) -> Result<T, EarlyExit>`.
628     #[test]
double_dash_custom()629     fn double_dash_custom() {}
630 
631     /// Repeating switches may be used to apply more emphasis.
632     /// A common example is increasing verbosity by passing more `-v` switches.
633     #[test]
switches_repeating()634     fn switches_repeating() {
635         #[derive(FromArgs, Debug)]
636         /// A type for testing repeating `-v`
637         struct CountVerbose {
638             #[argh(switch, short = 'v')]
639             /// increase the verbosity of the command.
640             verbose: i128,
641         }
642 
643         let cv = CountVerbose::from_args(&["cmdname"], &["-v", "-v", "-v"])
644             .expect("Parsing verbose flags should succeed");
645         assert_eq!(cv.verbose, 3);
646     }
647 
648     // When a tool has many subcommands, it should also have a help subcommand
649     // that displays help about the subcommands, e.g. `fx help build`.
650     //
651     // Elsewhere in the docs, it says the syntax `--help` is required, so we
652     // interpret that to mean:
653     //
654     // - `help` should always be accepted as a "keyword" in place of the first
655     //   positional argument for both the main command and subcommands.
656     //
657     // - If followed by the name of a subcommand it should forward to the
658     //   `--help` of said subcommand, otherwise it will fall back to the
659     //   help of the righmost command / subcommand.
660     //
661     // - `--help` will always consider itself the only meaningful argument to
662     //   the rightmost command / subcommand, and any following arguments will
663     //   be treated as an error.
664 
665     #[derive(FromArgs, Debug)]
666     /// A type for testing `--help`/`help`
667     struct HelpTopLevel {
668         #[argh(subcommand)]
669         _sub: HelpFirstSub,
670     }
671 
672     #[derive(FromArgs, Debug)]
673     #[argh(subcommand, name = "first")]
674     /// First subcommmand for testing `help`.
675     struct HelpFirstSub {
676         #[argh(subcommand)]
677         _sub: HelpSecondSub,
678     }
679 
680     #[derive(FromArgs, Debug)]
681     #[argh(subcommand, name = "second")]
682     /// Second subcommand for testing `help`.
683     struct HelpSecondSub {}
684 
expect_help(args: &[&str], expected_help_string: &str)685     fn expect_help(args: &[&str], expected_help_string: &str) {
686         let e = HelpTopLevel::from_args(&["cmdname"], args).expect_err("should exit early");
687         assert_eq!(expected_help_string, e.output);
688         e.status.expect("help returned an error");
689     }
690 
691     const MAIN_HELP_STRING: &str = r###"Usage: cmdname <command> [<args>]
692 
693 A type for testing `--help`/`help`
694 
695 Options:
696   --help            display usage information
697 
698 Commands:
699   first             First subcommmand for testing `help`.
700 "###;
701 
702     const FIRST_HELP_STRING: &str = r###"Usage: cmdname first <command> [<args>]
703 
704 First subcommmand for testing `help`.
705 
706 Options:
707   --help            display usage information
708 
709 Commands:
710   second            Second subcommand for testing `help`.
711 "###;
712 
713     const SECOND_HELP_STRING: &str = r###"Usage: cmdname first second
714 
715 Second subcommand for testing `help`.
716 
717 Options:
718   --help            display usage information
719 "###;
720 
721     #[test]
help_keyword_main()722     fn help_keyword_main() {
723         expect_help(&["help"], MAIN_HELP_STRING)
724     }
725 
726     #[test]
help_keyword_with_following_subcommand()727     fn help_keyword_with_following_subcommand() {
728         expect_help(&["help", "first"], FIRST_HELP_STRING);
729     }
730 
731     #[test]
help_keyword_between_subcommands()732     fn help_keyword_between_subcommands() {
733         expect_help(&["first", "help", "second"], SECOND_HELP_STRING);
734     }
735 
736     #[test]
help_keyword_with_two_trailing_subcommands()737     fn help_keyword_with_two_trailing_subcommands() {
738         expect_help(&["help", "first", "second"], SECOND_HELP_STRING);
739     }
740 
741     #[test]
help_flag_main()742     fn help_flag_main() {
743         expect_help(&["--help"], MAIN_HELP_STRING);
744     }
745 
746     #[test]
help_flag_subcommand()747     fn help_flag_subcommand() {
748         expect_help(&["first", "--help"], FIRST_HELP_STRING);
749     }
750 
751     #[test]
help_flag_trailing_arguments_are_an_error()752     fn help_flag_trailing_arguments_are_an_error() {
753         let e = OneOption::from_args(&["cmdname"], &["--help", "--foo", "bar"])
754             .expect_err("should exit early");
755         assert_eq!("Trailing arguments are not allowed after `help`.", e.output);
756         e.status.expect_err("should be an error");
757     }
758 
759     #[derive(FromArgs, PartialEq, Debug)]
760     #[argh(
761         description = "Destroy the contents of <file>.",
762         example = "Scribble 'abc' and then run |grind|.\n$ {command_name} -s 'abc' grind old.txt taxes.cp",
763         note = "Use `{command_name} help <command>` for details on [<args>] for a subcommand.",
764         error_code(2, "The blade is too dull."),
765         error_code(3, "Out of fuel.")
766     )]
767     struct HelpExample {
768         /// force, ignore minor errors. This description is so long that it wraps to the next line.
769         #[argh(switch, short = 'f')]
770         force: bool,
771 
772         /// documentation
773         #[argh(switch)]
774         really_really_really_long_name_for_pat: bool,
775 
776         /// write <scribble> repeatedly
777         #[argh(option, short = 's')]
778         scribble: String,
779 
780         /// say more. Defaults to $BLAST_VERBOSE.
781         #[argh(switch, short = 'v')]
782         verbose: bool,
783 
784         #[argh(subcommand)]
785         command: HelpExampleSubCommands,
786     }
787 
788     #[derive(FromArgs, PartialEq, Debug)]
789     #[argh(subcommand)]
790     enum HelpExampleSubCommands {
791         BlowUp(BlowUp),
792         Grind(GrindCommand),
793     }
794 
795     #[derive(FromArgs, PartialEq, Debug)]
796     #[argh(subcommand, name = "blow-up")]
797     /// explosively separate
798     struct BlowUp {
799         /// blow up bombs safely
800         #[argh(switch)]
801         safely: bool,
802     }
803 
804     #[derive(FromArgs, PartialEq, Debug)]
805     #[argh(subcommand, name = "grind", description = "make smaller by many small cuts")]
806     struct GrindCommand {
807         /// wear a visor while grinding
808         #[argh(switch)]
809         safely: bool,
810     }
811 
812     #[test]
example_parses_correctly()813     fn example_parses_correctly() {
814         let help_example = HelpExample::from_args(
815             &["program-name"],
816             &["-f", "--scribble", "fooey", "blow-up", "--safely"],
817         )
818         .unwrap();
819 
820         assert_eq!(
821             help_example,
822             HelpExample {
823                 force: true,
824                 scribble: "fooey".to_string(),
825                 really_really_really_long_name_for_pat: false,
826                 verbose: false,
827                 command: HelpExampleSubCommands::BlowUp(BlowUp { safely: true }),
828             },
829         );
830     }
831 
832     #[test]
example_errors_on_missing_required_option_and_missing_required_subcommand()833     fn example_errors_on_missing_required_option_and_missing_required_subcommand() {
834         let exit = HelpExample::from_args(&["program-name"], &[]).unwrap_err();
835         exit.status.unwrap_err();
836         assert_eq!(
837             exit.output,
838             concat!(
839                 "Required options not provided:\n",
840                 "    --scribble\n",
841                 "One of the following subcommands must be present:\n",
842                 "    help\n",
843                 "    blow-up\n",
844                 "    grind\n",
845             ),
846         );
847     }
848 
849     #[test]
help_example()850     fn help_example() {
851         assert_help_string::<HelpExample>(
852             r###"Usage: test_arg_0 [-f] [--really-really-really-long-name-for-pat] -s <scribble> [-v] <command> [<args>]
853 
854 Destroy the contents of <file>.
855 
856 Options:
857   -f, --force       force, ignore minor errors. This description is so long that
858                     it wraps to the next line.
859   --really-really-really-long-name-for-pat
860                     documentation
861   -s, --scribble    write <scribble> repeatedly
862   -v, --verbose     say more. Defaults to $BLAST_VERBOSE.
863   --help            display usage information
864 
865 Commands:
866   blow-up           explosively separate
867   grind             make smaller by many small cuts
868 
869 Examples:
870   Scribble 'abc' and then run |grind|.
871   $ test_arg_0 -s 'abc' grind old.txt taxes.cp
872 
873 Notes:
874   Use `test_arg_0 help <command>` for details on [<args>] for a subcommand.
875 
876 Error codes:
877   2 The blade is too dull.
878   3 Out of fuel.
879 "###,
880         );
881     }
882 
883     #[allow(dead_code)]
884     #[derive(argh::FromArgs)]
885     /// Destroy the contents of <file>.
886     struct WithArgName {
887         #[argh(positional, arg_name = "name")]
888         username: String,
889     }
890 
891     #[test]
with_arg_name()892     fn with_arg_name() {
893         assert_help_string::<WithArgName>(
894             r###"Usage: test_arg_0 <name>
895 
896 Destroy the contents of <file>.
897 
898 Positional Arguments:
899   name
900 
901 Options:
902   --help            display usage information
903 "###,
904         );
905     }
906 }
907 
908 #[test]
redact_arg_values_no_args()909 fn redact_arg_values_no_args() {
910     #[derive(FromArgs, Debug)]
911     /// Short description
912     struct Cmd {
913         #[argh(option)]
914         /// a msg param
915         _msg: Option<String>,
916     }
917 
918     let actual = Cmd::redact_arg_values(&["program-name"], &[]).unwrap();
919     assert_eq!(actual, &["program-name"]);
920 }
921 
922 #[test]
redact_arg_values_optional_arg()923 fn redact_arg_values_optional_arg() {
924     #[derive(FromArgs, Debug)]
925     /// Short description
926     struct Cmd {
927         #[argh(option)]
928         /// a msg param
929         _msg: Option<String>,
930     }
931 
932     let actual = Cmd::redact_arg_values(&["program-name"], &["--msg", "hello"]).unwrap();
933     assert_eq!(actual, &["program-name", "--msg"]);
934 }
935 
936 #[test]
redact_arg_values_optional_arg_short()937 fn redact_arg_values_optional_arg_short() {
938     #[derive(FromArgs, Debug)]
939     /// Short description
940     struct Cmd {
941         #[argh(option, short = 'm')]
942         /// a msg param
943         _msg: Option<String>,
944     }
945 
946     let actual = Cmd::redact_arg_values(&["program-name"], &["-m", "hello"]).unwrap();
947     assert_eq!(actual, &["program-name", "-m"]);
948 }
949 
950 #[test]
redact_arg_values_optional_arg_long()951 fn redact_arg_values_optional_arg_long() {
952     #[derive(FromArgs, Debug)]
953     /// Short description
954     struct Cmd {
955         #[argh(option, long = "my-msg")]
956         /// a msg param
957         _msg: Option<String>,
958     }
959 
960     let actual = Cmd::redact_arg_values(&["program-name"], &["--my-msg", "hello"]).unwrap();
961     assert_eq!(actual, &["program-name", "--my-msg"]);
962 }
963 
964 #[test]
redact_arg_values_two_option_args()965 fn redact_arg_values_two_option_args() {
966     #[derive(FromArgs, Debug)]
967     /// Short description
968     struct Cmd {
969         #[argh(option)]
970         /// a msg param
971         _msg: String,
972 
973         #[argh(option)]
974         /// a delivery param
975         _delivery: String,
976     }
977 
978     let actual =
979         Cmd::redact_arg_values(&["program-name"], &["--msg", "hello", "--delivery", "next day"])
980             .unwrap();
981     assert_eq!(actual, &["program-name", "--msg", "--delivery"]);
982 }
983 
984 #[test]
redact_arg_values_option_one_optional_args()985 fn redact_arg_values_option_one_optional_args() {
986     #[derive(FromArgs, Debug)]
987     /// Short description
988     struct Cmd {
989         #[argh(option)]
990         /// a msg param
991         _msg: String,
992 
993         #[argh(option)]
994         /// a delivery param
995         _delivery: Option<String>,
996     }
997 
998     let actual =
999         Cmd::redact_arg_values(&["program-name"], &["--msg", "hello", "--delivery", "next day"])
1000             .unwrap();
1001     assert_eq!(actual, &["program-name", "--msg", "--delivery"]);
1002 
1003     let actual = Cmd::redact_arg_values(&["program-name"], &["--msg", "hello"]).unwrap();
1004     assert_eq!(actual, &["program-name", "--msg"]);
1005 }
1006 
1007 #[test]
redact_arg_values_option_repeating()1008 fn redact_arg_values_option_repeating() {
1009     #[derive(FromArgs, Debug)]
1010     /// Short description
1011     struct Cmd {
1012         #[argh(option)]
1013         /// fooey
1014         _msg: Vec<String>,
1015     }
1016 
1017     let actual = Cmd::redact_arg_values(&["program-name"], &[]).unwrap();
1018     assert_eq!(actual, &["program-name"]);
1019 
1020     let actual =
1021         Cmd::redact_arg_values(&["program-name"], &["--msg", "abc", "--msg", "xyz"]).unwrap();
1022     assert_eq!(actual, &["program-name", "--msg", "--msg"]);
1023 }
1024 
1025 #[test]
redact_arg_values_switch()1026 fn redact_arg_values_switch() {
1027     #[derive(FromArgs, Debug)]
1028     /// Short description
1029     struct Cmd {
1030         #[argh(switch, short = 'f')]
1031         /// speed of cmd
1032         _faster: bool,
1033     }
1034 
1035     let actual = Cmd::redact_arg_values(&["program-name"], &["--faster"]).unwrap();
1036     assert_eq!(actual, &["program-name", "--faster"]);
1037 
1038     let actual = Cmd::redact_arg_values(&["program-name"], &["-f"]).unwrap();
1039     assert_eq!(actual, &["program-name", "-f"]);
1040 }
1041 
1042 #[test]
redact_arg_values_positional()1043 fn redact_arg_values_positional() {
1044     #[derive(FromArgs, Debug)]
1045     /// Short description
1046     struct Cmd {
1047         #[allow(unused)]
1048         #[argh(positional)]
1049         /// speed of cmd
1050         speed: u8,
1051     }
1052 
1053     let actual = Cmd::redact_arg_values(&["program-name"], &["5"]).unwrap();
1054     assert_eq!(actual, &["program-name", "speed"]);
1055 }
1056 
1057 #[test]
redact_arg_values_positional_arg_name()1058 fn redact_arg_values_positional_arg_name() {
1059     #[derive(FromArgs, Debug)]
1060     /// Short description
1061     struct Cmd {
1062         #[argh(positional, arg_name = "speed")]
1063         /// speed of cmd
1064         _speed: u8,
1065     }
1066 
1067     let actual = Cmd::redact_arg_values(&["program-name"], &["5"]).unwrap();
1068     assert_eq!(actual, &["program-name", "speed"]);
1069 }
1070 
1071 #[test]
redact_arg_values_positional_repeating()1072 fn redact_arg_values_positional_repeating() {
1073     #[derive(FromArgs, Debug)]
1074     /// Short description
1075     struct Cmd {
1076         #[argh(positional, arg_name = "speed")]
1077         /// speed of cmd
1078         _speed: Vec<u8>,
1079     }
1080 
1081     let actual = Cmd::redact_arg_values(&["program-name"], &["5", "6"]).unwrap();
1082     assert_eq!(actual, &["program-name", "speed", "speed"]);
1083 }
1084 
1085 #[test]
redact_arg_values_positional_err()1086 fn redact_arg_values_positional_err() {
1087     #[derive(FromArgs, Debug)]
1088     /// Short description
1089     struct Cmd {
1090         #[argh(positional, arg_name = "speed")]
1091         /// speed of cmd
1092         _speed: u8,
1093     }
1094 
1095     let actual = Cmd::redact_arg_values(&["program-name"], &[]).unwrap_err();
1096     assert_eq!(
1097         actual,
1098         argh::EarlyExit {
1099             output: "Required positional arguments not provided:\n    speed\n".into(),
1100             status: Err(()),
1101         }
1102     );
1103 }
1104 
1105 #[test]
redact_arg_values_two_positional()1106 fn redact_arg_values_two_positional() {
1107     #[derive(FromArgs, Debug)]
1108     /// Short description
1109     struct Cmd {
1110         #[argh(positional, arg_name = "speed")]
1111         /// speed of cmd
1112         _speed: u8,
1113 
1114         #[argh(positional, arg_name = "direction")]
1115         /// direction
1116         _direction: String,
1117     }
1118 
1119     let actual = Cmd::redact_arg_values(&["program-name"], &["5", "north"]).unwrap();
1120     assert_eq!(actual, &["program-name", "speed", "direction"]);
1121 }
1122 
1123 #[test]
redact_arg_values_positional_option()1124 fn redact_arg_values_positional_option() {
1125     #[derive(FromArgs, Debug)]
1126     /// Short description
1127     struct Cmd {
1128         #[argh(positional, arg_name = "speed")]
1129         /// speed of cmd
1130         _speed: u8,
1131 
1132         #[argh(option)]
1133         /// direction
1134         _direction: String,
1135     }
1136 
1137     let actual = Cmd::redact_arg_values(&["program-name"], &["5", "--direction", "north"]).unwrap();
1138     assert_eq!(actual, &["program-name", "speed", "--direction"]);
1139 }
1140 
1141 #[test]
redact_arg_values_positional_optional_option()1142 fn redact_arg_values_positional_optional_option() {
1143     #[derive(FromArgs, Debug)]
1144     /// Short description
1145     struct Cmd {
1146         #[argh(positional, arg_name = "speed")]
1147         /// speed of cmd
1148         _speed: u8,
1149 
1150         #[argh(option)]
1151         /// direction
1152         _direction: Option<String>,
1153     }
1154 
1155     let actual = Cmd::redact_arg_values(&["program-name"], &["5"]).unwrap();
1156     assert_eq!(actual, &["program-name", "speed"]);
1157 }
1158 
1159 #[test]
redact_arg_values_subcommand()1160 fn redact_arg_values_subcommand() {
1161     #[derive(FromArgs, Debug)]
1162     /// Short description
1163     struct Cmd {
1164         #[argh(positional, arg_name = "speed")]
1165         /// speed of cmd
1166         _speed: u8,
1167 
1168         #[argh(subcommand)]
1169         /// means of transportation
1170         _means: MeansSubcommand,
1171     }
1172 
1173     #[derive(FromArgs, Debug)]
1174     /// Short description
1175     #[argh(subcommand)]
1176     enum MeansSubcommand {
1177         Walking(WalkingSubcommand),
1178         Biking(BikingSubcommand),
1179         Driving(DrivingSubcommand),
1180     }
1181 
1182     #[derive(FromArgs, Debug)]
1183     #[argh(subcommand, name = "walking")]
1184     /// Short description
1185     struct WalkingSubcommand {
1186         #[argh(option)]
1187         /// a song to listen to
1188         _music: String,
1189     }
1190 
1191     #[derive(FromArgs, Debug)]
1192     #[argh(subcommand, name = "biking")]
1193     /// Short description
1194     struct BikingSubcommand {}
1195     #[derive(FromArgs, Debug)]
1196     #[argh(subcommand, name = "driving")]
1197     /// short description
1198     struct DrivingSubcommand {}
1199 
1200     let actual =
1201         Cmd::redact_arg_values(&["program-name"], &["5", "walking", "--music", "Bach"]).unwrap();
1202     assert_eq!(actual, &["program-name", "speed", "walking", "--music"]);
1203 }
1204 
1205 #[test]
redact_arg_values_subcommand_with_space_in_name()1206 fn redact_arg_values_subcommand_with_space_in_name() {
1207     #[derive(FromArgs, Debug)]
1208     /// Short description
1209     struct Cmd {
1210         #[argh(positional, arg_name = "speed")]
1211         /// speed of cmd
1212         _speed: u8,
1213 
1214         #[argh(subcommand)]
1215         /// means of transportation
1216         _means: MeansSubcommand,
1217     }
1218 
1219     #[derive(FromArgs, Debug)]
1220     /// Short description
1221     #[argh(subcommand)]
1222     enum MeansSubcommand {
1223         Walking(WalkingSubcommand),
1224         Biking(BikingSubcommand),
1225     }
1226 
1227     #[derive(FromArgs, Debug)]
1228     #[argh(subcommand, name = "has space")]
1229     /// Short description
1230     struct WalkingSubcommand {
1231         #[argh(option)]
1232         /// a song to listen to
1233         _music: String,
1234     }
1235 
1236     #[derive(FromArgs, Debug)]
1237     #[argh(subcommand, name = "biking")]
1238     /// Short description
1239     struct BikingSubcommand {}
1240 
1241     let actual =
1242         Cmd::redact_arg_values(&["program-name"], &["5", "has space", "--music", "Bach"]).unwrap();
1243     assert_eq!(actual, &["program-name", "speed", "has space", "--music"]);
1244 }
1245 
1246 #[test]
redact_arg_values_produces_help()1247 fn redact_arg_values_produces_help() {
1248     #[derive(argh::FromArgs, Debug, PartialEq)]
1249     /// Woot
1250     struct Repeating {
1251         #[argh(option, short = 'n')]
1252         /// fooey
1253         n: Vec<String>,
1254     }
1255 
1256     assert_eq!(
1257         Repeating::redact_arg_values(&["program-name"], &["--help"]),
1258         Err(argh::EarlyExit {
1259             output: r###"Usage: program-name [-n <n...>]
1260 
1261 Woot
1262 
1263 Options:
1264   -n, --n           fooey
1265   --help            display usage information
1266 "###
1267             .to_string(),
1268             status: Ok(()),
1269         }),
1270     );
1271 }
1272 
1273 #[test]
redact_arg_values_produces_errors_with_bad_arguments()1274 fn redact_arg_values_produces_errors_with_bad_arguments() {
1275     #[derive(argh::FromArgs, Debug, PartialEq)]
1276     /// Woot
1277     struct Cmd {
1278         #[argh(option, short = 'n')]
1279         /// fooey
1280         n: String,
1281     }
1282 
1283     assert_eq!(
1284         Cmd::redact_arg_values(&["program-name"], &["--n"]),
1285         Err(argh::EarlyExit {
1286             output: "No value provided for option '--n'.\n".to_string(),
1287             status: Err(()),
1288         }),
1289     );
1290 }
1291 
1292 #[test]
redact_arg_values_does_not_warn_if_used()1293 fn redact_arg_values_does_not_warn_if_used() {
1294     #[forbid(unused)]
1295     #[derive(FromArgs, Debug)]
1296     /// Short description
1297     struct Cmd {
1298         #[argh(positional)]
1299         /// speed of cmd
1300         speed: u8,
1301     }
1302 
1303     let cmd = Cmd::from_args(&["program-name"], &["5"]).unwrap();
1304     assert_eq!(cmd.speed, 5);
1305 
1306     let actual = Cmd::redact_arg_values(&["program-name"], &["5"]).unwrap();
1307     assert_eq!(actual, &["program-name", "speed"]);
1308 }
1309