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