]>
Commit | Line | Data |
---|---|---|
add651ee FG |
1 | use std::cmp::Ordering; |
2 | ||
3 | use clap_lex::OsStrExt as _; | |
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 | ||
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(format!("-{short_alias}"), arg.get_id().as_str())); | |
76 | } | |
77 | ||
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())); | |
81 | } | |
82 | ||
83 | for (long_alias, _) in &arg.aliases { | |
84 | long_flags.push(Flag::Arg(format!("--{long_alias}"), arg.get_id().as_str())); | |
85 | } | |
86 | ||
87 | // Name conflicts | |
88 | if let Some((first, second)) = cmd.two_args_of(|x| x.get_id() == arg.get_id()) { | |
89 | panic!( | |
90 | "Command {}: Argument names must be unique, but '{}' is in use by more than one argument or group{}", | |
91 | cmd.get_name(), | |
92 | arg.get_id(), | |
93 | duplicate_tip(cmd, first, second), | |
94 | ); | |
95 | } | |
96 | ||
97 | // Long conflicts | |
98 | if let Some(l) = arg.get_long() { | |
99 | if let Some((first, second)) = cmd.two_args_of(|x| x.get_long() == Some(l)) { | |
100 | panic!( | |
101 | "Command {}: Long option names must be unique for each argument, \ | |
102 | but '--{}' is in use by both '{}' and '{}'{}", | |
103 | cmd.get_name(), | |
104 | l, | |
105 | first.get_id(), | |
106 | second.get_id(), | |
107 | duplicate_tip(cmd, first, second) | |
108 | ) | |
109 | } | |
110 | } | |
111 | ||
112 | // Short conflicts | |
113 | if let Some(s) = arg.get_short() { | |
114 | if let Some((first, second)) = cmd.two_args_of(|x| x.get_short() == Some(s)) { | |
115 | panic!( | |
116 | "Command {}: Short option names must be unique for each argument, \ | |
117 | but '-{}' is in use by both '{}' and '{}'{}", | |
118 | cmd.get_name(), | |
119 | s, | |
120 | first.get_id(), | |
121 | second.get_id(), | |
122 | duplicate_tip(cmd, first, second), | |
123 | ) | |
124 | } | |
125 | } | |
126 | ||
127 | // Index conflicts | |
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)) | |
131 | { | |
132 | panic!( | |
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", | |
137 | cmd.get_name(), | |
138 | first.get_id(), | |
139 | second.get_id() | |
140 | ) | |
141 | } | |
142 | } | |
143 | ||
144 | // requires, r_if, r_unless | |
145 | for req in &arg.requires { | |
146 | assert!( | |
147 | cmd.id_exists(&req.1), | |
148 | "Command {}: Argument or group '{}' specified in 'requires*' for '{}' does not exist", | |
149 | cmd.get_name(), | |
150 | req.1, | |
151 | arg.get_id(), | |
152 | ); | |
153 | } | |
154 | ||
155 | for req in &arg.r_ifs { | |
156 | assert!( | |
157 | !arg.is_required_set(), | |
158 | "Argument {}: `required` conflicts with `required_if_eq*`", | |
159 | arg.get_id() | |
160 | ); | |
161 | assert!( | |
162 | cmd.id_exists(&req.0), | |
163 | "Command {}: Argument or group '{}' specified in 'required_if_eq*' for '{}' does not exist", | |
164 | cmd.get_name(), | |
165 | req.0, | |
166 | arg.get_id() | |
167 | ); | |
168 | } | |
169 | ||
170 | for req in &arg.r_ifs_all { | |
171 | assert!( | |
172 | !arg.is_required_set(), | |
173 | "Argument {}: `required` conflicts with `required_if_eq_all`", | |
174 | arg.get_id() | |
175 | ); | |
176 | assert!( | |
177 | cmd.id_exists(&req.0), | |
178 | "Command {}: Argument or group '{}' specified in 'required_if_eq_all' for '{}' does not exist", | |
179 | cmd.get_name(), | |
180 | req.0, | |
181 | arg.get_id() | |
182 | ); | |
183 | } | |
184 | ||
185 | for req in &arg.r_unless { | |
186 | assert!( | |
187 | !arg.is_required_set(), | |
188 | "Argument {}: `required` conflicts with `required_unless*`", | |
189 | arg.get_id() | |
190 | ); | |
191 | assert!( | |
192 | cmd.id_exists(req), | |
193 | "Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist", | |
194 | cmd.get_name(), | |
195 | req, | |
196 | arg.get_id(), | |
197 | ); | |
198 | } | |
199 | ||
200 | for req in &arg.r_unless_all { | |
201 | assert!( | |
202 | !arg.is_required_set(), | |
203 | "Argument {}: `required` conflicts with `required_unless*`", | |
204 | arg.get_id() | |
205 | ); | |
206 | assert!( | |
207 | cmd.id_exists(req), | |
208 | "Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist", | |
209 | cmd.get_name(), | |
210 | req, | |
211 | arg.get_id(), | |
212 | ); | |
213 | } | |
214 | ||
215 | // blacklist | |
216 | for req in &arg.blacklist { | |
217 | assert!( | |
218 | cmd.id_exists(req), | |
219 | "Command {}: Argument or group '{}' specified in 'conflicts_with*' for '{}' does not exist", | |
220 | cmd.get_name(), | |
221 | req, | |
222 | arg.get_id(), | |
223 | ); | |
224 | } | |
225 | ||
226 | // overrides | |
227 | for req in &arg.overrides { | |
228 | assert!( | |
229 | cmd.id_exists(req), | |
230 | "Command {}: Argument or group '{}' specified in 'overrides_with*' for '{}' does not exist", | |
231 | cmd.get_name(), | |
232 | req, | |
233 | arg.get_id(), | |
234 | ); | |
235 | } | |
236 | ||
237 | if arg.is_last_set() { | |
238 | assert!( | |
239 | arg.get_long().is_none(), | |
240 | "Command {}: Flags or Options cannot have last(true) set. '{}' has both a long and last(true) set.", | |
241 | cmd.get_name(), | |
242 | arg.get_id() | |
243 | ); | |
244 | assert!( | |
245 | arg.get_short().is_none(), | |
246 | "Command {}: Flags or Options cannot have last(true) set. '{}' has both a short and last(true) set.", | |
247 | cmd.get_name(), | |
248 | arg.get_id() | |
249 | ); | |
250 | } | |
251 | ||
252 | assert!( | |
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", | |
255 | cmd.get_name(), | |
256 | arg.get_id() | |
257 | ); | |
258 | ||
259 | if arg.get_value_hint() == ValueHint::CommandWithArguments { | |
260 | assert!( | |
261 | arg.is_positional(), | |
262 | "Command {}: Argument '{}' has hint CommandWithArguments and must be positional.", | |
263 | cmd.get_name(), | |
264 | arg.get_id() | |
265 | ); | |
266 | ||
267 | assert!( | |
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.", | |
270 | cmd.get_name(), | |
271 | arg.get_id() | |
272 | ); | |
273 | } | |
274 | } | |
275 | ||
276 | for group in cmd.get_groups() { | |
277 | // Name conflicts | |
278 | assert!( | |
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", | |
281 | cmd.get_name(), | |
282 | group.get_id(), | |
283 | ); | |
284 | ||
285 | // Groups should not have naming conflicts with Args | |
286 | assert!( | |
287 | !cmd.get_arguments().any(|x| x.get_id() == group.get_id()), | |
288 | "Command {}: Argument group name '{}' must not conflict with argument name", | |
289 | cmd.get_name(), | |
290 | group.get_id(), | |
291 | ); | |
292 | ||
293 | for arg in &group.args { | |
294 | // Args listed inside groups should exist | |
295 | assert!( | |
296 | cmd.get_arguments().any(|x| x.get_id() == arg), | |
297 | "Command {}: Argument group '{}' contains non-existent argument '{}'", | |
298 | cmd.get_name(), | |
299 | group.get_id(), | |
300 | arg | |
301 | ); | |
302 | } | |
303 | ||
304 | for arg in &group.requires { | |
305 | // Args listed inside groups should exist | |
306 | assert!( | |
307 | cmd.id_exists(arg), | |
308 | "Command {}: Argument group '{}' requires non-existent '{}' id", | |
309 | cmd.get_name(), | |
310 | group.get_id(), | |
311 | arg | |
312 | ); | |
313 | } | |
314 | ||
315 | for arg in &group.conflicts { | |
316 | // Args listed inside groups should exist | |
317 | assert!( | |
318 | cmd.id_exists(arg), | |
319 | "Command {}: Argument group '{}' conflicts with non-existent '{}' id", | |
320 | cmd.get_name(), | |
321 | group.get_id(), | |
322 | arg | |
323 | ); | |
324 | } | |
325 | } | |
326 | ||
327 | // Conflicts between flags and subcommands | |
328 | ||
329 | long_flags.sort_unstable(); | |
330 | short_flags.sort_unstable(); | |
331 | ||
332 | detect_duplicate_flags(&long_flags, "long"); | |
333 | detect_duplicate_flags(&short_flags, "short"); | |
334 | ||
335 | let mut subs = FlatSet::new(); | |
336 | for sc in cmd.get_subcommands() { | |
337 | assert!( | |
338 | subs.insert(sc.get_name()), | |
339 | "Command {}: command name `{}` is duplicated", | |
340 | cmd.get_name(), | |
341 | sc.get_name() | |
342 | ); | |
343 | for alias in sc.get_all_aliases() { | |
344 | assert!( | |
345 | subs.insert(alias), | |
346 | "Command {}: command `{}` alias `{}` is duplicated", | |
347 | cmd.get_name(), | |
348 | sc.get_name(), | |
349 | alias | |
350 | ); | |
351 | } | |
352 | } | |
353 | ||
354 | _verify_positionals(cmd); | |
355 | ||
356 | #[cfg(feature = "help")] | |
357 | if let Some(help_template) = cmd.get_help_template() { | |
358 | assert!( | |
359 | !help_template.to_string().contains("{flags}"), | |
360 | "Command {}: {}", | |
361 | cmd.get_name(), | |
362 | "`{flags}` template variable was removed in clap3, they are now included in `{options}`", | |
363 | ); | |
364 | assert!( | |
365 | !help_template.to_string().contains("{unified}"), | |
366 | "Command {}: {}", | |
367 | cmd.get_name(), | |
368 | "`{unified}` template variable was removed in clap3, use `{options}` instead" | |
369 | ); | |
370 | #[cfg(feature = "unstable-v5")] | |
371 | assert!( | |
372 | !help_template.to_string().contains("{bin}"), | |
373 | "Command {}: {}", | |
374 | cmd.get_name(), | |
375 | "`{bin}` template variable was removed in clap5, use `{name}` instead" | |
376 | ) | |
377 | } | |
378 | ||
379 | cmd._panic_on_missing_help(cmd.is_help_expected_set()); | |
380 | assert_app_flags(cmd); | |
381 | } | |
382 | ||
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) | |
386 | { | |
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) | |
390 | { | |
391 | " (call `cmd.disable_version_flag(true)` to remove the auto-generated `--version`)" | |
392 | } else { | |
393 | "" | |
394 | } | |
395 | } | |
396 | ||
397 | #[derive(Eq)] | |
398 | enum Flag<'a> { | |
399 | Command(String, &'a str), | |
400 | Arg(String, &'a str), | |
401 | } | |
402 | ||
403 | impl PartialEq for Flag<'_> { | |
404 | fn eq(&self, other: &Flag) -> bool { | |
405 | self.cmp(other) == Ordering::Equal | |
406 | } | |
407 | } | |
408 | ||
409 | impl PartialOrd for Flag<'_> { | |
410 | fn partial_cmp(&self, other: &Flag) -> Option<Ordering> { | |
411 | use Flag::*; | |
412 | ||
413 | match (self, other) { | |
414 | (Command(s1, _), Command(s2, _)) | |
415 | | (Arg(s1, _), Arg(s2, _)) | |
416 | | (Command(s1, _), Arg(s2, _)) | |
417 | | (Arg(s1, _), Command(s2, _)) => { | |
418 | if s1 == s2 { | |
419 | Some(Ordering::Equal) | |
420 | } else { | |
421 | s1.partial_cmp(s2) | |
422 | } | |
423 | } | |
424 | } | |
425 | } | |
426 | } | |
427 | ||
428 | impl Ord for Flag<'_> { | |
429 | fn cmp(&self, other: &Self) -> Ordering { | |
430 | self.partial_cmp(other).unwrap() | |
431 | } | |
432 | } | |
433 | ||
434 | fn detect_duplicate_flags(flags: &[Flag], short_or_long: &str) { | |
435 | use Flag::*; | |
436 | ||
437 | for (one, two) in find_duplicates(flags) { | |
438 | match (one, two) { | |
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" | |
441 | ), | |
442 | ||
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}'" | |
445 | ), | |
446 | ||
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" | |
450 | ), | |
451 | ||
452 | _ => {} | |
453 | } | |
454 | } | |
455 | } | |
456 | ||
457 | /// Find duplicates in a sorted array. | |
458 | /// | |
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| { | |
463 | if w[0] == w[1] { | |
464 | Some((&w[0], &w[1])) | |
465 | } else { | |
466 | None | |
467 | } | |
468 | }) | |
469 | } | |
470 | ||
471 | fn assert_app_flags(cmd: &Command) { | |
472 | macro_rules! checker { | |
473 | ($a:ident requires $($b:ident)|+) => { | |
474 | if cmd.$a() { | |
475 | let mut s = String::new(); | |
476 | ||
477 | $( | |
478 | if !cmd.$b() { | |
479 | use std::fmt::Write; | |
480 | write!(&mut s, " AppSettings::{} is required when AppSettings::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap(); | |
481 | } | |
482 | )+ | |
483 | ||
484 | if !s.is_empty() { | |
485 | panic!("{s}") | |
486 | } | |
487 | } | |
488 | }; | |
489 | ($a:ident conflicts $($b:ident)|+) => { | |
490 | if cmd.$a() { | |
491 | let mut s = String::new(); | |
492 | ||
493 | $( | |
494 | if cmd.$b() { | |
495 | use std::fmt::Write; | |
496 | write!(&mut s, " AppSettings::{} conflicts with AppSettings::{}.\n", std::stringify!($b), std::stringify!($a)).unwrap(); | |
497 | } | |
498 | )+ | |
499 | ||
500 | if !s.is_empty() { | |
501 | panic!("{}\n{}", cmd.get_name(), s) | |
502 | } | |
503 | } | |
504 | }; | |
505 | } | |
506 | ||
507 | checker!(is_multicall_set conflicts is_no_binary_name_set); | |
508 | } | |
509 | ||
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 | |
515 | // | |
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 | |
518 | // but no 2) | |
519 | ||
520 | let highest_idx = cmd | |
521 | .get_keymap() | |
522 | .keys() | |
523 | .filter_map(|x| { | |
524 | if let KeyType::Position(n) = x { | |
525 | Some(*n) | |
526 | } else { | |
527 | None | |
528 | } | |
529 | }) | |
530 | .max() | |
531 | .unwrap_or(0); | |
532 | ||
533 | let num_p = cmd.get_keymap().keys().filter(|x| x.is_position()).count(); | |
534 | ||
535 | assert!( | |
536 | highest_idx == num_p, | |
537 | "Found positional argument whose index is {highest_idx} but there \ | |
538 | are only {num_p} positional arguments defined", | |
539 | ); | |
540 | ||
541 | for arg in cmd.get_arguments() { | |
542 | if arg.index.unwrap_or(0) == highest_idx { | |
543 | assert!( | |
544 | !arg.is_trailing_var_arg_set() || !arg.is_last_set(), | |
545 | "{}:{}: `Arg::trailing_var_arg` and `Arg::last` cannot be used together", | |
546 | cmd.get_name(), | |
547 | arg.get_id() | |
548 | ); | |
549 | ||
550 | if arg.is_trailing_var_arg_set() { | |
551 | assert!( | |
552 | arg.is_multiple(), | |
553 | "{}:{}: `Arg::trailing_var_arg` must accept multiple values", | |
554 | cmd.get_name(), | |
555 | arg.get_id() | |
556 | ); | |
557 | } | |
558 | } else { | |
559 | assert!( | |
560 | !arg.is_trailing_var_arg_set(), | |
561 | "{}:{}: `Arg::trailing_var_arg` can only apply to last positional", | |
562 | cmd.get_name(), | |
563 | arg.get_id() | |
564 | ); | |
565 | } | |
566 | } | |
567 | ||
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 | |
576 | ||
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)]; | |
581 | ||
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(); | |
587 | assert!( | |
588 | ok, | |
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..)`" | |
591 | ); | |
592 | ||
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(); | |
595 | assert!( | |
596 | ok, | |
597 | "Only the last positional argument, or second to last positional \ | |
598 | argument may be set to `.num_args(1..)`" | |
599 | ); | |
600 | ||
601 | // Next we check how many have both Multiple and not a specific number of values set | |
602 | let count = cmd | |
603 | .get_positionals() | |
604 | .filter(|p| { | |
605 | p.is_multiple_values_set() | |
606 | && p.get_value_terminator().is_none() | |
607 | && !p.get_num_args().expect(INTERNAL_ERROR_MSG).is_fixed() | |
608 | }) | |
609 | .count(); | |
610 | let ok = count <= 1 | |
611 | || (last.is_last_set() | |
612 | && last.is_multiple() | |
613 | && second_to_last.is_multiple() | |
614 | && count == 2); | |
615 | assert!( | |
616 | ok, | |
617 | "Only one positional argument with `.num_args(1..)` set is allowed per \ | |
618 | command, unless the second one also has .last(true) set" | |
619 | ); | |
620 | } | |
621 | ||
622 | let mut found = false; | |
623 | ||
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; | |
628 | ||
629 | for p in cmd.get_positionals() { | |
630 | if foundx2 && !p.is_required_set() { | |
631 | assert!( | |
632 | p.is_required_set(), | |
633 | "Found non-required positional argument with a lower \ | |
634 | index than a required positional argument by two or more: {:?} \ | |
635 | index {:?}", | |
636 | p.get_id(), | |
637 | p.get_index() | |
638 | ); | |
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: | |
644 | // $ prog r1 -- r2 | |
645 | // $ prog r1 o1 -- r2 | |
646 | if found { | |
647 | foundx2 = true; | |
648 | continue; | |
649 | } | |
650 | found = true; | |
651 | continue; | |
652 | } else { | |
653 | found = false; | |
654 | } | |
655 | } | |
656 | } else { | |
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)) { | |
660 | if found { | |
661 | assert!( | |
662 | p.is_required_set(), | |
663 | "Found non-required positional argument with a lower \ | |
664 | index than a required positional argument: {:?} index {:?}", | |
665 | p.get_id(), | |
666 | p.get_index() | |
667 | ); | |
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: | |
673 | // $ prog r1 -- r2 | |
674 | // $ prog r1 o1 -- r2 | |
675 | found = true; | |
676 | continue; | |
677 | } | |
678 | } | |
679 | } | |
680 | assert!( | |
681 | cmd.get_positionals().filter(|p| p.is_last_set()).count() < 2, | |
682 | "Only one positional argument may have last(true) set. Found two." | |
683 | ); | |
684 | if cmd | |
685 | .get_positionals() | |
686 | .any(|p| p.is_last_set() && p.is_required_set()) | |
687 | && cmd.has_subcommands() | |
688 | && !cmd.is_subcommand_negates_reqs_set() | |
689 | { | |
690 | panic!( | |
691 | "Having a required positional argument with .last(true) set *and* child \ | |
692 | subcommands without setting SubcommandsNegateReqs isn't compatible." | |
693 | ); | |
694 | } | |
695 | ||
696 | true | |
697 | } | |
698 | ||
699 | fn assert_arg(arg: &Arg) { | |
700 | debug!("Arg::_debug_asserts:{}", arg.get_id()); | |
701 | ||
702 | // Self conflict | |
703 | // TODO: this check should be recursive | |
704 | assert!( | |
705 | !arg.blacklist.iter().any(|x| x == arg.get_id()), | |
706 | "Argument '{}' cannot conflict with itself", | |
707 | arg.get_id(), | |
708 | ); | |
709 | ||
710 | assert_eq!( | |
711 | arg.get_action().takes_values(), | |
712 | arg.is_takes_value_set(), | |
713 | "Argument `{}`'s selected action {:?} contradicts `takes_value`", | |
714 | arg.get_id(), | |
715 | arg.get_action() | |
716 | ); | |
717 | if let Some(action_type_id) = arg.get_action().value_type_id() { | |
718 | assert_eq!( | |
719 | action_type_id, | |
720 | arg.get_value_parser().type_id(), | |
721 | "Argument `{}`'s selected action {:?} contradicts `value_parser` ({:?})", | |
722 | arg.get_id(), | |
723 | arg.get_action(), | |
724 | arg.get_value_parser() | |
725 | ); | |
726 | } | |
727 | ||
728 | if arg.get_value_hint() != ValueHint::Unknown { | |
729 | assert!( | |
730 | arg.is_takes_value_set(), | |
731 | "Argument '{}' has value hint but takes no value", | |
732 | arg.get_id() | |
733 | ); | |
734 | ||
735 | if arg.get_value_hint() == ValueHint::CommandWithArguments { | |
736 | assert!( | |
737 | arg.is_multiple_values_set(), | |
738 | "Argument '{}' uses hint CommandWithArguments and must accept multiple values", | |
739 | arg.get_id() | |
740 | ) | |
741 | } | |
742 | } | |
743 | ||
744 | if arg.index.is_some() { | |
745 | assert!( | |
746 | arg.is_positional(), | |
747 | "Argument '{}' is a positional argument and can't have short or long name versions", | |
748 | arg.get_id() | |
749 | ); | |
750 | assert!( | |
751 | arg.is_takes_value_set(), | |
752 | "Argument '{}` is positional, it must take a value{}", | |
753 | arg.get_id(), | |
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`)" | |
758 | } else { | |
759 | "" | |
760 | } | |
761 | ); | |
762 | } | |
763 | ||
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 { | |
770 | panic!( | |
771 | "Argument {}: Too many value names ({}) compared to `num_args` ({})", | |
772 | arg.get_id(), | |
773 | num_val_names, | |
774 | num_vals | |
775 | ); | |
776 | } | |
777 | } | |
778 | ||
779 | assert_eq!( | |
780 | num_vals.takes_values(), | |
781 | arg.is_takes_value_set(), | |
782 | "Argument {}: mismatch between `num_args` ({}) and `takes_value`", | |
783 | arg.get_id(), | |
784 | num_vals, | |
785 | ); | |
786 | assert_eq!( | |
787 | num_vals.is_multiple(), | |
788 | arg.is_multiple_values_set(), | |
789 | "Argument {}: mismatch between `num_args` ({}) and `multiple_values`", | |
790 | arg.get_id(), | |
791 | num_vals, | |
792 | ); | |
793 | ||
794 | if 1 < num_vals.min_values() { | |
795 | assert!( | |
796 | !arg.is_require_equals_set(), | |
797 | "Argument {}: cannot accept more than 1 arg (num_args={}) with require_equals", | |
798 | arg.get_id(), | |
799 | num_vals | |
800 | ); | |
801 | } | |
802 | ||
803 | if num_vals == ValueRange::SINGLE { | |
804 | assert!( | |
805 | !arg.is_multiple_values_set(), | |
806 | "Argument {}: mismatch between `num_args` and `multiple_values`", | |
807 | arg.get_id() | |
808 | ); | |
809 | } | |
810 | ||
811 | assert_arg_flags(arg); | |
812 | ||
813 | assert_defaults(arg, "default_value", arg.default_vals.iter()); | |
814 | assert_defaults( | |
815 | arg, | |
816 | "default_missing_value", | |
817 | arg.default_missing_vals.iter(), | |
818 | ); | |
819 | assert_defaults( | |
820 | arg, | |
821 | "default_value_if", | |
822 | arg.default_vals_ifs | |
823 | .iter() | |
824 | .filter_map(|(_, _, default)| default.as_ref()), | |
825 | ); | |
826 | } | |
827 | ||
828 | fn assert_arg_flags(arg: &Arg) { | |
829 | macro_rules! checker { | |
830 | ($a:ident requires $($b:ident)|+) => { | |
831 | if arg.$a() { | |
832 | let mut s = String::new(); | |
833 | ||
834 | $( | |
835 | if !arg.$b() { | |
836 | use std::fmt::Write; | |
837 | write!(&mut s, " Arg::{} is required when Arg::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap(); | |
838 | } | |
839 | )+ | |
840 | ||
841 | if !s.is_empty() { | |
842 | panic!("Argument {:?}\n{}", arg.get_id(), s) | |
843 | } | |
844 | } | |
845 | } | |
846 | } | |
847 | ||
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); | |
856 | } | |
857 | ||
858 | fn assert_defaults<'d>( | |
859 | arg: &Arg, | |
860 | field: &'static str, | |
861 | defaults: impl IntoIterator<Item = &'d OsStr>, | |
862 | ) { | |
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) { | |
871 | panic!( | |
872 | "Argument `{}`'s {}={:?} failed validation: {}", | |
873 | arg.get_id(), | |
874 | field, | |
875 | part.to_string_lossy(), | |
876 | err | |
877 | ); | |
878 | } | |
879 | } | |
880 | } else if let Err(err) = value_parser.parse_ref(&assert_cmd, Some(arg), default_os) { | |
881 | panic!( | |
882 | "Argument `{}`'s {}={:?} failed validation: {}", | |
883 | arg.get_id(), | |
884 | field, | |
885 | default_os, | |
886 | err | |
887 | ); | |
888 | } | |
889 | } | |
890 | } |