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