1 use std
::cmp
::Ordering
;
3 use clap_lex
::OsStrExt
as _
;
5 use crate::builder
::OsStr
;
6 use crate::builder
::ValueRange
;
7 use crate::mkeymap
::KeyType
;
8 use crate::util
::FlatSet
;
11 use crate::INTERNAL_ERROR_MSG
;
12 use crate::{Arg, Command, ValueHint}
;
14 pub(crate) fn assert_app(cmd
: &Command
) {
15 debug
!("Command::_debug_asserts");
17 let mut short_flags
= vec
![];
18 let mut long_flags
= vec
![];
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
24 !cmd
.is_propagate_version_set(),
25 "Command {}: No version information via Command::version or Command::long_version to propagate",
29 // Used `Command::mut_arg("version", ..) but did not provide any version information to display
30 let version_needed
= cmd
32 .filter(|x
| matches
!(x
.get_action(), ArgAction
::Version
))
36 assert_eq
!(version_needed
, Vec
::<&str>::new(), "Command {}: `ArgAction::Version` used without providing Command::version or Command::long_version"
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()));
46 for short_alias
in sc
.get_all_short_flag_aliases() {
47 short_flags
.push(Flag
::Command(format
!("-{short_alias}"), sc
.get_name()));
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()));
55 for long_alias
in sc
.get_all_long_flag_aliases() {
56 long_flags
.push(Flag
::Command(format
!("--{long_alias}"), sc
.get_name()));
60 for arg
in cmd
.get_arguments() {
64 !cmd
.is_multicall_set(),
65 "Command {}: Arguments like {} cannot be set on a multicall command",
70 if let Some(s
) = arg
.get_short() {
71 short_flags
.push(Flag
::Arg(format
!("-{s}"), arg
.get_id().as_str()));
74 for (short_alias
, _
) in &arg
.short_aliases
{
75 short_flags
.push(Flag
::Arg(format
!("-{short_alias}"), arg
.get_id().as_str()));
78 if let Some(l
) = arg
.get_long() {
79 assert
!(!l
.starts_with('
-'
), "Argument {}: long {:?} must not start with a `-`, that will be handled by the parser", arg
.get_id(), l
);
80 long_flags
.push(Flag
::Arg(format
!("--{l}"), arg
.get_id().as_str()));
83 for (long_alias
, _
) in &arg
.aliases
{
84 long_flags
.push(Flag
::Arg(format
!("--{long_alias}"), arg
.get_id().as_str()));
88 if let Some((first
, second
)) = cmd
.two_args_of(|x
| x
.get_id() == arg
.get_id()) {
90 "Command {}: Argument names must be unique, but '{}' is in use by more than one argument or group{}",
93 duplicate_tip(cmd
, first
, second
),
98 if let Some(l
) = arg
.get_long() {
99 if let Some((first
, second
)) = cmd
.two_args_of(|x
| x
.get_long() == Some(l
)) {
101 "Command {}: Long option names must be unique for each argument, \
102 but '--{}' is in use by both '{}' and '{}'{}",
107 duplicate_tip(cmd
, first
, second
)
113 if let Some(s
) = arg
.get_short() {
114 if let Some((first
, second
)) = cmd
.two_args_of(|x
| x
.get_short() == Some(s
)) {
116 "Command {}: Short option names must be unique for each argument, \
117 but '-{}' is in use by both '{}' and '{}'{}",
122 duplicate_tip(cmd
, first
, second
),
128 if let Some(idx
) = arg
.index
{
129 if let Some((first
, second
)) =
130 cmd
.two_args_of(|x
| x
.is_positional() && x
.get_index() == Some(idx
))
133 "Command {}: Argument '{}' has the same index as '{}' \
134 and they are both positional arguments\n\n\t \
135 Use `Arg::num_args(1..)` to allow one \
136 positional argument to take multiple values",
144 // requires, r_if, r_unless
145 for req
in &arg
.requires
{
147 cmd
.id_exists(&req
.1),
148 "Command {}: Argument or group '{}' specified in 'requires*' for '{}' does not exist",
155 for req
in &arg
.r_ifs
{
157 !arg
.is_required_set(),
158 "Argument {}: `required` conflicts with `required_if_eq*`",
162 cmd
.id_exists(&req
.0),
163 "Command {}: Argument or group '{}' specified in 'required_if_eq*' for '{}' does not exist",
170 for req
in &arg
.r_ifs_all
{
172 !arg
.is_required_set(),
173 "Argument {}: `required` conflicts with `required_if_eq_all`",
177 cmd
.id_exists(&req
.0),
178 "Command {}: Argument or group '{}' specified in 'required_if_eq_all' for '{}' does not exist",
185 for req
in &arg
.r_unless
{
187 !arg
.is_required_set(),
188 "Argument {}: `required` conflicts with `required_unless*`",
193 "Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist",
200 for req
in &arg
.r_unless_all
{
202 !arg
.is_required_set(),
203 "Argument {}: `required` conflicts with `required_unless*`",
208 "Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist",
216 for req
in &arg
.blacklist
{
219 "Command {}: Argument or group '{}' specified in 'conflicts_with*' for '{}' does not exist",
227 for req
in &arg
.overrides
{
230 "Command {}: Argument or group '{}' specified in 'overrides_with*' for '{}' does not exist",
237 if arg
.is_last_set() {
239 arg
.get_long().is_none(),
240 "Command {}: Flags or Options cannot have last(true) set. '{}' has both a long and last(true) set.",
245 arg
.get_short().is_none(),
246 "Command {}: Flags or Options cannot have last(true) set. '{}' has both a short and last(true) set.",
253 !(arg
.is_required_set() && arg
.is_global_set()),
254 "Command {}: Global arguments cannot be required.\n\n\t'{}' is marked as both global and required",
259 if arg
.get_value_hint() == ValueHint
::CommandWithArguments
{
262 "Command {}: Argument '{}' has hint CommandWithArguments and must be positional.",
268 arg
.is_trailing_var_arg_set() || arg
.is_last_set(),
269 "Command {}: Positional argument '{}' has hint CommandWithArguments, so Command must have `trailing_var_arg(true)` or `last(true)` set.",
276 for group
in cmd
.get_groups() {
279 cmd
.get_groups().filter(|x
| x
.id
== group
.id
).count() < 2,
280 "Command {}: Argument group name must be unique\n\n\t'{}' is already in use",
285 // Groups should not have naming conflicts with Args
287 !cmd
.get_arguments().any(|x
| x
.get_id() == group
.get_id()),
288 "Command {}: Argument group name '{}' must not conflict with argument name",
293 for arg
in &group
.args
{
294 // Args listed inside groups should exist
296 cmd
.get_arguments().any(|x
| x
.get_id() == arg
),
297 "Command {}: Argument group '{}' contains non-existent argument '{}'",
304 for arg
in &group
.requires
{
305 // Args listed inside groups should exist
308 "Command {}: Argument group '{}' requires non-existent '{}' id",
315 for arg
in &group
.conflicts
{
316 // Args listed inside groups should exist
319 "Command {}: Argument group '{}' conflicts with non-existent '{}' id",
327 // Conflicts between flags and subcommands
329 long_flags
.sort_unstable();
330 short_flags
.sort_unstable();
332 detect_duplicate_flags(&long_flags
, "long");
333 detect_duplicate_flags(&short_flags
, "short");
335 let mut subs
= FlatSet
::new();
336 for sc
in cmd
.get_subcommands() {
338 subs
.insert(sc
.get_name()),
339 "Command {}: command name `{}` is duplicated",
343 for alias
in sc
.get_all_aliases() {
346 "Command {}: command `{}` alias `{}` is duplicated",
354 _verify_positionals(cmd
);
356 #[cfg(feature = "help")]
357 if let Some(help_template
) = cmd
.get_help_template() {
359 !help_template
.to_string().contains("{flags}"),
362 "`{flags}` template variable was removed in clap3, they are now included in `{options}`",
365 !help_template
.to_string().contains("{unified}"),
368 "`{unified}` template variable was removed in clap3, use `{options}` instead"
370 #[cfg(feature = "unstable-v5")]
372 !help_template
.to_string().contains("{bin}"),
375 "`{bin}` template variable was removed in clap5, use `{name}` instead"
379 cmd
._panic_on_missing_help(cmd
.is_help_expected_set());
380 assert_app_flags(cmd
);
383 fn duplicate_tip(cmd
: &Command
, first
: &Arg
, second
: &Arg
) -> &'
static str {
384 if !cmd
.is_disable_help_flag_set()
385 && (first
.get_id() == Id
::HELP
|| second
.get_id() == Id
::HELP
)
387 " (call `cmd.disable_help_flag(true)` to remove the auto-generated `--help`)"
388 } else if !cmd
.is_disable_version_flag_set()
389 && (first
.get_id() == Id
::VERSION
|| second
.get_id() == Id
::VERSION
)
391 " (call `cmd.disable_version_flag(true)` to remove the auto-generated `--version`)"
399 Command(String
, &'a
str),
400 Arg(String
, &'a
str),
403 impl PartialEq
for Flag
<'_
> {
404 fn eq(&self, other
: &Flag
) -> bool
{
405 self.cmp(other
) == Ordering
::Equal
409 impl PartialOrd
for Flag
<'_
> {
410 fn partial_cmp(&self, other
: &Flag
) -> Option
<Ordering
> {
413 match (self, other
) {
414 (Command(s1
, _
), Command(s2
, _
))
415 | (Arg(s1
, _
), Arg(s2
, _
))
416 | (Command(s1
, _
), Arg(s2
, _
))
417 | (Arg(s1
, _
), Command(s2
, _
)) => {
419 Some(Ordering
::Equal
)
428 impl Ord
for Flag
<'_
> {
429 fn cmp(&self, other
: &Self) -> Ordering
{
430 self.partial_cmp(other
).unwrap()
434 fn detect_duplicate_flags(flags
: &[Flag
], short_or_long
: &str) {
437 for (one
, two
) in find_duplicates(flags
) {
439 (Command(flag
, one
), Command(_
, another
)) if one
!= another
=> panic
!(
440 "the '{flag}' {short_or_long} flag is specified for both '{one}' and '{another}' subcommands"
443 (Arg(flag
, one
), Arg(_
, another
)) if one
!= another
=> panic
!(
444 "{short_or_long} option names must be unique, but '{flag}' is in use by both '{one}' and '{another}'"
447 (Arg(flag
, arg
), Command(_
, sub
)) | (Command(flag
, sub
), Arg(_
, arg
)) => panic
!(
448 "the '{flag}' {short_or_long} flag for the '{arg}' argument conflicts with the short flag \
449 for '{sub}' subcommand"
457 /// Find duplicates in a sorted array.
459 /// The algorithm is simple: the array is sorted, duplicates
460 /// must be placed next to each other, we can check only adjacent elements.
461 fn find_duplicates
<T
: PartialEq
>(slice
: &[T
]) -> impl Iterator
<Item
= (&T
, &T
)> {
462 slice
.windows(2).filter_map(|w
| {
471 fn assert_app_flags(cmd
: &Command
) {
472 macro_rules
! checker
{
473 ($a
:ident requires $
($b
:ident
)|+) => {
475 let mut s
= String
::new();
480 write
!(&mut s
, " AppSettings::{} is required when AppSettings::{} is set.\n", std
::stringify
!($b
), std
::stringify
!($a
)).unwrap();
489 ($a
:ident conflicts $
($b
:ident
)|+) => {
491 let mut s
= String
::new();
496 write
!(&mut s
, " AppSettings::{} conflicts with AppSettings::{}.\n", std
::stringify
!($b
), std
::stringify
!($a
)).unwrap();
501 panic
!("{}\n{}", cmd
.get_name(), s
)
507 checker
!(is_multicall_set conflicts is_no_binary_name_set
);
510 #[cfg(debug_assertions)]
511 fn _verify_positionals(cmd
: &Command
) -> bool
{
512 debug
!("Command::_verify_positionals");
513 // Because you must wait until all arguments have been supplied, this is the first chance
514 // to make assertions on positional argument indexes
516 // First we verify that the index highest supplied index, is equal to the number of
517 // positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3
520 let highest_idx
= cmd
524 if let KeyType
::Position(n
) = x
{
533 let num_p
= cmd
.get_keymap().keys().filter(|x
| x
.is_position()).count();
536 highest_idx
== num_p
,
537 "Found positional argument whose index is {highest_idx} but there \
538 are only {num_p} positional arguments defined",
541 for arg
in cmd
.get_arguments() {
542 if arg
.index
.unwrap_or(0) == highest_idx
{
544 !arg
.is_trailing_var_arg_set() || !arg
.is_last_set(),
545 "{}:{}: `Arg::trailing_var_arg` and `Arg::last` cannot be used together",
550 if arg
.is_trailing_var_arg_set() {
553 "{}:{}: `Arg::trailing_var_arg` must accept multiple values",
560 !arg
.is_trailing_var_arg_set(),
561 "{}:{}: `Arg::trailing_var_arg` can only apply to last positional",
568 // Next we verify that only the highest index has takes multiple arguments (if any)
569 let only_highest
= |a
: &Arg
| a
.is_multiple() && (a
.get_index().unwrap_or(0) != highest_idx
);
570 if cmd
.get_positionals().any(only_highest
) {
571 // First we make sure if there is a positional that allows multiple values
572 // the one before it (second to last) has one of these:
573 // * a value terminator
574 // * ArgSettings::Last
575 // * The last arg is Required
577 // We can't pass the closure (it.next()) to the macro directly because each call to
578 // find() (iterator, not macro) gets called repeatedly.
579 let last
= &cmd
.get_keymap()[&KeyType
::Position(highest_idx
)];
580 let second_to_last
= &cmd
.get_keymap()[&KeyType
::Position(highest_idx
- 1)];
582 // Either the final positional is required
583 // Or the second to last has a terminator or .last(true) set
584 let ok
= last
.is_required_set()
585 || (second_to_last
.terminator
.is_some() || second_to_last
.is_last_set())
586 || last
.is_last_set();
589 "Positional argument `{last}` *must* have `required(true)` or `last(true)` set \
590 because a prior positional argument (`{second_to_last}`) has `num_args(1..)`"
593 // We make sure if the second to last is Multiple the last is ArgSettings::Last
594 let ok
= second_to_last
.is_multiple() || last
.is_last_set();
597 "Only the last positional argument, or second to last positional \
598 argument may be set to `.num_args(1..)`"
601 // Next we check how many have both Multiple and not a specific number of values set
605 p
.is_multiple_values_set()
606 && p
.get_value_terminator().is_none()
607 && !p
.get_num_args().expect(INTERNAL_ERROR_MSG
).is_fixed()
611 || (last
.is_last_set()
612 && last
.is_multiple()
613 && second_to_last
.is_multiple()
617 "Only one positional argument with `.num_args(1..)` set is allowed per \
618 command, unless the second one also has .last(true) set"
622 let mut found
= false;
624 if cmd
.is_allow_missing_positional_set() {
625 // Check that if a required positional argument is found, all positions with a lower
626 // index are also required.
627 let mut foundx2
= false;
629 for p
in cmd
.get_positionals() {
630 if foundx2
&& !p
.is_required_set() {
633 "Found non-required positional argument with a lower \
634 index than a required positional argument by two or more: {:?} \
639 } else if p
.is_required_set() && !p
.is_last_set() {
640 // Args that .last(true) don't count since they can be required and have
641 // positionals with a lower index that aren't required
642 // Imagine: prog <req1> [opt1] -- <req2>
643 // Both of these are valid invocations:
645 // $ prog r1 o1 -- r2
657 // Check that if a required positional argument is found, all positions with a lower
658 // index are also required
659 for p
in (1..=num_p
).rev().filter_map(|n
| cmd
.get_keymap().get(&n
)) {
663 "Found non-required positional argument with a lower \
664 index than a required positional argument: {:?} index {:?}",
668 } else if p
.is_required_set() && !p
.is_last_set() {
669 // Args that .last(true) don't count since they can be required and have
670 // positionals with a lower index that aren't required
671 // Imagine: prog <req1> [opt1] -- <req2>
672 // Both of these are valid invocations:
674 // $ prog r1 o1 -- r2
681 cmd
.get_positionals().filter(|p
| p
.is_last_set()).count() < 2,
682 "Only one positional argument may have last(true) set. Found two."
686 .any(|p
| p
.is_last_set() && p
.is_required_set())
687 && cmd
.has_subcommands()
688 && !cmd
.is_subcommand_negates_reqs_set()
691 "Having a required positional argument with .last(true) set *and* child \
692 subcommands without setting SubcommandsNegateReqs isn't compatible."
699 fn assert_arg(arg
: &Arg
) {
700 debug
!("Arg::_debug_asserts:{}", arg
.get_id());
703 // TODO: this check should be recursive
705 !arg
.blacklist
.iter().any(|x
| x
== arg
.get_id()),
706 "Argument '{}' cannot conflict with itself",
711 arg
.get_action().takes_values(),
712 arg
.is_takes_value_set(),
713 "Argument `{}`'s selected action {:?} contradicts `takes_value`",
717 if let Some(action_type_id
) = arg
.get_action().value_type_id() {
720 arg
.get_value_parser().type_id(),
721 "Argument `{}`'s selected action {:?} contradicts `value_parser` ({:?})",
724 arg
.get_value_parser()
728 if arg
.get_value_hint() != ValueHint
::Unknown
{
730 arg
.is_takes_value_set(),
731 "Argument '{}' has value hint but takes no value",
735 if arg
.get_value_hint() == ValueHint
::CommandWithArguments
{
737 arg
.is_multiple_values_set(),
738 "Argument '{}' uses hint CommandWithArguments and must accept multiple values",
744 if arg
.index
.is_some() {
747 "Argument '{}' is a positional argument and can't have short or long name versions",
751 arg
.is_takes_value_set(),
752 "Argument '{}` is positional, it must take a value{}",
754 if arg
.get_id() == Id
::HELP
{
755 " (`mut_arg` no longer works with implicit `--help`)"
756 } else if arg
.get_id() == Id
::VERSION
{
757 " (`mut_arg` no longer works with implicit `--version`)"
764 let num_vals
= arg
.get_num_args().expect(INTERNAL_ERROR_MSG
);
765 // This can be the cause of later asserts, so put this first
766 if num_vals
!= ValueRange
::EMPTY
{
767 // HACK: Don't check for flags to make the derive easier
768 let num_val_names
= arg
.get_value_names().unwrap_or(&[]).len();
769 if num_vals
.max_values() < num_val_names
{
771 "Argument {}: Too many value names ({}) compared to `num_args` ({})",
780 num_vals
.takes_values(),
781 arg
.is_takes_value_set(),
782 "Argument {}: mismatch between `num_args` ({}) and `takes_value`",
787 num_vals
.is_multiple(),
788 arg
.is_multiple_values_set(),
789 "Argument {}: mismatch between `num_args` ({}) and `multiple_values`",
794 if 1 < num_vals
.min_values() {
796 !arg
.is_require_equals_set(),
797 "Argument {}: cannot accept more than 1 arg (num_args={}) with require_equals",
803 if num_vals
== ValueRange
::SINGLE
{
805 !arg
.is_multiple_values_set(),
806 "Argument {}: mismatch between `num_args` and `multiple_values`",
811 assert_arg_flags(arg
);
813 assert_defaults(arg
, "default_value", arg
.default_vals
.iter());
816 "default_missing_value",
817 arg
.default_missing_vals
.iter(),
824 .filter_map(|(_
, _
, default)| default.as_ref()),
828 fn assert_arg_flags(arg
: &Arg
) {
829 macro_rules
! checker
{
830 ($a
:ident requires $
($b
:ident
)|+) => {
832 let mut s
= String
::new();
837 write
!(&mut s
, " Arg::{} is required when Arg::{} is set.\n", std
::stringify
!($b
), std
::stringify
!($a
)).unwrap();
842 panic
!("Argument {:?}\n{}", arg
.get_id(), s
)
848 checker
!(is_hide_possible_values_set requires is_takes_value_set
);
849 checker
!(is_allow_hyphen_values_set requires is_takes_value_set
);
850 checker
!(is_allow_negative_numbers_set requires is_takes_value_set
);
851 checker
!(is_require_equals_set requires is_takes_value_set
);
852 checker
!(is_last_set requires is_takes_value_set
);
853 checker
!(is_hide_default_value_set requires is_takes_value_set
);
854 checker
!(is_multiple_values_set requires is_takes_value_set
);
855 checker
!(is_ignore_case_set requires is_takes_value_set
);
858 fn assert_defaults
<'d
>(
861 defaults
: impl IntoIterator
<Item
= &'d OsStr
>,
863 for default_os
in defaults
{
864 let value_parser
= arg
.get_value_parser();
865 let assert_cmd
= Command
::new("assert");
866 if let Some(val_delim
) = arg
.get_value_delimiter() {
867 let mut val_delim_buffer
= [0; 4];
868 let val_delim
= val_delim
.encode_utf8(&mut val_delim_buffer
);
869 for part
in default_os
.split(val_delim
) {
870 if let Err(err
) = value_parser
.parse_ref(&assert_cmd
, Some(arg
), part
) {
872 "Argument `{}`'s {}={:?} failed validation: {}",
875 part
.to_string_lossy(),
880 } else if let Err(err
) = value_parser
.parse_ref(&assert_cmd
, Some(arg
), default_os
) {
882 "Argument `{}`'s {}={:?} failed validation: {}",