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