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