1use std::collections::BTreeMap;
5use std::{env, fmt, result};
6
7pub type Result<T> = result::Result<T, UtilsArgParserError>;
8
9const ARG_PREFIX: &str = "--";
10const ARG_SEPARATOR: &str = "--";
11const HELP_ARG: &str = "--help";
12const SHORT_HELP_ARG: &str = "-h";
13const VERSION_ARG: &str = "--version";
14
15#[derive(Debug, PartialEq, Eq, thiserror::Error, displaydoc::Display)]
17pub enum UtilsArgParserError {
18 ForbiddenArgument(String, String),
20 MissingArgument(String),
22 MissingValue(String),
24 UnexpectedArgument(String),
26 DuplicateArgument(String),
28}
29
30#[derive(Debug, Clone, Default)]
32pub struct ArgParser<'a> {
33 arguments: Arguments<'a>,
34}
35
36impl<'a> ArgParser<'a> {
37 pub fn new() -> Self {
39 ArgParser::default()
40 }
41
42 pub fn arg(mut self, argument: Argument<'a>) -> Self {
44 self.arguments.insert_arg(argument);
45 self
46 }
47
48 pub fn parse_from_cmdline(&mut self) -> Result<()> {
50 self.arguments.parse_from_cmdline()
51 }
52
53 pub fn formatted_help(&self) -> String {
57 let mut help_builder = vec![];
58
59 let required_arguments = self.format_arguments(true);
60 if !required_arguments.is_empty() {
61 help_builder.push("required arguments:".to_string());
62 help_builder.push(required_arguments);
63 }
64
65 let optional_arguments = self.format_arguments(false);
66 if !optional_arguments.is_empty() {
67 if !help_builder.is_empty() {
69 help_builder.push("".to_string());
70 }
71
72 help_builder.push("optional arguments:".to_string());
73 help_builder.push(optional_arguments);
74 }
75
76 help_builder.join("\n")
77 }
78
79 pub fn arguments(&self) -> &Arguments<'_> {
81 &self.arguments
82 }
83
84 fn format_arguments(&self, is_required: bool) -> String {
87 let filtered_arguments = self
88 .arguments
89 .args
90 .values()
91 .filter(|arg| is_required == arg.required)
92 .collect::<Vec<_>>();
93
94 let max_arg_width = filtered_arguments
95 .iter()
96 .map(|arg| arg.format_name().len())
97 .max()
98 .unwrap_or(0);
99
100 filtered_arguments
101 .into_iter()
102 .map(|arg| arg.format_help(max_arg_width))
103 .collect::<Vec<_>>()
104 .join("\n")
105 }
106}
107
108#[derive(Clone, Debug, PartialEq, Eq)]
110pub struct Argument<'a> {
111 name: &'a str,
112 required: bool,
113 requires: Option<&'a str>,
114 forbids: Vec<&'a str>,
115 takes_value: bool,
116 allow_multiple: bool,
117 default_value: Option<Value>,
118 help: Option<&'a str>,
119 user_value: Option<Value>,
120}
121
122impl<'a> Argument<'a> {
123 pub fn new(name: &'a str) -> Argument<'a> {
125 Argument {
126 name,
127 required: false,
128 requires: None,
129 forbids: vec![],
130 takes_value: false,
131 allow_multiple: false,
132 default_value: None,
133 help: None,
134 user_value: None,
135 }
136 }
137
138 pub fn required(mut self, required: bool) -> Self {
140 self.required = required;
141 self
142 }
143
144 pub fn requires(mut self, other_arg: &'a str) -> Self {
146 self.requires = Some(other_arg);
147 self
148 }
149
150 pub fn forbids(mut self, args: Vec<&'a str>) -> Self {
152 self.forbids = args;
153 self
154 }
155
156 pub fn takes_value(mut self, takes_value: bool) -> Self {
159 self.takes_value = takes_value;
160 self
161 }
162
163 pub fn allow_multiple(mut self, allow_multiple: bool) -> Self {
167 if allow_multiple {
168 self.takes_value = true;
169 }
170 self.allow_multiple = allow_multiple;
171 self
172 }
173
174 pub fn default_value(mut self, default_value: &'a str) -> Self {
177 self.default_value = Some(Value::Single(String::from(default_value)));
178 self
179 }
180
181 pub fn help(mut self, help: &'a str) -> Self {
184 self.help = Some(help);
185 self
186 }
187
188 fn format_help(&self, arg_width: usize) -> String {
189 let mut help_builder = vec![];
190
191 let arg = self.format_name();
192 help_builder.push(format!("{:<arg_width$}", arg, arg_width = arg_width));
193
194 help_builder.push(" ".to_string());
196
197 match (self.help, &self.default_value) {
198 (Some(help), Some(default_value)) => {
199 help_builder.push(format!("{} [default: {}]", help, default_value))
200 }
201 (Some(help), None) => help_builder.push(help.to_string()),
202 (None, Some(default_value)) => {
203 help_builder.push(format!("[default: {}]", default_value))
204 }
205 (None, None) => (),
206 };
207
208 help_builder.concat()
209 }
210
211 fn format_name(&self) -> String {
212 if self.takes_value {
213 format!(" --{name} <{name}>", name = self.name)
214 } else {
215 format!(" --{}", self.name)
216 }
217 }
218}
219
220#[derive(Clone, Debug, PartialEq, Eq)]
222pub enum Value {
223 Flag,
224 Single(String),
225 Multiple(Vec<String>),
226}
227
228impl Value {
229 fn as_single_value(&self) -> Option<&String> {
230 match self {
231 Value::Single(s) => Some(s),
232 _ => None,
233 }
234 }
235
236 fn as_flag(&self) -> bool {
237 matches!(self, Value::Flag)
238 }
239
240 fn as_multiple(&self) -> Option<&[String]> {
241 match self {
242 Value::Multiple(v) => Some(v),
243 _ => None,
244 }
245 }
246}
247
248impl fmt::Display for Value {
249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250 match self {
251 Value::Flag => write!(f, "true"),
252 Value::Single(s) => write!(f, "\"{}\"", s),
253 Value::Multiple(v) => write!(f, "{:?}", v),
254 }
255 }
256}
257
258#[derive(Debug, Clone, Default)]
260pub struct Arguments<'a> {
261 args: BTreeMap<&'a str, Argument<'a>>,
263 extra_args: Vec<String>,
265}
266
267impl<'a> Arguments<'a> {
268 fn insert_arg(&mut self, argument: Argument<'a>) {
270 self.args.insert(argument.name, argument);
271 }
272
273 fn value_of(&self, arg_name: &'static str) -> Option<&Value> {
275 self.args.get(arg_name).and_then(|argument| {
276 argument
277 .user_value
278 .as_ref()
279 .or(argument.default_value.as_ref())
280 })
281 }
282
283 pub fn single_value(&self, arg_name: &'static str) -> Option<&String> {
286 self.value_of(arg_name)
287 .and_then(|arg_value| arg_value.as_single_value())
288 }
289
290 pub fn flag_present(&self, arg_name: &'static str) -> bool {
292 match self.value_of(arg_name) {
293 Some(v) => v.as_flag(),
294 None => false,
295 }
296 }
297
298 pub fn multiple_values(&self, arg_name: &'static str) -> Option<&[String]> {
301 self.value_of(arg_name)
302 .and_then(|arg_value| arg_value.as_multiple())
303 }
304
305 pub fn extra_args(&self) -> Vec<String> {
307 self.extra_args.clone()
308 }
309
310 fn split_args(args: &[String]) -> (&[String], &[String]) {
313 if let Some(index) = args.iter().position(|arg| arg == ARG_SEPARATOR) {
314 return (&args[..index], &args[index + 1..]);
315 }
316
317 (args, &[])
318 }
319
320 pub fn parse_from_cmdline(&mut self) -> Result<()> {
322 let args: Vec<String> = env::args().collect();
323
324 self.parse(&args)
325 }
326
327 pub fn parse(&mut self, args: &[String]) -> Result<()> {
330 let (args, extra_args) = Arguments::split_args(&args[1..]);
332 self.extra_args = extra_args.to_vec();
333
334 if args.contains(&HELP_ARG.to_string()) || args.contains(&SHORT_HELP_ARG.to_string()) {
338 let mut help_arg = Argument::new("help").help("Show the help message.");
339 help_arg.user_value = Some(Value::Flag);
340 self.insert_arg(help_arg);
341 return Ok(());
342 }
343
344 if args.contains(&VERSION_ARG.to_string()) {
348 let mut version_arg = Argument::new("version");
349 version_arg.user_value = Some(Value::Flag);
350 self.insert_arg(version_arg);
351 return Ok(());
352 }
353
354 self.populate_args(args)
356 }
357
358 fn validate_requirements(&self, args: &[String]) -> Result<()> {
361 for argument in self.args.values() {
362 if argument.required && argument.user_value.is_none() {
364 return Err(UtilsArgParserError::MissingArgument(
365 argument.name.to_string(),
366 ));
367 }
368 if argument.user_value.is_some() {
369 if let Some(arg_name) = argument.requires
372 && !args.contains(&(format!("--{}", arg_name)))
373 {
374 return Err(UtilsArgParserError::MissingArgument(arg_name.to_string()));
375 }
376 for arg_name in argument.forbids.iter() {
378 if args.contains(&(format!("--{}", arg_name))) {
379 return Err(UtilsArgParserError::ForbiddenArgument(
380 argument.name.to_string(),
381 arg_name.to_string(),
382 ));
383 }
384 }
385 }
386 }
387 Ok(())
388 }
389
390 fn validate_arg(&self, arg: &str) -> Result<()> {
392 if !arg.starts_with(ARG_PREFIX) {
393 return Err(UtilsArgParserError::UnexpectedArgument(arg.to_string()));
394 }
395 let arg_name = &arg[ARG_PREFIX.len()..];
396
397 let argument = self
400 .args
401 .get(arg_name)
402 .ok_or_else(|| UtilsArgParserError::UnexpectedArgument(arg_name.to_string()))?;
403
404 if !argument.allow_multiple && argument.user_value.is_some() {
405 return Err(UtilsArgParserError::DuplicateArgument(arg_name.to_string()));
406 }
407 Ok(())
408 }
409
410 fn populate_args(&mut self, args: &[String]) -> Result<()> {
413 let mut iter = args.iter();
414
415 while let Some(arg) = iter.next() {
416 self.validate_arg(arg)?;
417
418 let argument = self.args.get_mut(&arg[ARG_PREFIX.len()..]).ok_or_else(|| {
421 UtilsArgParserError::UnexpectedArgument(arg[ARG_PREFIX.len()..].to_string())
422 })?;
423
424 let arg_val = if argument.takes_value {
425 let val = iter
426 .next()
427 .filter(|v| !v.starts_with(ARG_PREFIX))
428 .ok_or_else(|| UtilsArgParserError::MissingValue(argument.name.to_string()))?
429 .clone();
430
431 if argument.allow_multiple {
432 match argument.user_value.take() {
433 Some(Value::Multiple(mut v)) => {
434 v.push(val);
435 Value::Multiple(v)
436 }
437 None => Value::Multiple(vec![val]),
438 _ => {
439 return Err(UtilsArgParserError::UnexpectedArgument(
440 argument.name.to_string(),
441 ));
442 }
443 }
444 } else {
445 Value::Single(val)
446 }
447 } else {
448 Value::Flag
449 };
450
451 argument.user_value = Some(arg_val);
452 }
453
454 self.validate_requirements(args)?;
457
458 Ok(())
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465 use crate::arg_parser::Value;
466
467 fn build_arg_parser() -> ArgParser<'static> {
468 ArgParser::new()
469 .arg(
470 Argument::new("exec-file")
471 .required(true)
472 .takes_value(true)
473 .help("'exec-file' info."),
474 )
475 .arg(
476 Argument::new("no-api")
477 .requires("config-file")
478 .takes_value(false)
479 .help("'no-api' info."),
480 )
481 .arg(
482 Argument::new("api-sock")
483 .takes_value(true)
484 .default_value("socket")
485 .help("'api-sock' info."),
486 )
487 .arg(
488 Argument::new("id")
489 .takes_value(true)
490 .default_value("instance")
491 .help("'id' info."),
492 )
493 .arg(
494 Argument::new("seccomp-filter")
495 .takes_value(true)
496 .help("'seccomp-filter' info.")
497 .forbids(vec!["no-seccomp"]),
498 )
499 .arg(
500 Argument::new("no-seccomp")
501 .help("'-no-seccomp' info.")
502 .forbids(vec!["seccomp-filter"]),
503 )
504 .arg(
505 Argument::new("config-file")
506 .takes_value(true)
507 .help("'config-file' info."),
508 )
509 .arg(
510 Argument::new("describe-snapshot")
511 .takes_value(true)
512 .help("'describe-snapshot' info."),
513 )
514 }
515
516 #[test]
517 fn test_arg_help() {
518 let width = 32;
520 let short_width = 16;
521
522 let mut argument = Argument::new("exec-file").takes_value(false);
523
524 assert_eq!(
525 argument.format_help(width),
526 " --exec-file "
527 );
528 assert_eq!(argument.format_help(short_width), " --exec-file ");
529
530 argument = Argument::new("exec-file").takes_value(true);
531
532 assert_eq!(
533 argument.format_help(width),
534 " --exec-file <exec-file> "
535 );
536 assert_eq!(
537 argument.format_help(short_width),
538 " --exec-file <exec-file> "
539 );
540
541 argument = Argument::new("exec-file")
542 .takes_value(true)
543 .help("'exec-file' info.");
544
545 assert_eq!(
546 argument.format_help(width),
547 " --exec-file <exec-file> 'exec-file' info."
548 );
549 assert_eq!(
550 argument.format_help(short_width),
551 " --exec-file <exec-file> 'exec-file' info."
552 );
553
554 argument = Argument::new("exec-file")
555 .takes_value(true)
556 .default_value("./exec-file");
557
558 assert_eq!(
559 argument.format_help(width),
560 " --exec-file <exec-file> [default: \"./exec-file\"]"
561 );
562 assert_eq!(
563 argument.format_help(short_width),
564 " --exec-file <exec-file> [default: \"./exec-file\"]"
565 );
566
567 argument = Argument::new("exec-file")
568 .takes_value(true)
569 .default_value("./exec-file")
570 .help("'exec-file' info.");
571
572 assert_eq!(
573 argument.format_help(width),
574 " --exec-file <exec-file> 'exec-file' info. [default: \"./exec-file\"]"
575 );
576 assert_eq!(
577 argument.format_help(short_width),
578 " --exec-file <exec-file> 'exec-file' info. [default: \"./exec-file\"]"
579 );
580 }
581
582 #[test]
583 fn test_arg_parser_help() {
584 let mut arg_parser = ArgParser::new()
586 .arg(
587 Argument::new("exec-file")
588 .required(true)
589 .takes_value(true)
590 .help("'exec-file' info."),
591 )
592 .arg(
593 Argument::new("api-sock")
594 .takes_value(true)
595 .help("'api-sock' info."),
596 );
597
598 assert_eq!(
599 arg_parser.formatted_help(),
600 "required arguments:\n --exec-file <exec-file> 'exec-file' info.\n\noptional \
601 arguments:\n --api-sock <api-sock> 'api-sock' info."
602 );
603
604 arg_parser = ArgParser::new()
605 .arg(Argument::new("id").takes_value(true).help("'id' info."))
606 .arg(
607 Argument::new("seccomp-filter")
608 .takes_value(true)
609 .help("'seccomp-filter' info."),
610 )
611 .arg(
612 Argument::new("config-file")
613 .takes_value(true)
614 .help("'config-file' info."),
615 );
616
617 assert_eq!(
618 arg_parser.formatted_help(),
619 "optional arguments:\n --config-file <config-file> 'config-file' info.\n \
620 --id <id> 'id' info.\n --seccomp-filter <seccomp-filter> \
621 'seccomp-filter' info."
622 );
623 }
624
625 #[test]
626 fn test_value() {
627 let mut value = Value::Flag;
629 assert!(Value::as_single_value(&value).is_none());
630 value = Value::Single("arg".to_string());
631 assert_eq!(Value::as_single_value(&value).unwrap(), "arg");
632
633 value = Value::Single("arg".to_string());
634 assert!(!Value::as_flag(&value));
635 value = Value::Flag;
636 assert!(Value::as_flag(&value));
637 }
638
639 #[test]
640 fn test_parse() {
641 let arg_parser = build_arg_parser();
642
643 let mut arguments = arg_parser.arguments().clone();
645
646 let args = vec!["binary-name", "--exec-file", "foo", "--help"]
647 .into_iter()
648 .map(String::from)
649 .collect::<Vec<String>>();
650
651 arguments.parse(&args).unwrap();
652 assert!(arguments.args.contains_key("help"));
653
654 arguments = arg_parser.arguments().clone();
655
656 let args = vec!["binary-name", "--exec-file", "foo", "-h"]
657 .into_iter()
658 .map(String::from)
659 .collect::<Vec<String>>();
660
661 arguments.parse(&args).unwrap();
662 assert!(arguments.args.contains_key("help"));
663
664 arguments = arg_parser.arguments().clone();
665
666 let args = vec!["binary-name", "--exec-file", "foo", "--version"]
667 .into_iter()
668 .map(String::from)
669 .collect::<Vec<String>>();
670
671 arguments.parse(&args).unwrap();
672 assert!(arguments.args.contains_key("version"));
673
674 arguments = arg_parser.arguments().clone();
675
676 let args = vec!["binary-name", "--exec-file", "foo", "--describe-snapshot"]
677 .into_iter()
678 .map(String::from)
679 .collect::<Vec<String>>();
680
681 assert_eq!(
682 arguments.parse(&args),
683 Err(UtilsArgParserError::MissingValue(
684 "describe-snapshot".to_string()
685 ))
686 );
687
688 arguments = arg_parser.arguments().clone();
689
690 let args = vec![
691 "binary-name",
692 "--exec-file",
693 "foo",
694 "--describe-snapshot",
695 "--",
696 ]
697 .into_iter()
698 .map(String::from)
699 .collect::<Vec<String>>();
700
701 assert_eq!(
702 arguments.parse(&args),
703 Err(UtilsArgParserError::MissingValue(
704 "describe-snapshot".to_string()
705 ))
706 );
707
708 arguments = arg_parser.arguments().clone();
709
710 let args = vec![
711 "binary-name",
712 "--exec-file",
713 "foo",
714 "--api-sock",
715 "--id",
716 "bar",
717 ]
718 .into_iter()
719 .map(String::from)
720 .collect::<Vec<String>>();
721
722 assert_eq!(
723 arguments.parse(&args),
724 Err(UtilsArgParserError::MissingValue("api-sock".to_string()))
725 );
726
727 arguments = arg_parser.arguments().clone();
728
729 let args = vec![
730 "binary-name",
731 "--exec-file",
732 "foo",
733 "--api-sock",
734 "bar",
735 "--api-sock",
736 "foobar",
737 ]
738 .into_iter()
739 .map(String::from)
740 .collect::<Vec<String>>();
741
742 assert_eq!(
743 arguments.parse(&args),
744 Err(UtilsArgParserError::DuplicateArgument(
745 "api-sock".to_string()
746 ))
747 );
748
749 arguments = arg_parser.arguments().clone();
750
751 let args = vec!["binary-name", "--api-sock", "foo"]
752 .into_iter()
753 .map(String::from)
754 .collect::<Vec<String>>();
755
756 assert_eq!(
757 arguments.parse(&args),
758 Err(UtilsArgParserError::MissingArgument(
759 "exec-file".to_string()
760 ))
761 );
762
763 arguments = arg_parser.arguments().clone();
764
765 let args = vec![
766 "binary-name",
767 "--exec-file",
768 "foo",
769 "--api-sock",
770 "bar",
771 "--invalid-arg",
772 ]
773 .into_iter()
774 .map(String::from)
775 .collect::<Vec<String>>();
776
777 assert_eq!(
778 arguments.parse(&args),
779 Err(UtilsArgParserError::UnexpectedArgument(
780 "invalid-arg".to_string()
781 ))
782 );
783
784 arguments = arg_parser.arguments().clone();
785
786 let args = vec![
787 "binary-name",
788 "--exec-file",
789 "foo",
790 "--api-sock",
791 "bar",
792 "--id",
793 "foobar",
794 "--no-api",
795 ]
796 .into_iter()
797 .map(String::from)
798 .collect::<Vec<String>>();
799
800 assert_eq!(
801 arguments.parse(&args),
802 Err(UtilsArgParserError::MissingArgument(
803 "config-file".to_string()
804 ))
805 );
806
807 arguments = arg_parser.arguments().clone();
808
809 let args = vec![
810 "binary-name",
811 "--exec-file",
812 "foo",
813 "--api-sock",
814 "bar",
815 "--id",
816 ]
817 .into_iter()
818 .map(String::from)
819 .collect::<Vec<String>>();
820
821 assert_eq!(
822 arguments.parse(&args),
823 Err(UtilsArgParserError::MissingValue("id".to_string()))
824 );
825
826 arguments = arg_parser.arguments().clone();
827
828 let args = vec![
829 "binary-name",
830 "--exec-file",
831 "foo",
832 "--config-file",
833 "bar",
834 "--no-api",
835 "foobar",
836 ]
837 .into_iter()
838 .map(String::from)
839 .collect::<Vec<String>>();
840
841 assert_eq!(
842 arguments.parse(&args),
843 Err(UtilsArgParserError::UnexpectedArgument(
844 "foobar".to_string()
845 ))
846 );
847
848 arguments = arg_parser.arguments().clone();
849
850 let args = vec![
851 "binary-name",
852 "--exec-file",
853 "foo",
854 "--api-sock",
855 "bar",
856 "--id",
857 "foobar",
858 "--seccomp-filter",
859 "0",
860 "--no-seccomp",
861 ]
862 .into_iter()
863 .map(String::from)
864 .collect::<Vec<String>>();
865
866 assert_eq!(
867 arguments.parse(&args),
868 Err(UtilsArgParserError::ForbiddenArgument(
869 "no-seccomp".to_string(),
870 "seccomp-filter".to_string(),
871 ))
872 );
873
874 arguments = arg_parser.arguments().clone();
875
876 let args = vec![
877 "binary-name",
878 "--exec-file",
879 "foo",
880 "--api-sock",
881 "bar",
882 "--id",
883 "foobar",
884 "--no-seccomp",
885 "--seccomp-filter",
886 "0",
887 ]
888 .into_iter()
889 .map(String::from)
890 .collect::<Vec<String>>();
891
892 assert_eq!(
893 arguments.parse(&args),
894 Err(UtilsArgParserError::ForbiddenArgument(
895 "no-seccomp".to_string(),
896 "seccomp-filter".to_string(),
897 ))
898 );
899
900 arguments = arg_parser.arguments().clone();
901
902 let args = vec![
903 "binary-name",
904 "--exec-file",
905 "foo",
906 "--api-sock",
907 "bar",
908 "foobar",
909 ]
910 .into_iter()
911 .map(String::from)
912 .collect::<Vec<String>>();
913
914 assert_eq!(
915 arguments.parse(&args),
916 Err(UtilsArgParserError::UnexpectedArgument(
917 "foobar".to_string()
918 ))
919 );
920
921 arguments = arg_parser.arguments().clone();
922
923 let args = vec!["binary-name", "foo"]
924 .into_iter()
925 .map(String::from)
926 .collect::<Vec<String>>();
927
928 assert_eq!(
929 arguments.parse(&args),
930 Err(UtilsArgParserError::UnexpectedArgument("foo".to_string()))
931 );
932
933 arguments = arg_parser.arguments().clone();
934
935 let args = vec![
936 "binary-name",
937 "--exec-file",
938 "foo",
939 "--api-sock",
940 "bar",
941 "--id",
942 "foobar",
943 "--seccomp-filter",
944 "0",
945 "--",
946 "--extra-flag",
947 ]
948 .into_iter()
949 .map(String::from)
950 .collect::<Vec<String>>();
951
952 arguments.parse(&args).unwrap();
953 assert!(arguments.extra_args.contains(&"--extra-flag".to_string()));
954 }
955
956 #[test]
957 fn test_split() {
958 let mut args = vec!["--exec-file", "foo", "--", "--extra-arg-1", "--extra-arg-2"]
959 .into_iter()
960 .map(String::from)
961 .collect::<Vec<String>>();
962 let (left, right) = Arguments::split_args(&args);
963 assert_eq!(left.to_vec(), vec!["--exec-file", "foo"]);
964 assert_eq!(right.to_vec(), vec!["--extra-arg-1", "--extra-arg-2"]);
965
966 args = vec!["--exec-file", "foo", "--"]
967 .into_iter()
968 .map(String::from)
969 .collect::<Vec<String>>();
970 let (left, right) = Arguments::split_args(&args);
971 assert_eq!(left.to_vec(), vec!["--exec-file", "foo"]);
972 assert!(right.is_empty());
973
974 args = vec!["--exec-file", "foo"]
975 .into_iter()
976 .map(String::from)
977 .collect::<Vec<String>>();
978 let (left, right) = Arguments::split_args(&args);
979 assert_eq!(left.to_vec(), vec!["--exec-file", "foo"]);
980 assert!(right.is_empty());
981 }
982
983 #[test]
984 fn test_error_display() {
985 assert_eq!(
986 format!(
987 "{}",
988 UtilsArgParserError::ForbiddenArgument("foo".to_string(), "bar".to_string())
989 ),
990 "Argument 'bar' cannot be used together with argument 'foo'."
991 );
992 assert_eq!(
993 format!(
994 "{}",
995 UtilsArgParserError::MissingArgument("foo".to_string())
996 ),
997 "Argument 'foo' required, but not found."
998 );
999 assert_eq!(
1000 format!("{}", UtilsArgParserError::MissingValue("foo".to_string())),
1001 "The argument 'foo' requires a value, but none was supplied."
1002 );
1003 assert_eq!(
1004 format!(
1005 "{}",
1006 UtilsArgParserError::UnexpectedArgument("foo".to_string())
1007 ),
1008 "Found argument 'foo' which wasn't expected, or isn't valid in this context."
1009 );
1010 assert_eq!(
1011 format!(
1012 "{}",
1013 UtilsArgParserError::DuplicateArgument("foo".to_string())
1014 ),
1015 "The argument 'foo' was provided more than once."
1016 );
1017 }
1018
1019 #[test]
1020 fn test_value_display() {
1021 assert_eq!(format!("{}", Value::Flag), "true");
1022 assert_eq!(format!("{}", Value::Single("foo".to_string())), "\"foo\"");
1023 }
1024
1025 #[test]
1026 fn test_allow_multiple() {
1027 let arg_parser = ArgParser::new()
1028 .arg(
1029 Argument::new("no-multiple")
1030 .takes_value(true)
1031 .help("argument that takes just one value."),
1032 )
1033 .arg(
1034 Argument::new("multiple")
1035 .allow_multiple(true)
1036 .help("argument that allows duplication."),
1037 );
1038
1039 let mut arguments = arg_parser.arguments().clone();
1040
1041 let args = vec!["binary-name", "--no-multiple", "1", "--no-multiple", "2"]
1043 .into_iter()
1044 .map(String::from)
1045 .collect::<Vec<String>>();
1046
1047 assert_eq!(
1048 arguments.parse(&args),
1049 Err(UtilsArgParserError::DuplicateArgument(
1050 "no-multiple".to_string()
1051 ))
1052 );
1053
1054 arguments = arg_parser.arguments().clone();
1055
1056 let args = vec!["binary-name", "--no-multiple", "1", "--multiple", "2"]
1059 .into_iter()
1060 .map(String::from)
1061 .collect::<Vec<String>>();
1062
1063 arguments.parse(&args).unwrap();
1064
1065 arguments = arg_parser.arguments().clone();
1066
1067 let args = vec!["binary-name", "--multiple", "1", "--multiple", "2"]
1069 .into_iter()
1070 .map(String::from)
1071 .collect::<Vec<String>>();
1072
1073 arguments.parse(&args).unwrap();
1074
1075 let args = vec!["binary-name", "--multiple", "--multiple", "2"]
1077 .into_iter()
1078 .map(String::from)
1079 .collect::<Vec<String>>();
1080
1081 assert_eq!(
1082 arguments.parse(&args),
1083 Err(UtilsArgParserError::MissingValue("multiple".to_string()))
1084 );
1085 }
1086}