1 #![cfg(test)]
2 // Copyright (c) 2023 Google LLC All rights reserved.
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file.
5 
6 use argh::{
7     ArgsInfo, CommandInfoWithArgs, ErrorCodeInfo, FlagInfo, FlagInfoKind, FromArgs, Optionality,
8     PositionalInfo, SubCommandInfo,
9 };
10 
assert_args_info<T: ArgsInfo>(expected: &CommandInfoWithArgs)11 fn assert_args_info<T: ArgsInfo>(expected: &CommandInfoWithArgs) {
12     let actual_value = T::get_args_info();
13     assert_eq!(expected, &actual_value)
14 }
15 
16 const HELP_FLAG: FlagInfo<'_> = FlagInfo {
17     kind: FlagInfoKind::Switch,
18     optionality: Optionality::Optional,
19     long: "--help",
20     short: None,
21     description: "display usage information",
22     hidden: false,
23 };
24 
25 /// Tests that exercise the JSON output for help text.
26 #[test]
args_info_test_subcommand()27 fn args_info_test_subcommand() {
28     #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
29     /// Top-level command.
30     struct TopLevel {
31         #[argh(subcommand)]
32         nested: MySubCommandEnum,
33     }
34 
35     #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
36     #[argh(subcommand)]
37     enum MySubCommandEnum {
38         One(SubCommandOne),
39         Two(SubCommandTwo),
40     }
41 
42     #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
43     /// First subcommand.
44     #[argh(subcommand, name = "one")]
45     struct SubCommandOne {
46         #[argh(option)]
47         /// how many x
48         x: usize,
49     }
50 
51     #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
52     /// Second subcommand.
53     #[argh(subcommand, name = "two")]
54     struct SubCommandTwo {
55         #[argh(switch)]
56         /// whether to fooey
57         fooey: bool,
58     }
59 
60     let command_one = CommandInfoWithArgs {
61         name: "one",
62         description: "First subcommand.",
63         flags: &[
64             HELP_FLAG,
65             FlagInfo {
66                 kind: FlagInfoKind::Option { arg_name: "x" },
67                 optionality: Optionality::Required,
68                 long: "--x",
69                 short: None,
70                 description: "how many x",
71                 hidden: false,
72             },
73         ],
74         ..Default::default()
75     };
76 
77     assert_args_info::<TopLevel>(&CommandInfoWithArgs {
78         name: "TopLevel",
79         description: "Top-level command.",
80         examples: &[],
81         flags: &[HELP_FLAG],
82         notes: &[],
83         positionals: &[],
84         error_codes: &[],
85         commands: vec![
86             SubCommandInfo { name: "one", command: command_one.clone() },
87             SubCommandInfo {
88                 name: "two",
89                 command: CommandInfoWithArgs {
90                     name: "two",
91                     description: "Second subcommand.",
92                     flags: &[
93                         HELP_FLAG,
94                         FlagInfo {
95                             kind: FlagInfoKind::Switch,
96                             optionality: Optionality::Optional,
97                             long: "--fooey",
98                             short: None,
99                             description: "whether to fooey",
100                             hidden: false,
101                         },
102                     ],
103                     ..Default::default()
104                 },
105             },
106         ],
107     });
108 
109     assert_args_info::<SubCommandOne>(&command_one);
110 }
111 
112 #[test]
args_info_test_multiline_doc_comment()113 fn args_info_test_multiline_doc_comment() {
114     #[derive(FromArgs, ArgsInfo)]
115     /// Short description
116     struct Cmd {
117         #[argh(switch)]
118         /// a switch with a description
119         /// that is spread across
120         /// a number of
121         /// lines of comments.
122         _s: bool,
123     }
124     assert_args_info::<Cmd>(
125             &CommandInfoWithArgs {
126                 name: "Cmd",
127                 description: "Short description",
128                 flags: &[HELP_FLAG,
129                 FlagInfo {
130                     kind: FlagInfoKind::Switch,
131                     optionality: Optionality::Optional,
132                     long: "--s",
133                     short: None,
134                     description: "a switch with a description that is spread across a number of lines of comments.",
135                     hidden:false
136                 }
137                 ],
138            ..Default::default()
139             });
140 }
141 
142 #[test]
args_info_test_basic_args()143 fn args_info_test_basic_args() {
144     #[allow(dead_code)]
145     #[derive(FromArgs, ArgsInfo)]
146     /// Basic command args demonstrating multiple types and cardinality. "With quotes"
147     struct Basic {
148         /// should the power be on. "Quoted value" should work too.
149         #[argh(switch)]
150         power: bool,
151 
152         /// option that is required because of no default and not Option<>.
153         #[argh(option, long = "required")]
154         required_flag: String,
155 
156         /// optional speed if not specified it is None.
157         #[argh(option, short = 's')]
158         speed: Option<u8>,
159 
160         /// repeatable option.
161         #[argh(option, arg_name = "url")]
162         link: Vec<String>,
163     }
164     assert_args_info::<Basic>(&CommandInfoWithArgs {
165         name: "Basic",
166         description:
167             "Basic command args demonstrating multiple types and cardinality. \"With quotes\"",
168         flags: &[
169             FlagInfo {
170                 kind: FlagInfoKind::Switch,
171                 optionality: Optionality::Optional,
172                 long: "--help",
173                 short: None,
174                 description: "display usage information",
175                 hidden: false,
176             },
177             FlagInfo {
178                 kind: FlagInfoKind::Switch,
179                 optionality: Optionality::Optional,
180                 long: "--power",
181                 short: None,
182                 description: "should the power be on. \"Quoted value\" should work too.",
183                 hidden: false,
184             },
185             FlagInfo {
186                 kind: FlagInfoKind::Option { arg_name: "required" },
187                 optionality: Optionality::Required,
188                 long: "--required",
189                 short: None,
190                 description: "option that is required because of no default and not Option<>.",
191                 hidden: false,
192             },
193             FlagInfo {
194                 kind: FlagInfoKind::Option { arg_name: "speed" },
195                 optionality: Optionality::Optional,
196                 long: "--speed",
197                 short: Some('s'),
198                 description: "optional speed if not specified it is None.",
199                 hidden: false,
200             },
201             FlagInfo {
202                 kind: FlagInfoKind::Option { arg_name: "url" },
203                 optionality: Optionality::Repeating,
204                 long: "--link",
205                 short: None,
206                 description: "repeatable option.",
207                 hidden: false,
208             },
209         ],
210         ..Default::default()
211     });
212 }
213 
214 #[test]
args_info_test_positional_args()215 fn args_info_test_positional_args() {
216     #[allow(dead_code)]
217     #[derive(FromArgs, ArgsInfo)]
218     /// Command with positional args demonstrating. "With quotes"
219     struct Positional {
220         /// the "root" position.
221         #[argh(positional, arg_name = "root")]
222         root_value: String,
223 
224         /// trunk value
225         #[argh(positional)]
226         trunk: String,
227 
228         /// leaves. There can be many leaves.
229         #[argh(positional)]
230         leaves: Vec<String>,
231     }
232     assert_args_info::<Positional>(&CommandInfoWithArgs {
233         name: "Positional",
234         description: "Command with positional args demonstrating. \"With quotes\"",
235         flags: &[HELP_FLAG],
236         positionals: &[
237             PositionalInfo {
238                 name: "root",
239                 description: "the \"root\" position.",
240                 optionality: Optionality::Required,
241                 hidden: false,
242             },
243             PositionalInfo {
244                 name: "trunk",
245                 description: "trunk value",
246                 optionality: Optionality::Required,
247                 hidden: false,
248             },
249             PositionalInfo {
250                 name: "leaves",
251                 description: "leaves. There can be many leaves.",
252                 optionality: Optionality::Repeating,
253                 hidden: false,
254             },
255         ],
256 
257         ..Default::default()
258     });
259 }
260 
261 #[test]
args_info_test_optional_positional_args()262 fn args_info_test_optional_positional_args() {
263     #[allow(dead_code)]
264     #[derive(FromArgs, ArgsInfo)]
265     /// Command with positional args demonstrating last value is optional
266     struct Positional {
267         /// the "root" position.
268         #[argh(positional, arg_name = "root")]
269         root_value: String,
270 
271         /// trunk value
272         #[argh(positional)]
273         trunk: String,
274 
275         /// leaves. There can be an optional leaves.
276         #[argh(positional)]
277         leaves: Option<String>,
278     }
279     assert_args_info::<Positional>(&CommandInfoWithArgs {
280         name: "Positional",
281         description: "Command with positional args demonstrating last value is optional",
282         flags: &[FlagInfo {
283             kind: FlagInfoKind::Switch,
284             optionality: Optionality::Optional,
285             long: "--help",
286             short: None,
287             description: "display usage information",
288             hidden: false,
289         }],
290         positionals: &[
291             PositionalInfo {
292                 name: "root",
293                 description: "the \"root\" position.",
294                 optionality: Optionality::Required,
295                 hidden: false,
296             },
297             PositionalInfo {
298                 name: "trunk",
299                 description: "trunk value",
300                 optionality: Optionality::Required,
301                 hidden: false,
302             },
303             PositionalInfo {
304                 name: "leaves",
305                 description: "leaves. There can be an optional leaves.",
306                 optionality: Optionality::Optional,
307                 hidden: false,
308             },
309         ],
310 
311         ..Default::default()
312     });
313 }
314 
315 #[test]
args_info_test_default_positional_args()316 fn args_info_test_default_positional_args() {
317     #[allow(dead_code)]
318     #[derive(FromArgs, ArgsInfo)]
319     /// Command with positional args demonstrating last value is defaulted.
320     struct Positional {
321         /// the "root" position.
322         #[argh(positional, arg_name = "root")]
323         root_value: String,
324 
325         /// trunk value
326         #[argh(positional)]
327         trunk: String,
328 
329         /// leaves. There can be one leaf, defaults to hello.
330         #[argh(positional, default = "String::from(\"hello\")")]
331         leaves: String,
332     }
333     assert_args_info::<Positional>(&CommandInfoWithArgs {
334         name: "Positional",
335         description: "Command with positional args demonstrating last value is defaulted.",
336         flags: &[HELP_FLAG],
337         positionals: &[
338             PositionalInfo {
339                 name: "root",
340                 description: "the \"root\" position.",
341                 optionality: Optionality::Required,
342                 hidden: false,
343             },
344             PositionalInfo {
345                 name: "trunk",
346                 description: "trunk value",
347                 optionality: Optionality::Required,
348                 hidden: false,
349             },
350             PositionalInfo {
351                 name: "leaves",
352                 description: "leaves. There can be one leaf, defaults to hello.",
353                 optionality: Optionality::Optional,
354                 hidden: false,
355             },
356         ],
357 
358         ..Default::default()
359     });
360 }
361 
362 #[test]
args_info_test_notes_examples_errors()363 fn args_info_test_notes_examples_errors() {
364     #[allow(dead_code)]
365     #[derive(FromArgs, ArgsInfo)]
366     /// Command with Examples and usage Notes, including error codes.
367     #[argh(
368         note = r##"
369     These usage notes appear for {command_name} and how to best use it.
370     The formatting should be preserved.
371     one
372     two
373     three then a blank
374 
375     and one last line with "quoted text"."##,
376         example = r##"
377     Use the command with 1 file:
378     `{command_name} /path/to/file`
379     Use it with a "wildcard":
380     `{command_name} /path/to/*`
381      a blank line
382 
383     and one last line with "quoted text"."##,
384         error_code(0, "Success"),
385         error_code(1, "General Error"),
386         error_code(2, "Some error with \"quotes\"")
387     )]
388     struct NotesExamplesErrors {
389         /// the "root" position.
390         #[argh(positional, arg_name = "files")]
391         fields: Vec<std::path::PathBuf>,
392     }
393     assert_args_info::<NotesExamplesErrors>(
394             &CommandInfoWithArgs {
395                 name: "NotesExamplesErrors",
396                 description: "Command with Examples and usage Notes, including error codes.",
397                 examples: &["\n    Use the command with 1 file:\n    `{command_name} /path/to/file`\n    Use it with a \"wildcard\":\n    `{command_name} /path/to/*`\n     a blank line\n    \n    and one last line with \"quoted text\"."],
398                 flags: &[HELP_FLAG
399                 ],
400                 positionals: &[
401                     PositionalInfo{
402                         name: "files",
403                         description: "the \"root\" position.",
404                         optionality: Optionality::Repeating,
405                         hidden:false
406                     }
407                 ],
408                 notes: &["\n    These usage notes appear for {command_name} and how to best use it.\n    The formatting should be preserved.\n    one\n    two\n    three then a blank\n    \n    and one last line with \"quoted text\"."],
409                 error_codes: & [ErrorCodeInfo { code: 0, description: "Success" }, ErrorCodeInfo { code: 1, description: "General Error" }, ErrorCodeInfo { code: 2, description: "Some error with \"quotes\"" }],
410                 ..Default::default()
411             });
412 }
413 
414 #[test]
args_info_test_subcommands()415 fn args_info_test_subcommands() {
416     #[allow(dead_code)]
417     #[derive(FromArgs, ArgsInfo)]
418     ///Top level command with "subcommands".
419     struct TopLevel {
420         /// show verbose output
421         #[argh(switch)]
422         verbose: bool,
423 
424         /// this doc comment does not appear anywhere.
425         #[argh(subcommand)]
426         cmd: SubcommandEnum,
427     }
428 
429     #[derive(FromArgs, ArgsInfo)]
430     #[argh(subcommand)]
431     /// Doc comments for subcommand enums does not appear in the help text.
432     enum SubcommandEnum {
433         Command1(Command1Args),
434         Command2(Command2Args),
435         Command3(Command3Args),
436     }
437 
438     /// Command1 args are used for Command1.
439     #[allow(dead_code)]
440     #[derive(FromArgs, ArgsInfo)]
441     #[argh(subcommand, name = "one")]
442     struct Command1Args {
443         /// the "root" position.
444         #[argh(positional, arg_name = "root")]
445         root_value: String,
446 
447         /// trunk value
448         #[argh(positional)]
449         trunk: String,
450 
451         /// leaves. There can be zero leaves, defaults to hello.
452         #[argh(positional, default = "String::from(\"hello\")")]
453         leaves: String,
454     }
455     /// Command2 args are used for Command2.
456     #[allow(dead_code)]
457     #[derive(FromArgs, ArgsInfo)]
458     #[argh(subcommand, name = "two")]
459     struct Command2Args {
460         /// should the power be on. "Quoted value" should work too.
461         #[argh(switch)]
462         power: bool,
463 
464         /// option that is required because of no default and not Option<>.
465         #[argh(option, long = "required")]
466         required_flag: String,
467 
468         /// optional speed if not specified it is None.
469         #[argh(option, short = 's')]
470         speed: Option<u8>,
471 
472         /// repeatable option.
473         #[argh(option, arg_name = "url")]
474         link: Vec<String>,
475     }
476     /// Command3 args are used for Command3 which has no options or arguments.
477     #[derive(FromArgs, ArgsInfo)]
478     #[argh(subcommand, name = "three")]
479     struct Command3Args {}
480 
481     assert_args_info::<TopLevel>(&CommandInfoWithArgs {
482         name: "TopLevel",
483         description: "Top level command with \"subcommands\".",
484         flags: &[
485             HELP_FLAG,
486             FlagInfo {
487                 kind: FlagInfoKind::Switch,
488                 optionality: Optionality::Optional,
489                 long: "--verbose",
490                 short: None,
491                 description: "show verbose output",
492                 hidden: false,
493             },
494         ],
495         positionals: &[],
496         commands: vec![
497             SubCommandInfo {
498                 name: "one",
499                 command: CommandInfoWithArgs {
500                     name: "one",
501                     description: "Command1 args are used for Command1.",
502                     flags: &[HELP_FLAG],
503                     positionals: &[
504                         PositionalInfo {
505                             name: "root",
506                             description: "the \"root\" position.",
507                             optionality: Optionality::Required,
508                             hidden: false,
509                         },
510                         PositionalInfo {
511                             name: "trunk",
512                             description: "trunk value",
513                             optionality: Optionality::Required,
514                             hidden: false,
515                         },
516                         PositionalInfo {
517                             name: "leaves",
518                             description: "leaves. There can be zero leaves, defaults to hello.",
519                             optionality: Optionality::Optional,
520                             hidden: false,
521                         },
522                     ],
523                     ..Default::default()
524                 },
525             },
526             SubCommandInfo {
527                 name: "two",
528                 command: CommandInfoWithArgs {
529                     name: "two",
530                     description: "Command2 args are used for Command2.",
531                     flags: &[
532                         HELP_FLAG,
533                         FlagInfo {
534                             kind: FlagInfoKind::Switch,
535                             optionality: Optionality::Optional,
536                             long: "--power",
537                             short: None,
538                             description:
539                                 "should the power be on. \"Quoted value\" should work too.",
540                             hidden: false,
541                         },
542                         FlagInfo {
543                             kind: FlagInfoKind::Option { arg_name: "required" },
544                             optionality: Optionality::Required,
545                             long: "--required",
546                             short: None,
547                             description:
548                                 "option that is required because of no default and not Option<>.",
549                             hidden: false,
550                         },
551                         FlagInfo {
552                             kind: FlagInfoKind::Option { arg_name: "speed" },
553                             optionality: Optionality::Optional,
554                             long: "--speed",
555                             short: Some('s'),
556                             description: "optional speed if not specified it is None.",
557                             hidden: false,
558                         },
559                         FlagInfo {
560                             kind: FlagInfoKind::Option { arg_name: "url" },
561                             optionality: Optionality::Repeating,
562                             long: "--link",
563                             short: None,
564                             description: "repeatable option.",
565                             hidden: false,
566                         },
567                     ],
568                     ..Default::default()
569                 },
570             },
571             SubCommandInfo {
572                 name: "three",
573                 command: CommandInfoWithArgs {
574                     name: "three",
575                     description:
576                         "Command3 args are used for Command3 which has no options or arguments.",
577                     flags: &[HELP_FLAG],
578                     positionals: &[],
579                     ..Default::default()
580                 },
581             },
582         ],
583         ..Default::default()
584     });
585 }
586 
587 #[test]
args_info_test_subcommand_notes_examples()588 fn args_info_test_subcommand_notes_examples() {
589     #[allow(dead_code)]
590     #[derive(FromArgs, ArgsInfo)]
591     ///Top level command with "subcommands".
592     #[argh(
593         note = "Top level note",
594         example = "Top level example",
595         error_code(0, "Top level success")
596     )]
597     struct TopLevel {
598         /// this doc comment does not appear anywhere.
599         #[argh(subcommand)]
600         cmd: SubcommandEnum,
601     }
602 
603     #[derive(FromArgs, ArgsInfo)]
604     #[argh(subcommand)]
605     /// Doc comments for subcommand enums does not appear in the help text.
606     enum SubcommandEnum {
607         Command1(Command1Args),
608     }
609 
610     /// Command1 args are used for subcommand one.
611     #[allow(dead_code)]
612     #[derive(FromArgs, ArgsInfo)]
613     #[argh(
614         subcommand,
615         name = "one",
616         note = "{command_name} is used as a subcommand of \"Top level\"",
617         example = "\"Typical\" usage is `{command_name}`.",
618         error_code(0, "one level success")
619     )]
620     struct Command1Args {
621         /// the "root" position.
622         #[argh(positional, arg_name = "root")]
623         root_value: String,
624 
625         /// trunk value
626         #[argh(positional)]
627         trunk: String,
628 
629         /// leaves. There can be many leaves.
630         #[argh(positional)]
631         leaves: Vec<String>,
632     }
633 
634     let command_one = CommandInfoWithArgs {
635         name: "one",
636         description: "Command1 args are used for subcommand one.",
637         error_codes: &[ErrorCodeInfo { code: 0, description: "one level success" }],
638         examples: &["\"Typical\" usage is `{command_name}`."],
639         flags: &[HELP_FLAG],
640         notes: &["{command_name} is used as a subcommand of \"Top level\""],
641         positionals: &[
642             PositionalInfo {
643                 name: "root",
644                 description: "the \"root\" position.",
645                 optionality: Optionality::Required,
646                 hidden: false,
647             },
648             PositionalInfo {
649                 name: "trunk",
650                 description: "trunk value",
651                 optionality: Optionality::Required,
652                 hidden: false,
653             },
654             PositionalInfo {
655                 name: "leaves",
656                 description: "leaves. There can be many leaves.",
657                 optionality: Optionality::Repeating,
658                 hidden: false,
659             },
660         ],
661         ..Default::default()
662     };
663 
664     assert_args_info::<TopLevel>(&CommandInfoWithArgs {
665         name: "TopLevel",
666         description: "Top level command with \"subcommands\".",
667         error_codes: &[ErrorCodeInfo { code: 0, description: "Top level success" }],
668         examples: &["Top level example"],
669         flags: &[HELP_FLAG],
670         notes: &["Top level note"],
671         commands: vec![SubCommandInfo { name: "one", command: command_one.clone() }],
672         ..Default::default()
673     });
674 
675     assert_args_info::<Command1Args>(&command_one);
676 }
677 
678 #[test]
args_info_test_example()679 fn args_info_test_example() {
680     #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
681     #[argh(
682         description = "Destroy the contents of <file> with a specific \"method of destruction\".",
683         example = "Scribble 'abc' and then run |grind|.\n$ {command_name} -s 'abc' grind old.txt taxes.cp",
684         note = "Use `{command_name} help <command>` for details on [<args>] for a subcommand.",
685         error_code(2, "The blade is too dull."),
686         error_code(3, "Out of fuel.")
687     )]
688     struct HelpExample {
689         /// force, ignore minor errors. This description is so long that it wraps to the next line.
690         #[argh(switch, short = 'f')]
691         force: bool,
692 
693         /// documentation
694         #[argh(switch)]
695         really_really_really_long_name_for_pat: bool,
696 
697         /// write <scribble> repeatedly
698         #[argh(option, short = 's')]
699         scribble: String,
700 
701         /// say more. Defaults to $BLAST_VERBOSE.
702         #[argh(switch, short = 'v')]
703         verbose: bool,
704 
705         #[argh(subcommand)]
706         command: HelpExampleSubCommands,
707     }
708 
709     #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
710     #[argh(subcommand)]
711     enum HelpExampleSubCommands {
712         BlowUp(BlowUp),
713         Grind(GrindCommand),
714     }
715 
716     #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
717     #[argh(subcommand, name = "blow-up")]
718     /// explosively separate
719     struct BlowUp {
720         /// blow up bombs safely
721         #[argh(switch)]
722         safely: bool,
723     }
724 
725     #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
726     #[argh(subcommand, name = "grind", description = "make smaller by many small cuts")]
727     struct GrindCommand {
728         /// wear a visor while grinding
729         #[argh(switch)]
730         safely: bool,
731     }
732 
733     assert_args_info::<HelpExample>(
734             &CommandInfoWithArgs {
735                 name: "HelpExample",
736                 description: "Destroy the contents of <file> with a specific \"method of destruction\".",
737                 examples: &["Scribble 'abc' and then run |grind|.\n$ {command_name} -s 'abc' grind old.txt taxes.cp"],
738                 flags: &[HELP_FLAG,
739                 FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--force", short: Some('f'), description: "force, ignore minor errors. This description is so long that it wraps to the next line.",
740                 hidden:false },
741                 FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--really-really-really-long-name-for-pat", short: None, description: "documentation",
742                 hidden:false },
743                 FlagInfo { kind: FlagInfoKind::Option { arg_name: "scribble"},
744                  optionality: Optionality::Required, long: "--scribble", short: Some('s'), description: "write <scribble> repeatedly",
745                  hidden:false },
746                   FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--verbose", short: Some('v'), description: "say more. Defaults to $BLAST_VERBOSE.",
747                   hidden:false }
748                 ],
749                 notes: &["Use `{command_name} help <command>` for details on [<args>] for a subcommand."],
750                 commands: vec![
751                     SubCommandInfo { name: "blow-up",
752                  command: CommandInfoWithArgs { name: "blow-up",
753                   description: "explosively separate",
754                   flags:& [HELP_FLAG,
755                    FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--safely", short: None, description: "blow up bombs safely",
756                    hidden:false }
757                    ],
758                 ..Default::default()
759              } },
760               SubCommandInfo {
761                  name: "grind",
762                  command: CommandInfoWithArgs {
763                      name: "grind",
764                      description: "make smaller by many small cuts",
765                      flags: &[HELP_FLAG,
766                       FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--safely", short: None, description: "wear a visor while grinding" ,hidden:false}],
767                       ..Default::default()
768                      }
769                 }],
770                 error_codes: &[ErrorCodeInfo { code: 2, description: "The blade is too dull." }, ErrorCodeInfo { code: 3, description: "Out of fuel." }],
771                 ..Default::default()
772             }
773             );
774 }
775 
776 #[test]
positional_greedy()777 fn positional_greedy() {
778     #[allow(dead_code)]
779     #[derive(FromArgs, ArgsInfo)]
780     /// Woot
781     struct LastRepeatingGreedy {
782         #[argh(positional)]
783         /// fooey
784         pub a: u32,
785         #[argh(switch)]
786         /// woo
787         pub b: bool,
788         #[argh(option)]
789         /// stuff
790         pub c: Option<String>,
791         #[argh(positional, greedy)]
792         /// fooey
793         pub d: Vec<String>,
794     }
795     assert_args_info::<LastRepeatingGreedy>(&CommandInfoWithArgs {
796         name: "LastRepeatingGreedy",
797         description: "Woot",
798         flags: &[
799             HELP_FLAG,
800             FlagInfo {
801                 kind: FlagInfoKind::Switch,
802                 optionality: Optionality::Optional,
803                 long: "--b",
804                 short: None,
805                 description: "woo",
806                 hidden: false,
807             },
808             FlagInfo {
809                 kind: FlagInfoKind::Option { arg_name: "c" },
810                 optionality: Optionality::Optional,
811                 long: "--c",
812                 short: None,
813                 description: "stuff",
814                 hidden: false,
815             },
816         ],
817         positionals: &[
818             PositionalInfo {
819                 name: "a",
820                 description: "fooey",
821                 optionality: Optionality::Required,
822                 hidden: false,
823             },
824             PositionalInfo {
825                 name: "d",
826                 description: "fooey",
827                 optionality: Optionality::Greedy,
828                 hidden: false,
829             },
830         ],
831         ..Default::default()
832     });
833 }
834 
835 #[test]
hidden_help_attribute()836 fn hidden_help_attribute() {
837     #[derive(FromArgs, ArgsInfo)]
838     /// Short description
839     struct Cmd {
840         /// this one should be hidden
841         #[argh(positional, hidden_help)]
842         _one: String,
843         #[argh(positional)]
844         /// this one is real
845         _two: String,
846         /// this one should be hidden
847         #[argh(option, hidden_help)]
848         _three: String,
849     }
850 
851     assert_args_info::<Cmd>(&CommandInfoWithArgs {
852         name: "Cmd",
853         description: "Short description",
854         flags: &[
855             HELP_FLAG,
856             FlagInfo {
857                 kind: FlagInfoKind::Option { arg_name: "three" },
858                 optionality: Optionality::Required,
859                 long: "--three",
860                 short: None,
861                 description: "this one should be hidden",
862                 hidden: true,
863             },
864         ],
865         positionals: &[
866             PositionalInfo {
867                 name: "one",
868                 description: "this one should be hidden",
869                 optionality: Optionality::Required,
870                 hidden: true,
871             },
872             PositionalInfo {
873                 name: "two",
874                 description: "this one is real",
875                 optionality: Optionality::Required,
876                 hidden: false,
877             },
878         ],
879         ..Default::default()
880     });
881 }
882 
883 #[test]
test_dynamic_subcommand()884 fn test_dynamic_subcommand() {
885     #[derive(PartialEq, Debug)]
886     struct DynamicSubCommandImpl {
887         got: String,
888     }
889 
890     impl argh::DynamicSubCommand for DynamicSubCommandImpl {
891         fn commands() -> &'static [&'static argh::CommandInfo] {
892             &[
893                 &argh::CommandInfo { name: "three", description: "Third command" },
894                 &argh::CommandInfo { name: "four", description: "Fourth command" },
895                 &argh::CommandInfo { name: "five", description: "Fifth command" },
896             ]
897         }
898 
899         fn try_redact_arg_values(
900             _command_name: &[&str],
901             _args: &[&str],
902         ) -> Option<Result<Vec<String>, argh::EarlyExit>> {
903             Some(Err(argh::EarlyExit::from("Test should not redact".to_owned())))
904         }
905 
906         fn try_from_args(
907             command_name: &[&str],
908             args: &[&str],
909         ) -> Option<Result<DynamicSubCommandImpl, argh::EarlyExit>> {
910             let command_name = match command_name.last() {
911                 Some(x) => *x,
912                 None => return Some(Err(argh::EarlyExit::from("No command".to_owned()))),
913             };
914             let description = Self::commands().iter().find(|x| x.name == command_name)?.description;
915             if args.len() > 1 {
916                 Some(Err(argh::EarlyExit::from("Too many arguments".to_owned())))
917             } else if let Some(arg) = args.first() {
918                 Some(Ok(DynamicSubCommandImpl { got: format!("{} got {:?}", description, arg) }))
919             } else {
920                 Some(Err(argh::EarlyExit::from("Not enough arguments".to_owned())))
921             }
922         }
923     }
924 
925     #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
926     /// Top-level command.
927     struct TopLevel {
928         #[argh(subcommand)]
929         nested: MySubCommandEnum,
930     }
931 
932     #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
933     #[argh(subcommand)]
934     enum MySubCommandEnum {
935         One(SubCommandOne),
936         Two(SubCommandTwo),
937         #[argh(dynamic)]
938         ThreeFourFive(DynamicSubCommandImpl),
939     }
940 
941     #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
942     /// First subcommand.
943     #[argh(subcommand, name = "one")]
944     struct SubCommandOne {
945         #[argh(option)]
946         /// how many x
947         x: usize,
948     }
949 
950     #[derive(FromArgs, ArgsInfo, PartialEq, Debug)]
951     /// Second subcommand.
952     #[argh(subcommand, name = "two")]
953     struct SubCommandTwo {
954         #[argh(switch)]
955         /// whether to fooey
956         fooey: bool,
957     }
958 
959     assert_args_info::<TopLevel>(&CommandInfoWithArgs {
960         name: "TopLevel",
961         description: "Top-level command.",
962         flags: &[HELP_FLAG],
963         commands: vec![
964             SubCommandInfo {
965                 name: "one",
966                 command: CommandInfoWithArgs {
967                     name: "one",
968                     description: "First subcommand.",
969                     flags: &[
970                         HELP_FLAG,
971                         FlagInfo {
972                             kind: FlagInfoKind::Option { arg_name: "x" },
973                             optionality: Optionality::Required,
974                             long: "--x",
975                             short: None,
976                             description: "how many x",
977                             hidden: false,
978                         },
979                     ],
980                     ..Default::default()
981                 },
982             },
983             SubCommandInfo {
984                 name: "two",
985                 command: CommandInfoWithArgs {
986                     name: "two",
987                     description: "Second subcommand.",
988                     flags: &[
989                         HELP_FLAG,
990                         FlagInfo {
991                             kind: FlagInfoKind::Switch,
992                             optionality: Optionality::Optional,
993                             long: "--fooey",
994                             short: None,
995                             description: "whether to fooey",
996                             hidden: false,
997                         },
998                     ],
999                     ..Default::default()
1000                 },
1001             },
1002             SubCommandInfo {
1003                 name: "three",
1004                 command: CommandInfoWithArgs {
1005                     name: "three",
1006                     description: "Third command",
1007                     ..Default::default()
1008                 },
1009             },
1010             SubCommandInfo {
1011                 name: "four",
1012                 command: CommandInfoWithArgs {
1013                     name: "four",
1014                     description: "Fourth command",
1015                     ..Default::default()
1016                 },
1017             },
1018             SubCommandInfo {
1019                 name: "five",
1020                 command: CommandInfoWithArgs {
1021                     name: "five",
1022                     description: "Fifth command",
1023                     ..Default::default()
1024                 },
1025             },
1026         ],
1027         ..Default::default()
1028     })
1029 }
1030