1 use crate::{
2 build::arg::{debug_asserts::assert_arg, ArgProvider},
3 mkeymap::KeyType,
4 util::Id,
5 App, AppSettings, Arg, ArgSettings, ValueHint,
6 };
7 use std::cmp::Ordering;
8
assert_app(app: &App)9 pub(crate) fn assert_app(app: &App) {
10 debug!("App::_debug_asserts");
11
12 let mut short_flags = vec![];
13 let mut long_flags = vec![];
14
15 // Invalid version flag settings
16 if app.version.is_none() && app.long_version.is_none() {
17 // PropagateVersion is meaningless if there is no version
18 assert!(
19 !app.settings.is_set(AppSettings::PropagateVersion),
20 "App {}: No version information via App::version or App::long_version to propagate",
21 app.get_name(),
22 );
23
24 // Used `App::mut_arg("version", ..) but did not provide any version information to display
25 let has_mutated_version = app
26 .args
27 .args()
28 .any(|x| x.id == Id::version_hash() && x.provider == ArgProvider::GeneratedMutated);
29
30 if has_mutated_version {
31 assert!(app.settings.is_set(AppSettings::NoAutoVersion),
32 "App {}: Used App::mut_arg(\"version\", ..) without providing App::version, App::long_version or using AppSettings::NoAutoVersion"
33 ,app.get_name()
34 );
35 }
36 }
37
38 for sc in &app.subcommands {
39 if let Some(s) = sc.short_flag.as_ref() {
40 short_flags.push(Flag::App(format!("-{}", s), &sc.name));
41 }
42
43 for (short_alias, _) in &sc.short_flag_aliases {
44 short_flags.push(Flag::App(format!("-{}", short_alias), &sc.name));
45 }
46
47 if let Some(l) = sc.long_flag.as_ref() {
48 long_flags.push(Flag::App(format!("--{}", l), &sc.name));
49 }
50
51 for (long_alias, _) in &sc.long_flag_aliases {
52 long_flags.push(Flag::App(format!("--{}", long_alias), &sc.name));
53 }
54 }
55
56 for arg in app.args.args() {
57 assert_arg(arg);
58
59 if let Some(s) = arg.short.as_ref() {
60 short_flags.push(Flag::Arg(format!("-{}", s), &*arg.name));
61 }
62
63 for (short_alias, _) in &arg.short_aliases {
64 short_flags.push(Flag::Arg(format!("-{}", short_alias), arg.name));
65 }
66
67 if let Some(l) = arg.long.as_ref() {
68 long_flags.push(Flag::Arg(format!("--{}", l), &*arg.name));
69 }
70
71 for (long_alias, _) in &arg.aliases {
72 long_flags.push(Flag::Arg(format!("--{}", long_alias), arg.name));
73 }
74
75 // Name conflicts
76 assert!(
77 app.two_args_of(|x| x.id == arg.id).is_none(),
78 "App {}: Argument names must be unique, but '{}' is in use by more than one argument or group",
79 app.get_name(),
80 arg.name,
81 );
82
83 // Long conflicts
84 if let Some(l) = arg.long {
85 if let Some((first, second)) = app.two_args_of(|x| x.long == Some(l)) {
86 panic!(
87 "App {}: Long option names must be unique for each argument, \
88 but '--{}' is in use by both '{}' and '{}'",
89 app.get_name(),
90 l,
91 first.name,
92 second.name
93 )
94 }
95 }
96
97 // Short conflicts
98 if let Some(s) = arg.short {
99 if let Some((first, second)) = app.two_args_of(|x| x.short == Some(s)) {
100 panic!(
101 "App {}: Short option names must be unique for each argument, \
102 but '-{}' is in use by both '{}' and '{}'",
103 app.get_name(),
104 s,
105 first.name,
106 second.name
107 )
108 }
109 }
110
111 // Index conflicts
112 if let Some(idx) = arg.index {
113 if let Some((first, second)) =
114 app.two_args_of(|x| x.is_positional() && x.index == Some(idx))
115 {
116 panic!(
117 "App {}: Argument '{}' has the same index as '{}' \
118 and they are both positional arguments\n\n\t \
119 Use Arg::multiple_values(true) to allow one \
120 positional argument to take multiple values",
121 app.get_name(),
122 first.name,
123 second.name
124 )
125 }
126 }
127
128 // requires, r_if, r_unless
129 for req in &arg.requires {
130 assert!(
131 app.id_exists(&req.1),
132 "App {}: Argument or group '{:?}' specified in 'requires*' for '{}' does not exist",
133 app.get_name(),
134 req.1,
135 arg.name,
136 );
137 }
138
139 for req in &arg.r_ifs {
140 assert!(
141 app.id_exists(&req.0),
142 "App {}: Argument or group '{:?}' specified in 'required_if_eq*' for '{}' does not exist",
143 app.get_name(),
144 req.0,
145 arg.name
146 );
147 }
148
149 for req in &arg.r_ifs_all {
150 assert!(
151 app.id_exists(&req.0),
152 "App {}: Argument or group '{:?}' specified in 'required_if_eq_all' for '{}' does not exist",
153 app.get_name(),
154 req.0,
155 arg.name
156 );
157 }
158
159 for req in &arg.r_unless {
160 assert!(
161 app.id_exists(req),
162 "App {}: Argument or group '{:?}' specified in 'required_unless*' for '{}' does not exist",
163 app.get_name(),
164 req,
165 arg.name,
166 );
167 }
168
169 // blacklist
170 for req in &arg.blacklist {
171 assert!(
172 app.id_exists(req),
173 "App {}: Argument or group '{:?}' specified in 'conflicts_with*' for '{}' does not exist",
174 app.get_name(),
175 req,
176 arg.name,
177 );
178 }
179
180 if arg.is_set(ArgSettings::Last) {
181 assert!(
182 arg.long.is_none(),
183 "App {}: Flags or Options cannot have last(true) set. '{}' has both a long and last(true) set.",
184 app.get_name(),
185 arg.name
186 );
187 assert!(
188 arg.short.is_none(),
189 "App {}: Flags or Options cannot have last(true) set. '{}' has both a short and last(true) set.",
190 app.get_name(),
191 arg.name
192 );
193 }
194
195 assert!(
196 !(arg.is_set(ArgSettings::Required) && arg.get_global()),
197 "App {}: Global arguments cannot be required.\n\n\t'{}' is marked as both global and required",
198 app.get_name(),
199 arg.name
200 );
201
202 // validators
203 assert!(
204 arg.validator.is_none() || arg.validator_os.is_none(),
205 "App {}: Argument '{}' has both `validator` and `validator_os` set which is not allowed",
206 app.get_name(),
207 arg.name
208 );
209
210 if arg.value_hint == ValueHint::CommandWithArguments {
211 assert!(
212 arg.is_positional(),
213 "App {}: Argument '{}' has hint CommandWithArguments and must be positional.",
214 app.get_name(),
215 arg.name
216 );
217
218 assert!(
219 app.is_set(AppSettings::TrailingVarArg),
220 "App {}: Positional argument '{}' has hint CommandWithArguments, so App must have TrailingVarArg set.",
221 app.get_name(),
222 arg.name
223 );
224 }
225 }
226
227 for group in &app.groups {
228 // Name conflicts
229 assert!(
230 app.groups.iter().filter(|x| x.id == group.id).count() < 2,
231 "App {}: Argument group name must be unique\n\n\t'{}' is already in use",
232 app.get_name(),
233 group.name,
234 );
235
236 // Groups should not have naming conflicts with Args
237 assert!(
238 !app.args.args().any(|x| x.id == group.id),
239 "App {}: Argument group name '{}' must not conflict with argument name",
240 app.get_name(),
241 group.name,
242 );
243
244 // Required groups should have at least one arg without default values
245 if group.required && !group.args.is_empty() {
246 assert!(
247 group.args.iter().any(|arg| {
248 app.args
249 .args()
250 .any(|x| x.id == *arg && x.default_vals.is_empty())
251 }),
252 "App {}: Argument group '{}' is required but all of it's arguments have a default value.",
253 app.get_name(),
254 group.name
255 )
256 }
257
258 for arg in &group.args {
259 // Args listed inside groups should exist
260 assert!(
261 app.args.args().any(|x| x.id == *arg),
262 "App {}: Argument group '{}' contains non-existent argument '{:?}'",
263 app.get_name(),
264 group.name,
265 arg
266 );
267 }
268 }
269
270 // Conflicts between flags and subcommands
271
272 long_flags.sort_unstable();
273 short_flags.sort_unstable();
274
275 detect_duplicate_flags(&long_flags, "long");
276 detect_duplicate_flags(&short_flags, "short");
277
278 _verify_positionals(app);
279
280 if let Some(help_template) = app.template {
281 assert!(
282 !help_template.contains("{flags}"),
283 "App {}: {}",
284 app.get_name(),
285 "`{flags}` template variable was removed in clap3, they are now included in `{options}`",
286 );
287 assert!(
288 !help_template.contains("{unified}"),
289 "App {}: {}",
290 app.get_name(),
291 "`{unified}` template variable was removed in clap3, use `{options}` instead"
292 );
293 }
294
295 app._panic_on_missing_help(app.g_settings.is_set(AppSettings::HelpExpected));
296 assert_app_flags(app);
297 }
298
299 #[derive(Eq)]
300 enum Flag<'a> {
301 App(String, &'a str),
302 Arg(String, &'a str),
303 }
304
305 impl PartialEq for Flag<'_> {
eq(&self, other: &Flag) -> bool306 fn eq(&self, other: &Flag) -> bool {
307 self.cmp(other) == Ordering::Equal
308 }
309 }
310
311 impl PartialOrd for Flag<'_> {
partial_cmp(&self, other: &Flag) -> Option<Ordering>312 fn partial_cmp(&self, other: &Flag) -> Option<Ordering> {
313 use Flag::*;
314
315 match (self, other) {
316 (App(s1, _), App(s2, _))
317 | (Arg(s1, _), Arg(s2, _))
318 | (App(s1, _), Arg(s2, _))
319 | (Arg(s1, _), App(s2, _)) => {
320 if s1 == s2 {
321 Some(Ordering::Equal)
322 } else {
323 s1.partial_cmp(s2)
324 }
325 }
326 }
327 }
328 }
329
330 impl Ord for Flag<'_> {
cmp(&self, other: &Self) -> Ordering331 fn cmp(&self, other: &Self) -> Ordering {
332 self.partial_cmp(other).unwrap()
333 }
334 }
335
detect_duplicate_flags(flags: &[Flag], short_or_long: &str)336 fn detect_duplicate_flags(flags: &[Flag], short_or_long: &str) {
337 use Flag::*;
338
339 for (one, two) in find_duplicates(flags) {
340 match (one, two) {
341 (App(flag, one), App(_, another)) if one != another => panic!(
342 "the '{}' {} flag is specified for both '{}' and '{}' subcommands",
343 flag, short_or_long, one, another
344 ),
345
346 (Arg(flag, one), Arg(_, another)) if one != another => panic!(
347 "{} option names must be unique, but '{}' is in use by both '{}' and '{}'",
348 short_or_long, flag, one, another
349 ),
350
351 (Arg(flag, arg), App(_, sub)) | (App(flag, sub), Arg(_, arg)) => panic!(
352 "the '{}' {} flag for the '{}' argument conflicts with the short flag \
353 for '{}' subcommand",
354 flag, short_or_long, arg, sub
355 ),
356
357 _ => {}
358 }
359 }
360 }
361
362 /// Find duplicates in a sorted array.
363 ///
364 /// The algorithm is simple: the array is sorted, duplicates
365 /// must be placed next to each other, we can check only adjacent elements.
find_duplicates<T: PartialEq>(slice: &[T]) -> impl Iterator<Item = (&T, &T)>366 fn find_duplicates<T: PartialEq>(slice: &[T]) -> impl Iterator<Item = (&T, &T)> {
367 slice.windows(2).filter_map(|w| {
368 if w[0] == w[1] {
369 Some((&w[0], &w[1]))
370 } else {
371 None
372 }
373 })
374 }
375
assert_app_flags(app: &App)376 fn assert_app_flags(app: &App) {
377 use AppSettings::*;
378
379 macro_rules! checker {
380 ($a:ident requires $($b:ident)|+) => {
381 if app.is_set($a) {
382 let mut s = String::new();
383
384 $(
385 if !app.is_set($b) {
386 s.push_str(&format!(" AppSettings::{} is required when AppSettings::{} is set.\n", std::stringify!($b), std::stringify!($a)));
387 }
388 )+
389
390 if !s.is_empty() {
391 panic!("{}", s)
392 }
393 }
394 };
395 ($a:ident conflicts $($b:ident)|+) => {
396 if app.is_set($a) {
397 let mut s = String::new();
398
399 $(
400 if app.is_set($b) {
401 s.push_str(&format!(" AppSettings::{} conflicts with AppSettings::{}.\n", std::stringify!($b), std::stringify!($a)));
402 }
403 )+
404
405 if !s.is_empty() {
406 panic!("{}\n{}", app.get_name(), s)
407 }
408 }
409 };
410 }
411
412 checker!(AllowInvalidUtf8ForExternalSubcommands requires AllowExternalSubcommands);
413 #[cfg(feature = "unstable-multicall")]
414 checker!(Multicall conflicts NoBinaryName);
415 }
416
417 #[cfg(debug_assertions)]
_verify_positionals(app: &App) -> bool418 fn _verify_positionals(app: &App) -> bool {
419 debug!("App::_verify_positionals");
420 // Because you must wait until all arguments have been supplied, this is the first chance
421 // to make assertions on positional argument indexes
422 //
423 // First we verify that the index highest supplied index, is equal to the number of
424 // positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3
425 // but no 2)
426
427 let highest_idx = app
428 .args
429 .keys()
430 .filter_map(|x| {
431 if let KeyType::Position(n) = x {
432 Some(*n)
433 } else {
434 None
435 }
436 })
437 .max()
438 .unwrap_or(0);
439
440 let num_p = app.args.keys().filter(|x| x.is_position()).count();
441
442 assert!(
443 highest_idx == num_p,
444 "Found positional argument whose index is {} but there \
445 are only {} positional arguments defined",
446 highest_idx,
447 num_p
448 );
449
450 // Next we verify that only the highest index has takes multiple arguments (if any)
451 let only_highest = |a: &Arg| a.is_multiple() && (a.index.unwrap_or(0) != highest_idx);
452 if app.get_positionals().any(only_highest) {
453 // First we make sure if there is a positional that allows multiple values
454 // the one before it (second to last) has one of these:
455 // * a value terminator
456 // * ArgSettings::Last
457 // * The last arg is Required
458
459 // We can't pass the closure (it.next()) to the macro directly because each call to
460 // find() (iterator, not macro) gets called repeatedly.
461 let last = &app.args[&KeyType::Position(highest_idx)];
462 let second_to_last = &app.args[&KeyType::Position(highest_idx - 1)];
463
464 // Either the final positional is required
465 // Or the second to last has a terminator or .last(true) set
466 let ok = last.is_set(ArgSettings::Required)
467 || (second_to_last.terminator.is_some() || second_to_last.is_set(ArgSettings::Last))
468 || last.is_set(ArgSettings::Last);
469 assert!(
470 ok,
471 "When using a positional argument with .multiple_values(true) that is *not the \
472 last* positional argument, the last positional argument (i.e. the one \
473 with the highest index) *must* have .required(true) or .last(true) set."
474 );
475
476 // We make sure if the second to last is Multiple the last is ArgSettings::Last
477 let ok = second_to_last.is_multiple() || last.is_set(ArgSettings::Last);
478 assert!(
479 ok,
480 "Only the last positional argument, or second to last positional \
481 argument may be set to .multiple_values(true)"
482 );
483
484 // Next we check how many have both Multiple and not a specific number of values set
485 let count = app
486 .get_positionals()
487 .filter(|p| {
488 p.settings.is_set(ArgSettings::MultipleOccurrences)
489 || (p.settings.is_set(ArgSettings::MultipleValues) && p.num_vals.is_none())
490 })
491 .count();
492 let ok = count <= 1
493 || (last.is_set(ArgSettings::Last)
494 && last.is_multiple()
495 && second_to_last.is_multiple()
496 && count == 2);
497 assert!(
498 ok,
499 "Only one positional argument with .multiple_values(true) set is allowed per \
500 command, unless the second one also has .last(true) set"
501 );
502 }
503
504 let mut found = false;
505
506 if app.is_set(AppSettings::AllowMissingPositional) {
507 // Check that if a required positional argument is found, all positions with a lower
508 // index are also required.
509 let mut foundx2 = false;
510
511 for p in app.get_positionals() {
512 if foundx2 && !p.is_set(ArgSettings::Required) {
513 assert!(
514 p.is_set(ArgSettings::Required),
515 "Found non-required positional argument with a lower \
516 index than a required positional argument by two or more: {:?} \
517 index {:?}",
518 p.name,
519 p.index
520 );
521 } else if p.is_set(ArgSettings::Required) && !p.is_set(ArgSettings::Last) {
522 // Args that .last(true) don't count since they can be required and have
523 // positionals with a lower index that aren't required
524 // Imagine: prog <req1> [opt1] -- <req2>
525 // Both of these are valid invocations:
526 // $ prog r1 -- r2
527 // $ prog r1 o1 -- r2
528 if found {
529 foundx2 = true;
530 continue;
531 }
532 found = true;
533 continue;
534 } else {
535 found = false;
536 }
537 }
538 } else {
539 // Check that if a required positional argument is found, all positions with a lower
540 // index are also required
541 for p in (1..=num_p).rev().filter_map(|n| app.args.get(&n)) {
542 if found {
543 assert!(
544 p.is_set(ArgSettings::Required),
545 "Found non-required positional argument with a lower \
546 index than a required positional argument: {:?} index {:?}",
547 p.name,
548 p.index
549 );
550 } else if p.is_set(ArgSettings::Required) && !p.is_set(ArgSettings::Last) {
551 // Args that .last(true) don't count since they can be required and have
552 // positionals with a lower index that aren't required
553 // Imagine: prog <req1> [opt1] -- <req2>
554 // Both of these are valid invocations:
555 // $ prog r1 -- r2
556 // $ prog r1 o1 -- r2
557 found = true;
558 continue;
559 }
560 }
561 }
562 assert!(
563 app.get_positionals()
564 .filter(|p| p.is_set(ArgSettings::Last))
565 .count()
566 < 2,
567 "Only one positional argument may have last(true) set. Found two."
568 );
569 if app
570 .get_positionals()
571 .any(|p| p.is_set(ArgSettings::Last) && p.is_set(ArgSettings::Required))
572 && app.has_subcommands()
573 && !app.is_set(AppSettings::SubcommandsNegateReqs)
574 {
575 panic!(
576 "Having a required positional argument with .last(true) set *and* child \
577 subcommands without setting SubcommandsNegateReqs isn't compatible."
578 );
579 }
580
581 true
582 }
583