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