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