1 use std::cmp::Ordering;
2
3 use clap_lex::RawOsStr;
4
5 use crate::builder::OsStr;
6 use crate::builder::ValueRange;
7 use crate::mkeymap::KeyType;
8 use crate::util::FlatSet;
9 use crate::util::Id;
10 use crate::ArgAction;
11 use crate::INTERNAL_ERROR_MSG;
12 use crate::{Arg, Command, ValueHint};
13
assert_app(cmd: &Command)14 pub(crate) fn assert_app(cmd: &Command) {
15 debug!("Command::_debug_asserts");
16
17 let mut short_flags = vec![];
18 let mut long_flags = vec![];
19
20 // Invalid version flag settings
21 if cmd.get_version().is_none() && cmd.get_long_version().is_none() {
22 // PropagateVersion is meaningless if there is no version
23 assert!(
24 !cmd.is_propagate_version_set(),
25 "Command {}: No version information via Command::version or Command::long_version to propagate",
26 cmd.get_name(),
27 );
28
29 // Used `Command::mut_arg("version", ..) but did not provide any version information to display
30 let version_needed = cmd
31 .get_arguments()
32 .filter(|x| matches!(x.get_action(), ArgAction::Version))
33 .map(|x| x.get_id())
34 .collect::<Vec<_>>();
35
36 assert_eq!(version_needed, Vec::<&str>::new(), "Command {}: `ArgAction::Version` used without providing Command::version or Command::long_version"
37 ,cmd.get_name()
38 );
39 }
40
41 for sc in cmd.get_subcommands() {
42 if let Some(s) = sc.get_short_flag().as_ref() {
43 short_flags.push(Flag::Command(format!("-{}", s), sc.get_name()));
44 }
45
46 for short_alias in sc.get_all_short_flag_aliases() {
47 short_flags.push(Flag::Command(format!("-{}", short_alias), sc.get_name()));
48 }
49
50 if let Some(l) = sc.get_long_flag().as_ref() {
51 assert!(!l.starts_with('-'), "Command {}: long_flag {:?} must not start with a `-`, that will be handled by the parser", sc.get_name(), l);
52 long_flags.push(Flag::Command(format!("--{}", l), sc.get_name()));
53 }
54
55 for long_alias in sc.get_all_long_flag_aliases() {
56 long_flags.push(Flag::Command(format!("--{}", long_alias), sc.get_name()));
57 }
58 }
59
60 for arg in cmd.get_arguments() {
61 assert_arg(arg);
62
63 assert!(
64 !cmd.is_multicall_set(),
65 "Command {}: Arguments like {} cannot be set on a multicall command",
66 cmd.get_name(),
67 arg.get_id()
68 );
69
70 if let Some(s) = arg.get_short() {
71 short_flags.push(Flag::Arg(format!("-{}", s), arg.get_id().as_str()));
72 }
73
74 for (short_alias, _) in &arg.short_aliases {
75 short_flags.push(Flag::Arg(
76 format!("-{}", short_alias),
77 arg.get_id().as_str(),
78 ));
79 }
80
81 if let Some(l) = arg.get_long() {
82 assert!(!l.starts_with('-'), "Argument {}: long {:?} must not start with a `-`, that will be handled by the parser", arg.get_id(), l);
83 long_flags.push(Flag::Arg(format!("--{}", l), arg.get_id().as_str()));
84 }
85
86 for (long_alias, _) in &arg.aliases {
87 long_flags.push(Flag::Arg(
88 format!("--{}", long_alias),
89 arg.get_id().as_str(),
90 ));
91 }
92
93 // Name conflicts
94 if let Some((first, second)) = cmd.two_args_of(|x| x.get_id() == arg.get_id()) {
95 panic!(
96 "Command {}: Argument names must be unique, but '{}' is in use by more than one argument or group{}",
97 cmd.get_name(),
98 arg.get_id(),
99 duplicate_tip(cmd, first, second),
100 );
101 }
102
103 // Long conflicts
104 if let Some(l) = arg.get_long() {
105 if let Some((first, second)) = cmd.two_args_of(|x| x.get_long() == Some(l)) {
106 panic!(
107 "Command {}: Long option names must be unique for each argument, \
108 but '--{}' is in use by both '{}' and '{}'{}",
109 cmd.get_name(),
110 l,
111 first.get_id(),
112 second.get_id(),
113 duplicate_tip(cmd, first, second)
114 )
115 }
116 }
117
118 // Short conflicts
119 if let Some(s) = arg.get_short() {
120 if let Some((first, second)) = cmd.two_args_of(|x| x.get_short() == Some(s)) {
121 panic!(
122 "Command {}: Short option names must be unique for each argument, \
123 but '-{}' is in use by both '{}' and '{}'{}",
124 cmd.get_name(),
125 s,
126 first.get_id(),
127 second.get_id(),
128 duplicate_tip(cmd, first, second),
129 )
130 }
131 }
132
133 // Index conflicts
134 if let Some(idx) = arg.index {
135 if let Some((first, second)) =
136 cmd.two_args_of(|x| x.is_positional() && x.get_index() == Some(idx))
137 {
138 panic!(
139 "Command {}: Argument '{}' has the same index as '{}' \
140 and they are both positional arguments\n\n\t \
141 Use `Arg::num_args(1..)` to allow one \
142 positional argument to take multiple values",
143 cmd.get_name(),
144 first.get_id(),
145 second.get_id()
146 )
147 }
148 }
149
150 // requires, r_if, r_unless
151 for req in &arg.requires {
152 assert!(
153 cmd.id_exists(&req.1),
154 "Command {}: Argument or group '{}' specified in 'requires*' for '{}' does not exist",
155 cmd.get_name(),
156 req.1,
157 arg.get_id(),
158 );
159 }
160
161 for req in &arg.r_ifs {
162 assert!(
163 !arg.is_required_set(),
164 "Argument {}: `required` conflicts with `required_if_eq*`",
165 arg.get_id()
166 );
167 assert!(
168 cmd.id_exists(&req.0),
169 "Command {}: Argument or group '{}' specified in 'required_if_eq*' for '{}' does not exist",
170 cmd.get_name(),
171 req.0,
172 arg.get_id()
173 );
174 }
175
176 for req in &arg.r_ifs_all {
177 assert!(
178 !arg.is_required_set(),
179 "Argument {}: `required` conflicts with `required_if_eq_all`",
180 arg.get_id()
181 );
182 assert!(
183 cmd.id_exists(&req.0),
184 "Command {}: Argument or group '{}' specified in 'required_if_eq_all' for '{}' does not exist",
185 cmd.get_name(),
186 req.0,
187 arg.get_id()
188 );
189 }
190
191 for req in &arg.r_unless {
192 assert!(
193 !arg.is_required_set(),
194 "Argument {}: `required` conflicts with `required_unless*`",
195 arg.get_id()
196 );
197 assert!(
198 cmd.id_exists(req),
199 "Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist",
200 cmd.get_name(),
201 req,
202 arg.get_id(),
203 );
204 }
205
206 for req in &arg.r_unless_all {
207 assert!(
208 !arg.is_required_set(),
209 "Argument {}: `required` conflicts with `required_unless*`",
210 arg.get_id()
211 );
212 assert!(
213 cmd.id_exists(req),
214 "Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist",
215 cmd.get_name(),
216 req,
217 arg.get_id(),
218 );
219 }
220
221 // blacklist
222 for req in &arg.blacklist {
223 assert!(
224 cmd.id_exists(req),
225 "Command {}: Argument or group '{}' specified in 'conflicts_with*' for '{}' does not exist",
226 cmd.get_name(),
227 req,
228 arg.get_id(),
229 );
230 }
231
232 // overrides
233 for req in &arg.overrides {
234 assert!(
235 cmd.id_exists(req),
236 "Command {}: Argument or group '{}' specified in 'overrides_with*' for '{}' does not exist",
237 cmd.get_name(),
238 req,
239 arg.get_id(),
240 );
241 }
242
243 if arg.is_last_set() {
244 assert!(
245 arg.get_long().is_none(),
246 "Command {}: Flags or Options cannot have last(true) set. '{}' has both a long and last(true) set.",
247 cmd.get_name(),
248 arg.get_id()
249 );
250 assert!(
251 arg.get_short().is_none(),
252 "Command {}: Flags or Options cannot have last(true) set. '{}' has both a short and last(true) set.",
253 cmd.get_name(),
254 arg.get_id()
255 );
256 }
257
258 assert!(
259 !(arg.is_required_set() && arg.is_global_set()),
260 "Command {}: Global arguments cannot be required.\n\n\t'{}' is marked as both global and required",
261 cmd.get_name(),
262 arg.get_id()
263 );
264
265 if arg.get_value_hint() == ValueHint::CommandWithArguments {
266 assert!(
267 arg.is_positional(),
268 "Command {}: Argument '{}' has hint CommandWithArguments and must be positional.",
269 cmd.get_name(),
270 arg.get_id()
271 );
272
273 assert!(
274 arg.is_trailing_var_arg_set() || arg.is_last_set(),
275 "Command {}: Positional argument '{}' has hint CommandWithArguments, so Command must have `trailing_var_arg(true)` or `last(true)` set.",
276 cmd.get_name(),
277 arg.get_id()
278 );
279 }
280 }
281
282 for group in cmd.get_groups() {
283 let derive_hint = if cfg!(feature = "derive") {
284 " (note: `Args` implicitly creates `ArgGroup`s; disable with `#[group(skip)]`"
285 } else {
286 ""
287 };
288
289 // Name conflicts
290 assert!(
291 cmd.get_groups().filter(|x| x.id == group.id).count() < 2,
292 "Command {}: Argument group name must be unique\n\n\t'{}' is already in use{}",
293 cmd.get_name(),
294 group.get_id(),
295 derive_hint
296 );
297
298 // Groups should not have naming conflicts with Args
299 assert!(
300 !cmd.get_arguments().any(|x| x.get_id() == group.get_id()),
301 "Command {}: Argument group name '{}' must not conflict with argument name{}",
302 cmd.get_name(),
303 group.get_id(),
304 derive_hint
305 );
306
307 for arg in &group.args {
308 // Args listed inside groups should exist
309 assert!(
310 cmd.get_arguments().any(|x| x.get_id() == arg),
311 "Command {}: Argument group '{}' contains non-existent argument '{}'",
312 cmd.get_name(),
313 group.get_id(),
314 arg
315 );
316 }
317 }
318
319 // Conflicts between flags and subcommands
320
321 long_flags.sort_unstable();
322 short_flags.sort_unstable();
323
324 detect_duplicate_flags(&long_flags, "long");
325 detect_duplicate_flags(&short_flags, "short");
326
327 let mut subs = FlatSet::new();
328 for sc in cmd.get_subcommands() {
329 assert!(
330 subs.insert(sc.get_name()),
331 "Command {}: command name `{}` is duplicated",
332 cmd.get_name(),
333 sc.get_name()
334 );
335 for alias in sc.get_all_aliases() {
336 assert!(
337 subs.insert(alias),
338 "Command {}: command `{}` alias `{}` is duplicated",
339 cmd.get_name(),
340 sc.get_name(),
341 alias
342 );
343 }
344 }
345
346 _verify_positionals(cmd);
347
348 #[cfg(feature = "help")]
349 if let Some(help_template) = cmd.get_help_template() {
350 assert!(
351 !help_template.to_string().contains("{flags}"),
352 "Command {}: {}",
353 cmd.get_name(),
354 "`{flags}` template variable was removed in clap3, they are now included in `{options}`",
355 );
356 assert!(
357 !help_template.to_string().contains("{unified}"),
358 "Command {}: {}",
359 cmd.get_name(),
360 "`{unified}` template variable was removed in clap3, use `{options}` instead"
361 );
362 #[cfg(feature = "unstable-v5")]
363 assert!(
364 !help_template.to_string().contains("{bin}"),
365 "Command {}: {}",
366 cmd.get_name(),
367 "`{bin}` template variable was removed in clap5, use `{name}` instead"
368 )
369 }
370
371 cmd._panic_on_missing_help(cmd.is_help_expected_set());
372 assert_app_flags(cmd);
373 }
374
duplicate_tip(cmd: &Command, first: &Arg, second: &Arg) -> &'static str375 fn duplicate_tip(cmd: &Command, first: &Arg, second: &Arg) -> &'static str {
376 if !cmd.is_disable_help_flag_set()
377 && (first.get_id() == Id::HELP || second.get_id() == Id::HELP)
378 {
379 " (call `cmd.disable_help_flag(true)` to remove the auto-generated `--help`)"
380 } else if !cmd.is_disable_version_flag_set()
381 && (first.get_id() == Id::VERSION || second.get_id() == Id::VERSION)
382 {
383 " (call `cmd.disable_version_flag(true)` to remove the auto-generated `--version`)"
384 } else {
385 ""
386 }
387 }
388
389 #[derive(Eq)]
390 enum Flag<'a> {
391 Command(String, &'a str),
392 Arg(String, &'a str),
393 }
394
395 impl PartialEq for Flag<'_> {
eq(&self, other: &Flag) -> bool396 fn eq(&self, other: &Flag) -> bool {
397 self.cmp(other) == Ordering::Equal
398 }
399 }
400
401 impl PartialOrd for Flag<'_> {
partial_cmp(&self, other: &Flag) -> Option<Ordering>402 fn partial_cmp(&self, other: &Flag) -> Option<Ordering> {
403 use Flag::*;
404
405 match (self, other) {
406 (Command(s1, _), Command(s2, _))
407 | (Arg(s1, _), Arg(s2, _))
408 | (Command(s1, _), Arg(s2, _))
409 | (Arg(s1, _), Command(s2, _)) => {
410 if s1 == s2 {
411 Some(Ordering::Equal)
412 } else {
413 s1.partial_cmp(s2)
414 }
415 }
416 }
417 }
418 }
419
420 impl Ord for Flag<'_> {
cmp(&self, other: &Self) -> Ordering421 fn cmp(&self, other: &Self) -> Ordering {
422 self.partial_cmp(other).unwrap()
423 }
424 }
425
detect_duplicate_flags(flags: &[Flag], short_or_long: &str)426 fn detect_duplicate_flags(flags: &[Flag], short_or_long: &str) {
427 use Flag::*;
428
429 for (one, two) in find_duplicates(flags) {
430 match (one, two) {
431 (Command(flag, one), Command(_, another)) if one != another => panic!(
432 "the '{}' {} flag is specified for both '{}' and '{}' subcommands",
433 flag, short_or_long, one, another
434 ),
435
436 (Arg(flag, one), Arg(_, another)) if one != another => panic!(
437 "{} option names must be unique, but '{}' is in use by both '{}' and '{}'",
438 short_or_long, flag, one, another
439 ),
440
441 (Arg(flag, arg), Command(_, sub)) | (Command(flag, sub), Arg(_, arg)) => panic!(
442 "the '{}' {} flag for the '{}' argument conflicts with the short flag \
443 for '{}' subcommand",
444 flag, short_or_long, arg, sub
445 ),
446
447 _ => {}
448 }
449 }
450 }
451
452 /// Find duplicates in a sorted array.
453 ///
454 /// The algorithm is simple: the array is sorted, duplicates
455 /// must be placed next to each other, we can check only adjacent elements.
find_duplicates<T: PartialEq>(slice: &[T]) -> impl Iterator<Item = (&T, &T)>456 fn find_duplicates<T: PartialEq>(slice: &[T]) -> impl Iterator<Item = (&T, &T)> {
457 slice.windows(2).filter_map(|w| {
458 if w[0] == w[1] {
459 Some((&w[0], &w[1]))
460 } else {
461 None
462 }
463 })
464 }
465
assert_app_flags(cmd: &Command)466 fn assert_app_flags(cmd: &Command) {
467 macro_rules! checker {
468 ($a:ident requires $($b:ident)|+) => {
469 if cmd.$a() {
470 let mut s = String::new();
471
472 $(
473 if !cmd.$b() {
474 use std::fmt::Write;
475 write!(&mut s, " AppSettings::{} is required when AppSettings::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap();
476 }
477 )+
478
479 if !s.is_empty() {
480 panic!("{}", s)
481 }
482 }
483 };
484 ($a:ident conflicts $($b:ident)|+) => {
485 if cmd.$a() {
486 let mut s = String::new();
487
488 $(
489 if cmd.$b() {
490 use std::fmt::Write;
491 write!(&mut s, " AppSettings::{} conflicts with AppSettings::{}.\n", std::stringify!($b), std::stringify!($a)).unwrap();
492 }
493 )+
494
495 if !s.is_empty() {
496 panic!("{}\n{}", cmd.get_name(), s)
497 }
498 }
499 };
500 }
501
502 checker!(is_multicall_set conflicts is_no_binary_name_set);
503 }
504
505 #[cfg(debug_assertions)]
_verify_positionals(cmd: &Command) -> bool506 fn _verify_positionals(cmd: &Command) -> bool {
507 debug!("Command::_verify_positionals");
508 // Because you must wait until all arguments have been supplied, this is the first chance
509 // to make assertions on positional argument indexes
510 //
511 // First we verify that the index highest supplied index, is equal to the number of
512 // positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3
513 // but no 2)
514
515 let highest_idx = cmd
516 .get_keymap()
517 .keys()
518 .filter_map(|x| {
519 if let KeyType::Position(n) = x {
520 Some(*n)
521 } else {
522 None
523 }
524 })
525 .max()
526 .unwrap_or(0);
527
528 let num_p = cmd.get_keymap().keys().filter(|x| x.is_position()).count();
529
530 assert!(
531 highest_idx == num_p,
532 "Found positional argument whose index is {} but there \
533 are only {} positional arguments defined",
534 highest_idx,
535 num_p
536 );
537
538 for arg in cmd.get_arguments() {
539 if arg.index.unwrap_or(0) == highest_idx {
540 assert!(
541 !arg.is_trailing_var_arg_set() || !arg.is_last_set(),
542 "{}:{}: `Arg::trailing_var_arg` and `Arg::last` cannot be used together",
543 cmd.get_name(),
544 arg.get_id()
545 );
546
547 if arg.is_trailing_var_arg_set() {
548 assert!(
549 arg.is_multiple(),
550 "{}:{}: `Arg::trailing_var_arg` must accept multiple values",
551 cmd.get_name(),
552 arg.get_id()
553 );
554 }
555 } else {
556 assert!(
557 !arg.is_trailing_var_arg_set(),
558 "{}:{}: `Arg::trailing_var_arg` can only apply to last positional",
559 cmd.get_name(),
560 arg.get_id()
561 );
562 }
563 }
564
565 // Next we verify that only the highest index has takes multiple arguments (if any)
566 let only_highest = |a: &Arg| a.is_multiple() && (a.get_index().unwrap_or(0) != highest_idx);
567 if cmd.get_positionals().any(only_highest) {
568 // First we make sure if there is a positional that allows multiple values
569 // the one before it (second to last) has one of these:
570 // * a value terminator
571 // * ArgSettings::Last
572 // * The last arg is Required
573
574 // We can't pass the closure (it.next()) to the macro directly because each call to
575 // find() (iterator, not macro) gets called repeatedly.
576 let last = &cmd.get_keymap()[&KeyType::Position(highest_idx)];
577 let second_to_last = &cmd.get_keymap()[&KeyType::Position(highest_idx - 1)];
578
579 // Either the final positional is required
580 // Or the second to last has a terminator or .last(true) set
581 let ok = last.is_required_set()
582 || (second_to_last.terminator.is_some() || second_to_last.is_last_set())
583 || last.is_last_set();
584 assert!(
585 ok,
586 "When using a positional argument with `.num_args(1..)` that is *not the \
587 last* positional argument, the last positional argument (i.e. the one \
588 with the highest index) *must* have .required(true) or .last(true) set."
589 );
590
591 // We make sure if the second to last is Multiple the last is ArgSettings::Last
592 let ok = second_to_last.is_multiple() || last.is_last_set();
593 assert!(
594 ok,
595 "Only the last positional argument, or second to last positional \
596 argument may be set to `.num_args(1..)`"
597 );
598
599 // Next we check how many have both Multiple and not a specific number of values set
600 let count = cmd
601 .get_positionals()
602 .filter(|p| {
603 p.is_multiple_values_set()
604 && !p.get_num_args().expect(INTERNAL_ERROR_MSG).is_fixed()
605 })
606 .count();
607 let ok = count <= 1
608 || (last.is_last_set()
609 && last.is_multiple()
610 && second_to_last.is_multiple()
611 && count == 2);
612 assert!(
613 ok,
614 "Only one positional argument with `.num_args(1..)` set is allowed per \
615 command, unless the second one also has .last(true) set"
616 );
617 }
618
619 let mut found = false;
620
621 if cmd.is_allow_missing_positional_set() {
622 // Check that if a required positional argument is found, all positions with a lower
623 // index are also required.
624 let mut foundx2 = false;
625
626 for p in cmd.get_positionals() {
627 if foundx2 && !p.is_required_set() {
628 assert!(
629 p.is_required_set(),
630 "Found non-required positional argument with a lower \
631 index than a required positional argument by two or more: {:?} \
632 index {:?}",
633 p.get_id(),
634 p.get_index()
635 );
636 } else if p.is_required_set() && !p.is_last_set() {
637 // Args that .last(true) don't count since they can be required and have
638 // positionals with a lower index that aren't required
639 // Imagine: prog <req1> [opt1] -- <req2>
640 // Both of these are valid invocations:
641 // $ prog r1 -- r2
642 // $ prog r1 o1 -- r2
643 if found {
644 foundx2 = true;
645 continue;
646 }
647 found = true;
648 continue;
649 } else {
650 found = false;
651 }
652 }
653 } else {
654 // Check that if a required positional argument is found, all positions with a lower
655 // index are also required
656 for p in (1..=num_p).rev().filter_map(|n| cmd.get_keymap().get(&n)) {
657 if found {
658 assert!(
659 p.is_required_set(),
660 "Found non-required positional argument with a lower \
661 index than a required positional argument: {:?} index {:?}",
662 p.get_id(),
663 p.get_index()
664 );
665 } else if p.is_required_set() && !p.is_last_set() {
666 // Args that .last(true) don't count since they can be required and have
667 // positionals with a lower index that aren't required
668 // Imagine: prog <req1> [opt1] -- <req2>
669 // Both of these are valid invocations:
670 // $ prog r1 -- r2
671 // $ prog r1 o1 -- r2
672 found = true;
673 continue;
674 }
675 }
676 }
677 assert!(
678 cmd.get_positionals().filter(|p| p.is_last_set()).count() < 2,
679 "Only one positional argument may have last(true) set. Found two."
680 );
681 if cmd
682 .get_positionals()
683 .any(|p| p.is_last_set() && p.is_required_set())
684 && cmd.has_subcommands()
685 && !cmd.is_subcommand_negates_reqs_set()
686 {
687 panic!(
688 "Having a required positional argument with .last(true) set *and* child \
689 subcommands without setting SubcommandsNegateReqs isn't compatible."
690 );
691 }
692
693 true
694 }
695
assert_arg(arg: &Arg)696 fn assert_arg(arg: &Arg) {
697 debug!("Arg::_debug_asserts:{}", arg.get_id());
698
699 // Self conflict
700 // TODO: this check should be recursive
701 assert!(
702 !arg.blacklist.iter().any(|x| x == arg.get_id()),
703 "Argument '{}' cannot conflict with itself",
704 arg.get_id(),
705 );
706
707 assert_eq!(
708 arg.get_action().takes_values(),
709 arg.is_takes_value_set(),
710 "Argument `{}`'s selected action {:?} contradicts `takes_value`",
711 arg.get_id(),
712 arg.get_action()
713 );
714 if let Some(action_type_id) = arg.get_action().value_type_id() {
715 assert_eq!(
716 action_type_id,
717 arg.get_value_parser().type_id(),
718 "Argument `{}`'s selected action {:?} contradicts `value_parser` ({:?})",
719 arg.get_id(),
720 arg.get_action(),
721 arg.get_value_parser()
722 );
723 }
724
725 if arg.get_value_hint() != ValueHint::Unknown {
726 assert!(
727 arg.is_takes_value_set(),
728 "Argument '{}' has value hint but takes no value",
729 arg.get_id()
730 );
731
732 if arg.get_value_hint() == ValueHint::CommandWithArguments {
733 assert!(
734 arg.is_multiple_values_set(),
735 "Argument '{}' uses hint CommandWithArguments and must accept multiple values",
736 arg.get_id()
737 )
738 }
739 }
740
741 if arg.index.is_some() {
742 assert!(
743 arg.is_positional(),
744 "Argument '{}' is a positional argument and can't have short or long name versions",
745 arg.get_id()
746 );
747 assert!(
748 arg.is_takes_value_set(),
749 "Argument '{}` is positional, it must take a value{}",
750 arg.get_id(),
751 if arg.get_id() == Id::HELP {
752 " (`mut_arg` no longer works with implicit `--help`)"
753 } else if arg.get_id() == Id::VERSION {
754 " (`mut_arg` no longer works with implicit `--version`)"
755 } else {
756 ""
757 }
758 );
759 }
760
761 let num_vals = arg.get_num_args().expect(INTERNAL_ERROR_MSG);
762 // This can be the cause of later asserts, so put this first
763 if num_vals != ValueRange::EMPTY {
764 // HACK: Don't check for flags to make the derive easier
765 let num_val_names = arg.get_value_names().unwrap_or(&[]).len();
766 if num_vals.max_values() < num_val_names {
767 panic!(
768 "Argument {}: Too many value names ({}) compared to `num_args` ({})",
769 arg.get_id(),
770 num_val_names,
771 num_vals
772 );
773 }
774 }
775
776 assert_eq!(
777 num_vals.takes_values(),
778 arg.is_takes_value_set(),
779 "Argument {}: mismatch between `num_args` ({}) and `takes_value`",
780 arg.get_id(),
781 num_vals,
782 );
783 assert_eq!(
784 num_vals.is_multiple(),
785 arg.is_multiple_values_set(),
786 "Argument {}: mismatch between `num_args` ({}) and `multiple_values`",
787 arg.get_id(),
788 num_vals,
789 );
790
791 if 1 < num_vals.min_values() {
792 assert!(
793 !arg.is_require_equals_set(),
794 "Argument {}: cannot accept more than 1 arg (num_args={}) with require_equals",
795 arg.get_id(),
796 num_vals
797 );
798 }
799
800 if num_vals == ValueRange::SINGLE {
801 assert!(
802 !arg.is_multiple_values_set(),
803 "Argument {}: mismatch between `num_args` and `multiple_values`",
804 arg.get_id()
805 );
806 }
807
808 assert_arg_flags(arg);
809
810 assert_defaults(arg, "default_value", arg.default_vals.iter());
811 assert_defaults(
812 arg,
813 "default_missing_value",
814 arg.default_missing_vals.iter(),
815 );
816 assert_defaults(
817 arg,
818 "default_value_if",
819 arg.default_vals_ifs
820 .iter()
821 .filter_map(|(_, _, default)| default.as_ref()),
822 );
823 }
824
assert_arg_flags(arg: &Arg)825 fn assert_arg_flags(arg: &Arg) {
826 macro_rules! checker {
827 ($a:ident requires $($b:ident)|+) => {
828 if arg.$a() {
829 let mut s = String::new();
830
831 $(
832 if !arg.$b() {
833 use std::fmt::Write;
834 write!(&mut s, " Arg::{} is required when Arg::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap();
835 }
836 )+
837
838 if !s.is_empty() {
839 panic!("Argument {:?}\n{}", arg.get_id(), s)
840 }
841 }
842 }
843 }
844
845 checker!(is_hide_possible_values_set requires is_takes_value_set);
846 checker!(is_allow_hyphen_values_set requires is_takes_value_set);
847 checker!(is_allow_negative_numbers_set requires is_takes_value_set);
848 checker!(is_require_equals_set requires is_takes_value_set);
849 checker!(is_last_set requires is_takes_value_set);
850 checker!(is_hide_default_value_set requires is_takes_value_set);
851 checker!(is_multiple_values_set requires is_takes_value_set);
852 checker!(is_ignore_case_set requires is_takes_value_set);
853 }
854
assert_defaults<'d>( arg: &Arg, field: &'static str, defaults: impl IntoIterator<Item = &'d OsStr>, )855 fn assert_defaults<'d>(
856 arg: &Arg,
857 field: &'static str,
858 defaults: impl IntoIterator<Item = &'d OsStr>,
859 ) {
860 for default_os in defaults {
861 let value_parser = arg.get_value_parser();
862 let assert_cmd = Command::new("assert");
863 if let Some(delim) = arg.get_value_delimiter() {
864 let default_os = RawOsStr::new(default_os);
865 for part in default_os.split(delim) {
866 if let Err(err) = value_parser.parse_ref(&assert_cmd, Some(arg), &part.to_os_str())
867 {
868 panic!(
869 "Argument `{}`'s {}={:?} failed validation: {}",
870 arg.get_id(),
871 field,
872 part.to_str_lossy(),
873 err
874 );
875 }
876 }
877 } else if let Err(err) = value_parser.parse_ref(&assert_cmd, Some(arg), default_os) {
878 panic!(
879 "Argument `{}`'s {}={:?} failed validation: {}",
880 arg.get_id(),
881 field,
882 default_os,
883 err
884 );
885 }
886 }
887 }
888