1 use std
::cmp
::Ordering
;
3 use clap_lex
::RawOsStr
;
5 use crate::builder
::arg
::ArgProvider
;
6 use crate::mkeymap
::KeyType
;
8 use crate::{Arg, Command, ValueHint}
;
10 pub(crate) fn assert_app(cmd
: &Command
) {
11 debug
!("Command::_debug_asserts");
13 let mut short_flags
= vec
![];
14 let mut long_flags
= vec
![];
16 // Invalid version flag settings
17 if cmd
.get_version().is_none() && cmd
.get_long_version().is_none() {
18 // PropagateVersion is meaningless if there is no version
20 !cmd
.is_propagate_version_set(),
21 "Command {}: No version information via Command::version or Command::long_version to propagate",
25 // Used `Command::mut_arg("version", ..) but did not provide any version information to display
26 let version_needed
= cmd
29 let action_set
= matches
!(x
.get_action(), ArgAction
::Version
);
30 #[cfg(not(feature = "unstable-v4"))]
31 let provider_set
= matches
!(x
.provider
, ArgProvider
::GeneratedMutated
);
32 #[cfg(feature = "unstable-v4")]
33 let provider_set
= matches
!(
35 ArgProvider
::User
| ArgProvider
::GeneratedMutated
37 action_set
&& provider_set
42 assert_eq
!(version_needed
, Vec
::<&str>::new(), "Command {}: `ArgAction::Version` used without providing Command::version or Command::long_version"
47 for sc
in cmd
.get_subcommands() {
48 if let Some(s
) = sc
.get_short_flag().as_ref() {
49 short_flags
.push(Flag
::Command(format
!("-{}", s
), sc
.get_name()));
52 for short_alias
in sc
.get_all_short_flag_aliases() {
53 short_flags
.push(Flag
::Command(format
!("-{}", short_alias
), sc
.get_name()));
56 if let Some(l
) = sc
.get_long_flag().as_ref() {
57 #[cfg(feature = "unstable-v4")]
59 assert
!(!l
.starts_with('
-'
), "Command {}: long_flag {:?} must not start with a `-`, that will be handled by the parser", sc
.get_name(), l
);
61 long_flags
.push(Flag
::Command(format
!("--{}", l
), sc
.get_name()));
64 for long_alias
in sc
.get_all_long_flag_aliases() {
65 long_flags
.push(Flag
::Command(format
!("--{}", long_alias
), sc
.get_name()));
69 for arg
in cmd
.get_arguments() {
73 !cmd
.is_multicall_set(),
74 "Command {}: Arguments like {} cannot be set on a multicall command",
79 if let Some(s
) = arg
.short
.as_ref() {
80 short_flags
.push(Flag
::Arg(format
!("-{}", s
), &*arg
.name
));
83 for (short_alias
, _
) in &arg
.short_aliases
{
84 short_flags
.push(Flag
::Arg(format
!("-{}", short_alias
), arg
.name
));
87 if let Some(l
) = arg
.long
.as_ref() {
88 #[cfg(feature = "unstable-v4")]
90 assert
!(!l
.starts_with('
-'
), "Argument {}: long {:?} must not start with a `-`, that will be handled by the parser", arg
.name
, l
);
92 long_flags
.push(Flag
::Arg(format
!("--{}", l
), &*arg
.name
));
95 for (long_alias
, _
) in &arg
.aliases
{
96 long_flags
.push(Flag
::Arg(format
!("--{}", long_alias
), arg
.name
));
101 cmd
.two_args_of(|x
| x
.id
== arg
.id
).is_none(),
102 "Command {}: Argument names must be unique, but '{}' is in use by more than one argument or group",
108 if let Some(l
) = arg
.long
{
109 if let Some((first
, second
)) = cmd
.two_args_of(|x
| x
.long
== Some(l
)) {
111 "Command {}: Long option names must be unique for each argument, \
112 but '--{}' is in use by both '{}' and '{}'",
122 if let Some(s
) = arg
.short
{
123 if let Some((first
, second
)) = cmd
.two_args_of(|x
| x
.short
== Some(s
)) {
125 "Command {}: Short option names must be unique for each argument, \
126 but '-{}' is in use by both '{}' and '{}'",
136 if let Some(idx
) = arg
.index
{
137 if let Some((first
, second
)) =
138 cmd
.two_args_of(|x
| x
.is_positional() && x
.index
== Some(idx
))
141 "Command {}: Argument '{}' has the same index as '{}' \
142 and they are both positional arguments\n\n\t \
143 Use Arg::multiple_values(true) to allow one \
144 positional argument to take multiple values",
152 // requires, r_if, r_unless
153 for req
in &arg
.requires
{
155 cmd
.id_exists(&req
.1),
156 "Command {}: Argument or group '{:?}' specified in 'requires*' for '{}' does not exist",
163 for req
in &arg
.r_ifs
{
164 #[cfg(feature = "unstable-v4")]
167 !arg
.is_required_set(),
168 "Argument {}: `required` conflicts with `required_if_eq*`",
173 cmd
.id_exists(&req
.0),
174 "Command {}: Argument or group '{:?}' specified in 'required_if_eq*' for '{}' does not exist",
181 for req
in &arg
.r_ifs_all
{
182 #[cfg(feature = "unstable-v4")]
185 !arg
.is_required_set(),
186 "Argument {}: `required` conflicts with `required_if_eq_all`",
191 cmd
.id_exists(&req
.0),
192 "Command {}: Argument or group '{:?}' specified in 'required_if_eq_all' for '{}' does not exist",
199 for req
in &arg
.r_unless
{
200 #[cfg(feature = "unstable-v4")]
203 !arg
.is_required_set(),
204 "Argument {}: `required` conflicts with `required_unless*`",
210 "Command {}: Argument or group '{:?}' specified in 'required_unless*' for '{}' does not exist",
217 for req
in &arg
.r_unless_all
{
218 #[cfg(feature = "unstable-v4")]
221 !arg
.is_required_set(),
222 "Argument {}: `required` conflicts with `required_unless*`",
228 "Command {}: Argument or group '{:?}' specified in 'required_unless*' for '{}' does not exist",
236 for req
in &arg
.blacklist
{
239 "Command {}: Argument or group '{:?}' specified in 'conflicts_with*' for '{}' does not exist",
246 if arg
.is_last_set() {
249 "Command {}: Flags or Options cannot have last(true) set. '{}' has both a long and last(true) set.",
255 "Command {}: Flags or Options cannot have last(true) set. '{}' has both a short and last(true) set.",
262 !(arg
.is_required_set() && arg
.is_global_set()),
263 "Command {}: Global arguments cannot be required.\n\n\t'{}' is marked as both global and required",
270 arg
.validator
.is_none() || arg
.validator_os
.is_none(),
271 "Command {}: Argument '{}' has both `validator` and `validator_os` set which is not allowed",
276 if arg
.get_value_hint() == ValueHint
::CommandWithArguments
{
279 "Command {}: Argument '{}' has hint CommandWithArguments and must be positional.",
285 cmd
.is_trailing_var_arg_set(),
286 "Command {}: Positional argument '{}' has hint CommandWithArguments, so Command must have TrailingVarArg set.",
293 for group
in cmd
.get_groups() {
296 cmd
.get_groups().filter(|x
| x
.id
== group
.id
).count() < 2,
297 "Command {}: Argument group name must be unique\n\n\t'{}' is already in use",
302 // Groups should not have naming conflicts with Args
304 !cmd
.get_arguments().any(|x
| x
.id
== group
.id
),
305 "Command {}: Argument group name '{}' must not conflict with argument name",
310 for arg
in &group
.args
{
311 // Args listed inside groups should exist
313 cmd
.get_arguments().any(|x
| x
.id
== *arg
),
314 "Command {}: Argument group '{}' contains non-existent argument '{:?}'",
322 // Conflicts between flags and subcommands
324 long_flags
.sort_unstable();
325 short_flags
.sort_unstable();
327 detect_duplicate_flags(&long_flags
, "long");
328 detect_duplicate_flags(&short_flags
, "short");
330 _verify_positionals(cmd
);
332 if let Some(help_template
) = cmd
.get_help_template() {
334 !help_template
.contains("{flags}"),
337 "`{flags}` template variable was removed in clap3, they are now included in `{options}`",
340 !help_template
.contains("{unified}"),
343 "`{unified}` template variable was removed in clap3, use `{options}` instead"
347 cmd
._panic_on_missing_help(cmd
.is_help_expected_set());
348 assert_app_flags(cmd
);
353 Command(String
, &'a
str),
354 Arg(String
, &'a
str),
357 impl PartialEq
for Flag
<'_
> {
358 fn eq(&self, other
: &Flag
) -> bool
{
359 self.cmp(other
) == Ordering
::Equal
363 impl PartialOrd
for Flag
<'_
> {
364 fn partial_cmp(&self, other
: &Flag
) -> Option
<Ordering
> {
367 match (self, other
) {
368 (Command(s1
, _
), Command(s2
, _
))
369 | (Arg(s1
, _
), Arg(s2
, _
))
370 | (Command(s1
, _
), Arg(s2
, _
))
371 | (Arg(s1
, _
), Command(s2
, _
)) => {
373 Some(Ordering
::Equal
)
382 impl Ord
for Flag
<'_
> {
383 fn cmp(&self, other
: &Self) -> Ordering
{
384 self.partial_cmp(other
).unwrap()
388 fn detect_duplicate_flags(flags
: &[Flag
], short_or_long
: &str) {
391 for (one
, two
) in find_duplicates(flags
) {
393 (Command(flag
, one
), Command(_
, another
)) if one
!= another
=> panic
!(
394 "the '{}' {} flag is specified for both '{}' and '{}' subcommands",
395 flag
, short_or_long
, one
, another
398 (Arg(flag
, one
), Arg(_
, another
)) if one
!= another
=> panic
!(
399 "{} option names must be unique, but '{}' is in use by both '{}' and '{}'",
400 short_or_long
, flag
, one
, another
403 (Arg(flag
, arg
), Command(_
, sub
)) | (Command(flag
, sub
), Arg(_
, arg
)) => panic
!(
404 "the '{}' {} flag for the '{}' argument conflicts with the short flag \
405 for '{}' subcommand",
406 flag
, short_or_long
, arg
, sub
414 /// Find duplicates in a sorted array.
416 /// The algorithm is simple: the array is sorted, duplicates
417 /// must be placed next to each other, we can check only adjacent elements.
418 fn find_duplicates
<T
: PartialEq
>(slice
: &[T
]) -> impl Iterator
<Item
= (&T
, &T
)> {
419 slice
.windows(2).filter_map(|w
| {
428 fn assert_app_flags(cmd
: &Command
) {
429 macro_rules
! checker
{
430 ($a
:ident requires $
($b
:ident
)|+) => {
432 let mut s
= String
::new();
436 s
.push_str(&format
!(" AppSettings::{} is required when AppSettings::{} is set.\n", std
::stringify
!($b
), std
::stringify
!($a
)));
445 ($a
:ident conflicts $
($b
:ident
)|+) => {
447 let mut s
= String
::new();
451 s
.push_str(&format
!(" AppSettings::{} conflicts with AppSettings::{}.\n", std
::stringify
!($b
), std
::stringify
!($a
)));
456 panic
!("{}\n{}", cmd
.get_name(), s
)
462 checker
!(is_allow_invalid_utf8_for_external_subcommands_set requires is_allow_external_subcommands_set
);
463 checker
!(is_multicall_set conflicts is_no_binary_name_set
);
466 #[cfg(debug_assertions)]
467 fn _verify_positionals(cmd
: &Command
) -> bool
{
468 debug
!("Command::_verify_positionals");
469 // Because you must wait until all arguments have been supplied, this is the first chance
470 // to make assertions on positional argument indexes
472 // First we verify that the index highest supplied index, is equal to the number of
473 // positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3
476 let highest_idx
= cmd
480 if let KeyType
::Position(n
) = x
{
489 let num_p
= cmd
.get_keymap().keys().filter(|x
| x
.is_position()).count();
492 highest_idx
== num_p
,
493 "Found positional argument whose index is {} but there \
494 are only {} positional arguments defined",
499 // Next we verify that only the highest index has takes multiple arguments (if any)
500 let only_highest
= |a
: &Arg
| a
.is_multiple() && (a
.index
.unwrap_or(0) != highest_idx
);
501 if cmd
.get_positionals().any(only_highest
) {
502 // First we make sure if there is a positional that allows multiple values
503 // the one before it (second to last) has one of these:
504 // * a value terminator
505 // * ArgSettings::Last
506 // * The last arg is Required
508 // We can't pass the closure (it.next()) to the macro directly because each call to
509 // find() (iterator, not macro) gets called repeatedly.
510 let last
= &cmd
.get_keymap()[&KeyType
::Position(highest_idx
)];
511 let second_to_last
= &cmd
.get_keymap()[&KeyType
::Position(highest_idx
- 1)];
513 // Either the final positional is required
514 // Or the second to last has a terminator or .last(true) set
515 let ok
= last
.is_required_set()
516 || (second_to_last
.terminator
.is_some() || second_to_last
.is_last_set())
517 || last
.is_last_set();
520 "When using a positional argument with .multiple_values(true) that is *not the \
521 last* positional argument, the last positional argument (i.e. the one \
522 with the highest index) *must* have .required(true) or .last(true) set."
525 // We make sure if the second to last is Multiple the last is ArgSettings::Last
526 let ok
= second_to_last
.is_multiple() || last
.is_last_set();
529 "Only the last positional argument, or second to last positional \
530 argument may be set to .multiple_values(true)"
533 // Next we check how many have both Multiple and not a specific number of values set
539 p
.is_multiple_occurrences_set()
540 || (p
.is_multiple_values_set() && p
.num_vals
.is_none())
545 || (last
.is_last_set()
546 && last
.is_multiple()
547 && second_to_last
.is_multiple()
551 "Only one positional argument with .multiple_values(true) set is allowed per \
552 command, unless the second one also has .last(true) set"
556 let mut found
= false;
558 if cmd
.is_allow_missing_positional_set() {
559 // Check that if a required positional argument is found, all positions with a lower
560 // index are also required.
561 let mut foundx2
= false;
563 for p
in cmd
.get_positionals() {
564 if foundx2
&& !p
.is_required_set() {
567 "Found non-required positional argument with a lower \
568 index than a required positional argument by two or more: {:?} \
573 } else if p
.is_required_set() && !p
.is_last_set() {
574 // Args that .last(true) don't count since they can be required and have
575 // positionals with a lower index that aren't required
576 // Imagine: prog <req1> [opt1] -- <req2>
577 // Both of these are valid invocations:
579 // $ prog r1 o1 -- r2
591 // Check that if a required positional argument is found, all positions with a lower
592 // index are also required
593 for p
in (1..=num_p
).rev().filter_map(|n
| cmd
.get_keymap().get(&n
)) {
597 "Found non-required positional argument with a lower \
598 index than a required positional argument: {:?} index {:?}",
602 } else if p
.is_required_set() && !p
.is_last_set() {
603 // Args that .last(true) don't count since they can be required and have
604 // positionals with a lower index that aren't required
605 // Imagine: prog <req1> [opt1] -- <req2>
606 // Both of these are valid invocations:
608 // $ prog r1 o1 -- r2
615 cmd
.get_positionals().filter(|p
| p
.is_last_set()).count() < 2,
616 "Only one positional argument may have last(true) set. Found two."
620 .any(|p
| p
.is_last_set() && p
.is_required_set())
621 && cmd
.has_subcommands()
622 && !cmd
.is_subcommand_negates_reqs_set()
625 "Having a required positional argument with .last(true) set *and* child \
626 subcommands without setting SubcommandsNegateReqs isn't compatible."
633 fn assert_arg(arg
: &Arg
) {
634 debug
!("Arg::_debug_asserts:{}", arg
.name
);
637 // TODO: this check should be recursive
639 !arg
.blacklist
.iter().any(|x
| *x
== arg
.id
),
640 "Argument '{}' cannot conflict with itself",
645 arg
.get_action().takes_values(),
646 arg
.is_takes_value_set(),
647 "Argument `{}`'s selected action {:?} contradicts `takes_value`",
651 if let Some(action_type_id
) = arg
.get_action().value_type_id() {
654 arg
.get_value_parser().type_id(),
655 "Argument `{}`'s selected action {:?} contradicts `value_parser` ({:?})",
658 arg
.get_value_parser()
662 if arg
.get_value_hint() != ValueHint
::Unknown
{
664 arg
.is_takes_value_set(),
665 "Argument '{}' has value hint but takes no value",
669 if arg
.get_value_hint() == ValueHint
::CommandWithArguments
{
671 arg
.is_multiple_values_set(),
672 "Argument '{}' uses hint CommandWithArguments and must accept multiple values",
678 if arg
.index
.is_some() {
681 "Argument '{}' is a positional argument and can't have short or long name versions",
685 arg
.is_takes_value_set(),
686 "Argument '{}` is positional, it must take a value",
691 #[cfg(feature = "unstable-v4")]
693 let num_vals
= arg
.get_num_vals().unwrap_or(usize::MAX
);
694 let num_val_names
= arg
.get_value_names().unwrap_or(&[]).len();
695 if num_vals
< num_val_names
{
697 "Argument {}: Too many value names ({}) compared to number_of_values ({})",
698 arg
.name
, num_val_names
, num_vals
703 assert_arg_flags(arg
);
705 assert_defaults(arg
, "default_value", arg
.default_vals
.iter().copied());
708 "default_missing_value",
709 arg
.default_missing_vals
.iter().copied(),
716 .filter_map(|(_
, _
, default)| *default),
720 fn assert_arg_flags(arg
: &Arg
) {
721 macro_rules
! checker
{
722 ($a
:ident requires $
($b
:ident
)|+) => {
724 let mut s
= String
::new();
728 s
.push_str(&format
!(" Arg::{} is required when Arg::{} is set.\n", std
::stringify
!($b
), std
::stringify
!($a
)));
733 panic
!("Argument {:?}\n{}", arg
.get_id(), s
)
739 checker
!(is_require_value_delimiter_set requires is_takes_value_set
);
740 checker
!(is_require_value_delimiter_set requires is_use_value_delimiter_set
);
741 checker
!(is_hide_possible_values_set requires is_takes_value_set
);
742 checker
!(is_allow_hyphen_values_set requires is_takes_value_set
);
743 checker
!(is_require_equals_set requires is_takes_value_set
);
744 checker
!(is_last_set requires is_takes_value_set
);
745 checker
!(is_hide_default_value_set requires is_takes_value_set
);
746 checker
!(is_multiple_values_set requires is_takes_value_set
);
747 checker
!(is_ignore_case_set requires is_takes_value_set
);
749 #![allow(deprecated)]
750 checker
!(is_forbid_empty_values_set requires is_takes_value_set
);
751 checker
!(is_allow_invalid_utf8_set requires is_takes_value_set
);
755 fn assert_defaults
<'d
>(
758 defaults
: impl IntoIterator
<Item
= &'d std
::ffi
::OsStr
>,
760 for default_os
in defaults
{
761 if let Some(default_s
) = default_os
.to_str() {
762 if !arg
.possible_vals
.is_empty() {
763 if let Some(delim
) = arg
.get_value_delimiter() {
764 for part
in default_s
.split(delim
) {
766 arg
.possible_vals
.iter().any(|possible_val
| {
767 possible_val
.matches(part
, arg
.is_ignore_case_set())
769 "Argument `{}`'s {}={} doesn't match possible values",
777 arg
.possible_vals
.iter().any(|possible_val
| {
778 possible_val
.matches(default_s
, arg
.is_ignore_case_set())
780 "Argument `{}`'s {}={} doesn't match possible values",
788 if let Some(validator
) = arg
.validator
.as_ref() {
789 let mut validator
= validator
.lock().unwrap();
790 if let Some(delim
) = arg
.get_value_delimiter() {
791 for part
in default_s
.split(delim
) {
792 if let Err(err
) = validator(part
) {
794 "Argument `{}`'s {}={} failed validation: {}",
795 arg
.name
, field
, part
, err
799 } else if let Err(err
) = validator(default_s
) {
801 "Argument `{}`'s {}={} failed validation: {}",
802 arg
.name
, field
, default_s
, err
808 if let Some(validator
) = arg
.validator_os
.as_ref() {
809 let mut validator
= validator
.lock().unwrap();
810 if let Some(delim
) = arg
.get_value_delimiter() {
811 let default_os
= RawOsStr
::new(default_os
);
812 for part
in default_os
.split(delim
) {
813 if let Err(err
) = validator(&part
.to_os_str()) {
815 "Argument `{}`'s {}={:?} failed validation: {}",
816 arg
.name
, field
, part
, err
820 } else if let Err(err
) = validator(default_os
) {
822 "Argument `{}`'s {}={:?} failed validation: {}",
823 arg
.name
, field
, default_os
, err
828 let value_parser
= arg
.get_value_parser();
829 let assert_cmd
= Command
::new("assert");
830 if let Some(delim
) = arg
.get_value_delimiter() {
831 let default_os
= RawOsStr
::new(default_os
);
832 for part
in default_os
.split(delim
) {
833 if let Err(err
) = value_parser
.parse_ref(&assert_cmd
, Some(arg
), &part
.to_os_str())
836 "Argument `{}`'s {}={:?} failed validation: {}",
844 } else if let Err(err
) = value_parser
.parse_ref(&assert_cmd
, Some(arg
), default_os
) {
846 "Argument `{}`'s {}={:?} failed validation: {}",
847 arg
.name
, field
, default_os
, err