1use std::cmp::Ordering; 2 3use clap_lex::RawOsStr; 4 5use crate::builder::OsStr; 6use crate::builder::ValueRange; 7use crate::mkeymap::KeyType; 8use crate::util::FlatSet; 9use crate::util::Id; 10use crate::ArgAction; 11use crate::INTERNAL_ERROR_MSG; 12use crate::{Arg, Command, ValueHint}; 13 14pub(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 let derive_hint = if cfg!(feature = "derive") { 278 " (note: `Args` implicitly creates `ArgGroup`s; disable with `#[group(skip)]`" 279 } else { 280 "" 281 }; 282 283 // Name conflicts 284 assert!( 285 cmd.get_groups().filter(|x| x.id == group.id).count() < 2, 286 "Command {}: Argument group name must be unique\n\n\t'{}' is already in use{}", 287 cmd.get_name(), 288 group.get_id(), 289 derive_hint 290 ); 291 292 // Groups should not have naming conflicts with Args 293 assert!( 294 !cmd.get_arguments().any(|x| x.get_id() == group.get_id()), 295 "Command {}: Argument group name '{}' must not conflict with argument name{}", 296 cmd.get_name(), 297 group.get_id(), 298 derive_hint 299 ); 300 301 for arg in &group.args { 302 // Args listed inside groups should exist 303 assert!( 304 cmd.get_arguments().any(|x| x.get_id() == arg), 305 "Command {}: Argument group '{}' contains non-existent argument '{}'", 306 cmd.get_name(), 307 group.get_id(), 308 arg 309 ); 310 } 311 } 312 313 // Conflicts between flags and subcommands 314 315 long_flags.sort_unstable(); 316 short_flags.sort_unstable(); 317 318 detect_duplicate_flags(&long_flags, "long"); 319 detect_duplicate_flags(&short_flags, "short"); 320 321 let mut subs = FlatSet::new(); 322 for sc in cmd.get_subcommands() { 323 assert!( 324 subs.insert(sc.get_name()), 325 "Command {}: command name `{}` is duplicated", 326 cmd.get_name(), 327 sc.get_name() 328 ); 329 for alias in sc.get_all_aliases() { 330 assert!( 331 subs.insert(alias), 332 "Command {}: command `{}` alias `{}` is duplicated", 333 cmd.get_name(), 334 sc.get_name(), 335 alias 336 ); 337 } 338 } 339 340 _verify_positionals(cmd); 341 342 #[cfg(feature = "help")] 343 if let Some(help_template) = cmd.get_help_template() { 344 assert!( 345 !help_template.to_string().contains("{flags}"), 346 "Command {}: {}", 347 cmd.get_name(), 348 "`{flags}` template variable was removed in clap3, they are now included in `{options}`", 349 ); 350 assert!( 351 !help_template.to_string().contains("{unified}"), 352 "Command {}: {}", 353 cmd.get_name(), 354 "`{unified}` template variable was removed in clap3, use `{options}` instead" 355 ); 356 #[cfg(feature = "unstable-v5")] 357 assert!( 358 !help_template.to_string().contains("{bin}"), 359 "Command {}: {}", 360 cmd.get_name(), 361 "`{bin}` template variable was removed in clap5, use `{name}` instead" 362 ) 363 } 364 365 cmd._panic_on_missing_help(cmd.is_help_expected_set()); 366 assert_app_flags(cmd); 367} 368 369fn duplicate_tip(cmd: &Command, first: &Arg, second: &Arg) -> &'static str { 370 if !cmd.is_disable_help_flag_set() 371 && (first.get_id() == Id::HELP || second.get_id() == Id::HELP) 372 { 373 " (call `cmd.disable_help_flag(true)` to remove the auto-generated `--help`)" 374 } else if !cmd.is_disable_version_flag_set() 375 && (first.get_id() == Id::VERSION || second.get_id() == Id::VERSION) 376 { 377 " (call `cmd.disable_version_flag(true)` to remove the auto-generated `--version`)" 378 } else { 379 "" 380 } 381} 382 383#[derive(Eq)] 384enum Flag<'a> { 385 Command(String, &'a str), 386 Arg(String, &'a str), 387} 388 389impl PartialEq for Flag<'_> { 390 fn eq(&self, other: &Flag) -> bool { 391 self.cmp(other) == Ordering::Equal 392 } 393} 394 395impl PartialOrd for Flag<'_> { 396 fn partial_cmp(&self, other: &Flag) -> Option<Ordering> { 397 use Flag::*; 398 399 match (self, other) { 400 (Command(s1, _), Command(s2, _)) 401 | (Arg(s1, _), Arg(s2, _)) 402 | (Command(s1, _), Arg(s2, _)) 403 | (Arg(s1, _), Command(s2, _)) => { 404 if s1 == s2 { 405 Some(Ordering::Equal) 406 } else { 407 s1.partial_cmp(s2) 408 } 409 } 410 } 411 } 412} 413 414impl Ord for Flag<'_> { 415 fn cmp(&self, other: &Self) -> Ordering { 416 self.partial_cmp(other).unwrap() 417 } 418} 419 420fn detect_duplicate_flags(flags: &[Flag], short_or_long: &str) { 421 use Flag::*; 422 423 for (one, two) in find_duplicates(flags) { 424 match (one, two) { 425 (Command(flag, one), Command(_, another)) if one != another => panic!( 426 "the '{flag}' {short_or_long} flag is specified for both '{one}' and '{another}' subcommands" 427 ), 428 429 (Arg(flag, one), Arg(_, another)) if one != another => panic!( 430 "{short_or_long} option names must be unique, but '{flag}' is in use by both '{one}' and '{another}'" 431 ), 432 433 (Arg(flag, arg), Command(_, sub)) | (Command(flag, sub), Arg(_, arg)) => panic!( 434 "the '{flag}' {short_or_long} flag for the '{arg}' argument conflicts with the short flag \ 435 for '{sub}' subcommand" 436 ), 437 438 _ => {} 439 } 440 } 441} 442 443/// Find duplicates in a sorted array. 444/// 445/// The algorithm is simple: the array is sorted, duplicates 446/// must be placed next to each other, we can check only adjacent elements. 447fn find_duplicates<T: PartialEq>(slice: &[T]) -> impl Iterator<Item = (&T, &T)> { 448 slice.windows(2).filter_map(|w| { 449 if w[0] == w[1] { 450 Some((&w[0], &w[1])) 451 } else { 452 None 453 } 454 }) 455} 456 457fn assert_app_flags(cmd: &Command) { 458 macro_rules! checker { 459 ($a:ident requires $($b:ident)|+) => { 460 if cmd.$a() { 461 let mut s = String::new(); 462 463 $( 464 if !cmd.$b() { 465 use std::fmt::Write; 466 write!(&mut s, " AppSettings::{} is required when AppSettings::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap(); 467 } 468 )+ 469 470 if !s.is_empty() { 471 panic!("{}", s) 472 } 473 } 474 }; 475 ($a:ident conflicts $($b:ident)|+) => { 476 if cmd.$a() { 477 let mut s = String::new(); 478 479 $( 480 if cmd.$b() { 481 use std::fmt::Write; 482 write!(&mut s, " AppSettings::{} conflicts with AppSettings::{}.\n", std::stringify!($b), std::stringify!($a)).unwrap(); 483 } 484 )+ 485 486 if !s.is_empty() { 487 panic!("{}\n{}", cmd.get_name(), s) 488 } 489 } 490 }; 491 } 492 493 checker!(is_multicall_set conflicts is_no_binary_name_set); 494} 495 496#[cfg(debug_assertions)] 497fn _verify_positionals(cmd: &Command) -> bool { 498 debug!("Command::_verify_positionals"); 499 // Because you must wait until all arguments have been supplied, this is the first chance 500 // to make assertions on positional argument indexes 501 // 502 // First we verify that the index highest supplied index, is equal to the number of 503 // positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3 504 // but no 2) 505 506 let highest_idx = cmd 507 .get_keymap() 508 .keys() 509 .filter_map(|x| { 510 if let KeyType::Position(n) = x { 511 Some(*n) 512 } else { 513 None 514 } 515 }) 516 .max() 517 .unwrap_or(0); 518 519 let num_p = cmd.get_keymap().keys().filter(|x| x.is_position()).count(); 520 521 assert!( 522 highest_idx == num_p, 523 "Found positional argument whose index is {highest_idx} but there \ 524 are only {num_p} positional arguments defined", 525 ); 526 527 for arg in cmd.get_arguments() { 528 if arg.index.unwrap_or(0) == highest_idx { 529 assert!( 530 !arg.is_trailing_var_arg_set() || !arg.is_last_set(), 531 "{}:{}: `Arg::trailing_var_arg` and `Arg::last` cannot be used together", 532 cmd.get_name(), 533 arg.get_id() 534 ); 535 536 if arg.is_trailing_var_arg_set() { 537 assert!( 538 arg.is_multiple(), 539 "{}:{}: `Arg::trailing_var_arg` must accept multiple values", 540 cmd.get_name(), 541 arg.get_id() 542 ); 543 } 544 } else { 545 assert!( 546 !arg.is_trailing_var_arg_set(), 547 "{}:{}: `Arg::trailing_var_arg` can only apply to last positional", 548 cmd.get_name(), 549 arg.get_id() 550 ); 551 } 552 } 553 554 // Next we verify that only the highest index has takes multiple arguments (if any) 555 let only_highest = |a: &Arg| a.is_multiple() && (a.get_index().unwrap_or(0) != highest_idx); 556 if cmd.get_positionals().any(only_highest) { 557 // First we make sure if there is a positional that allows multiple values 558 // the one before it (second to last) has one of these: 559 // * a value terminator 560 // * ArgSettings::Last 561 // * The last arg is Required 562 563 // We can't pass the closure (it.next()) to the macro directly because each call to 564 // find() (iterator, not macro) gets called repeatedly. 565 let last = &cmd.get_keymap()[&KeyType::Position(highest_idx)]; 566 let second_to_last = &cmd.get_keymap()[&KeyType::Position(highest_idx - 1)]; 567 568 // Either the final positional is required 569 // Or the second to last has a terminator or .last(true) set 570 let ok = last.is_required_set() 571 || (second_to_last.terminator.is_some() || second_to_last.is_last_set()) 572 || last.is_last_set(); 573 assert!( 574 ok, 575 "When using a positional argument with `.num_args(1..)` that is *not the \ 576 last* positional argument, the last positional argument (i.e. the one \ 577 with the highest index) *must* have .required(true) or .last(true) set." 578 ); 579 580 // We make sure if the second to last is Multiple the last is ArgSettings::Last 581 let ok = second_to_last.is_multiple() || last.is_last_set(); 582 assert!( 583 ok, 584 "Only the last positional argument, or second to last positional \ 585 argument may be set to `.num_args(1..)`" 586 ); 587 588 // Next we check how many have both Multiple and not a specific number of values set 589 let count = cmd 590 .get_positionals() 591 .filter(|p| { 592 p.is_multiple_values_set() 593 && !p.get_num_args().expect(INTERNAL_ERROR_MSG).is_fixed() 594 }) 595 .count(); 596 let ok = count <= 1 597 || (last.is_last_set() 598 && last.is_multiple() 599 && second_to_last.is_multiple() 600 && count == 2); 601 assert!( 602 ok, 603 "Only one positional argument with `.num_args(1..)` set is allowed per \ 604 command, unless the second one also has .last(true) set" 605 ); 606 } 607 608 let mut found = false; 609 610 if cmd.is_allow_missing_positional_set() { 611 // Check that if a required positional argument is found, all positions with a lower 612 // index are also required. 613 let mut foundx2 = false; 614 615 for p in cmd.get_positionals() { 616 if foundx2 && !p.is_required_set() { 617 assert!( 618 p.is_required_set(), 619 "Found non-required positional argument with a lower \ 620 index than a required positional argument by two or more: {:?} \ 621 index {:?}", 622 p.get_id(), 623 p.get_index() 624 ); 625 } else if p.is_required_set() && !p.is_last_set() { 626 // Args that .last(true) don't count since they can be required and have 627 // positionals with a lower index that aren't required 628 // Imagine: prog <req1> [opt1] -- <req2> 629 // Both of these are valid invocations: 630 // $ prog r1 -- r2 631 // $ prog r1 o1 -- r2 632 if found { 633 foundx2 = true; 634 continue; 635 } 636 found = true; 637 continue; 638 } else { 639 found = false; 640 } 641 } 642 } else { 643 // Check that if a required positional argument is found, all positions with a lower 644 // index are also required 645 for p in (1..=num_p).rev().filter_map(|n| cmd.get_keymap().get(&n)) { 646 if found { 647 assert!( 648 p.is_required_set(), 649 "Found non-required positional argument with a lower \ 650 index than a required positional argument: {:?} index {:?}", 651 p.get_id(), 652 p.get_index() 653 ); 654 } else if p.is_required_set() && !p.is_last_set() { 655 // Args that .last(true) don't count since they can be required and have 656 // positionals with a lower index that aren't required 657 // Imagine: prog <req1> [opt1] -- <req2> 658 // Both of these are valid invocations: 659 // $ prog r1 -- r2 660 // $ prog r1 o1 -- r2 661 found = true; 662 continue; 663 } 664 } 665 } 666 assert!( 667 cmd.get_positionals().filter(|p| p.is_last_set()).count() < 2, 668 "Only one positional argument may have last(true) set. Found two." 669 ); 670 if cmd 671 .get_positionals() 672 .any(|p| p.is_last_set() && p.is_required_set()) 673 && cmd.has_subcommands() 674 && !cmd.is_subcommand_negates_reqs_set() 675 { 676 panic!( 677 "Having a required positional argument with .last(true) set *and* child \ 678 subcommands without setting SubcommandsNegateReqs isn't compatible." 679 ); 680 } 681 682 true 683} 684 685fn assert_arg(arg: &Arg) { 686 debug!("Arg::_debug_asserts:{}", arg.get_id()); 687 688 // Self conflict 689 // TODO: this check should be recursive 690 assert!( 691 !arg.blacklist.iter().any(|x| x == arg.get_id()), 692 "Argument '{}' cannot conflict with itself", 693 arg.get_id(), 694 ); 695 696 assert_eq!( 697 arg.get_action().takes_values(), 698 arg.is_takes_value_set(), 699 "Argument `{}`'s selected action {:?} contradicts `takes_value`", 700 arg.get_id(), 701 arg.get_action() 702 ); 703 if let Some(action_type_id) = arg.get_action().value_type_id() { 704 assert_eq!( 705 action_type_id, 706 arg.get_value_parser().type_id(), 707 "Argument `{}`'s selected action {:?} contradicts `value_parser` ({:?})", 708 arg.get_id(), 709 arg.get_action(), 710 arg.get_value_parser() 711 ); 712 } 713 714 if arg.get_value_hint() != ValueHint::Unknown { 715 assert!( 716 arg.is_takes_value_set(), 717 "Argument '{}' has value hint but takes no value", 718 arg.get_id() 719 ); 720 721 if arg.get_value_hint() == ValueHint::CommandWithArguments { 722 assert!( 723 arg.is_multiple_values_set(), 724 "Argument '{}' uses hint CommandWithArguments and must accept multiple values", 725 arg.get_id() 726 ) 727 } 728 } 729 730 if arg.index.is_some() { 731 assert!( 732 arg.is_positional(), 733 "Argument '{}' is a positional argument and can't have short or long name versions", 734 arg.get_id() 735 ); 736 assert!( 737 arg.is_takes_value_set(), 738 "Argument '{}` is positional, it must take a value{}", 739 arg.get_id(), 740 if arg.get_id() == Id::HELP { 741 " (`mut_arg` no longer works with implicit `--help`)" 742 } else if arg.get_id() == Id::VERSION { 743 " (`mut_arg` no longer works with implicit `--version`)" 744 } else { 745 "" 746 } 747 ); 748 } 749 750 let num_vals = arg.get_num_args().expect(INTERNAL_ERROR_MSG); 751 // This can be the cause of later asserts, so put this first 752 if num_vals != ValueRange::EMPTY { 753 // HACK: Don't check for flags to make the derive easier 754 let num_val_names = arg.get_value_names().unwrap_or(&[]).len(); 755 if num_vals.max_values() < num_val_names { 756 panic!( 757 "Argument {}: Too many value names ({}) compared to `num_args` ({})", 758 arg.get_id(), 759 num_val_names, 760 num_vals 761 ); 762 } 763 } 764 765 assert_eq!( 766 num_vals.takes_values(), 767 arg.is_takes_value_set(), 768 "Argument {}: mismatch between `num_args` ({}) and `takes_value`", 769 arg.get_id(), 770 num_vals, 771 ); 772 assert_eq!( 773 num_vals.is_multiple(), 774 arg.is_multiple_values_set(), 775 "Argument {}: mismatch between `num_args` ({}) and `multiple_values`", 776 arg.get_id(), 777 num_vals, 778 ); 779 780 if 1 < num_vals.min_values() { 781 assert!( 782 !arg.is_require_equals_set(), 783 "Argument {}: cannot accept more than 1 arg (num_args={}) with require_equals", 784 arg.get_id(), 785 num_vals 786 ); 787 } 788 789 if num_vals == ValueRange::SINGLE { 790 assert!( 791 !arg.is_multiple_values_set(), 792 "Argument {}: mismatch between `num_args` and `multiple_values`", 793 arg.get_id() 794 ); 795 } 796 797 assert_arg_flags(arg); 798 799 assert_defaults(arg, "default_value", arg.default_vals.iter()); 800 assert_defaults( 801 arg, 802 "default_missing_value", 803 arg.default_missing_vals.iter(), 804 ); 805 assert_defaults( 806 arg, 807 "default_value_if", 808 arg.default_vals_ifs 809 .iter() 810 .filter_map(|(_, _, default)| default.as_ref()), 811 ); 812} 813 814fn assert_arg_flags(arg: &Arg) { 815 macro_rules! checker { 816 ($a:ident requires $($b:ident)|+) => { 817 if arg.$a() { 818 let mut s = String::new(); 819 820 $( 821 if !arg.$b() { 822 use std::fmt::Write; 823 write!(&mut s, " Arg::{} is required when Arg::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap(); 824 } 825 )+ 826 827 if !s.is_empty() { 828 panic!("Argument {:?}\n{}", arg.get_id(), s) 829 } 830 } 831 } 832 } 833 834 checker!(is_hide_possible_values_set requires is_takes_value_set); 835 checker!(is_allow_hyphen_values_set requires is_takes_value_set); 836 checker!(is_allow_negative_numbers_set requires is_takes_value_set); 837 checker!(is_require_equals_set requires is_takes_value_set); 838 checker!(is_last_set requires is_takes_value_set); 839 checker!(is_hide_default_value_set requires is_takes_value_set); 840 checker!(is_multiple_values_set requires is_takes_value_set); 841 checker!(is_ignore_case_set requires is_takes_value_set); 842} 843 844fn assert_defaults<'d>( 845 arg: &Arg, 846 field: &'static str, 847 defaults: impl IntoIterator<Item = &'d OsStr>, 848) { 849 for default_os in defaults { 850 let value_parser = arg.get_value_parser(); 851 let assert_cmd = Command::new("assert"); 852 if let Some(delim) = arg.get_value_delimiter() { 853 let default_os = RawOsStr::new(default_os); 854 for part in default_os.split(delim) { 855 if let Err(err) = value_parser.parse_ref(&assert_cmd, Some(arg), &part.to_os_str()) 856 { 857 panic!( 858 "Argument `{}`'s {}={:?} failed validation: {}", 859 arg.get_id(), 860 field, 861 part.to_str_lossy(), 862 err 863 ); 864 } 865 } 866 } else if let Err(err) = value_parser.parse_ref(&assert_cmd, Some(arg), default_os) { 867 panic!( 868 "Argument `{}`'s {}={:?} failed validation: {}", 869 arg.get_id(), 870 field, 871 default_os, 872 err 873 ); 874 } 875 } 876} 877