|
1 | 1 | use crate::{ |
2 | 2 | build::arg::{debug_asserts::assert_arg, ArgProvider}, |
| 3 | + mkeymap::KeyType, |
3 | 4 | util::Id, |
4 | | - App, AppSettings, ArgSettings, ValueHint, |
| 5 | + App, AppSettings, Arg, ArgSettings, ValueHint, |
5 | 6 | }; |
6 | 7 | use std::cmp::Ordering; |
7 | 8 |
|
@@ -274,6 +275,8 @@ pub(crate) fn assert_app(app: &App) { |
274 | 275 | detect_duplicate_flags(&long_flags, "long"); |
275 | 276 | detect_duplicate_flags(&short_flags, "short"); |
276 | 277 |
|
| 278 | + _verify_positionals(app); |
| 279 | + |
277 | 280 | if let Some(help_template) = app.template { |
278 | 281 | assert!( |
279 | 282 | !help_template.contains("{flags}"), |
@@ -410,3 +413,170 @@ fn assert_app_flags(app: &App) { |
410 | 413 | #[cfg(feature = "unstable-multicall")] |
411 | 414 | checker!(Multicall conflicts NoBinaryName); |
412 | 415 | } |
| 416 | + |
| 417 | +#[cfg(debug_assertions)] |
| 418 | +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 | +} |
0 commit comments