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