Skip to content

Commit 1b2e7ad

Browse files
committed
Auto merge of clap-rs#898 - kbknapp:issues-896,895, r=kbknapp
Issues 896,895
2 parents 814b126 + e7c2eaf commit 1b2e7ad

File tree

8 files changed

+105
-58
lines changed

8 files changed

+105
-58
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
<a name="v2.21.1"></a>
2+
### v2.21.1 (2017-03-12)
3+
4+
5+
#### Bug Fixes
6+
7+
* **ArgRequiredElseHelp:** fixes the precedence of this error to prioritize over other error messages ([74b751ff](https://github.com/kbknapp/clap-rs/commit/74b751ff2e3631e337b7946347c1119829a41c53), closes [#895](https://github.com/kbknapp/clap-rs/issues/895))
8+
* **Positionals:** fixes some regression bugs resulting from old asserts in debug mode. ([9a3bc98e](https://github.com/kbknapp/clap-rs/commit/9a3bc98e9b55e7514b74b73374c5ac8b6e5e0508), closes [#896](https://github.com/kbknapp/clap-rs/issues/896))
9+
10+
11+
112
<a name="v2.21.0"></a>
213
## v2.21.0 (2017-03-09)
314

CONTRIBUTORS.md

Lines changed: 33 additions & 25 deletions
Large diffs are not rendered by default.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
name = "clap"
4-
version = "2.21.0"
4+
version = "2.21.1"
55
authors = ["Kevin K. <[email protected]>"]
66
exclude = ["examples/*", "clap-test/*", "tests/*", "benches/*", "*.png", "clap-perf/*", "*.dot"]
77
repository = "https://github.com/kbknapp/clap-rs.git"

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)
4545

4646
## What's New
4747

48+
Here's the highlights for v2.21.1
49+
50+
* fixes the precedence of this error to prioritize over other error messages
51+
* fixes some regression bugs resulting from old asserts in debug mode.
52+
4853
Here's the highlights for v2.21.0
4954

5055
* adds the ability to mark a positional argument as 'last' which means it should be used with `--` syntax and can be accessed early to effectivly skip other positional args

src/app/parser.rs

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -455,31 +455,30 @@ impl<'a, 'b> Parser<'a, 'b>
455455
// Or the second to last has a terminator or .last(true) set
456456
let ok = last.is_set(ArgSettings::Required) ||
457457
(second_to_last.v.terminator.is_some() ||
458-
second_to_last.b.is_set(ArgSettings::Last));
458+
second_to_last.b.is_set(ArgSettings::Last)) ||
459+
last.is_set(ArgSettings::Last);
459460
assert!(ok,
460461
"When using a positional argument with .multiple(true) that is *not the \
461462
last* positional argument, the last positional argument (i.e the one \
462463
with the highest index) *must* have .required(true) or .last(true) set.");
463-
let num = self.positionals.len() - 1;
464-
let ok = self.positionals
465-
.get(num)
466-
.unwrap()
467-
.is_set(ArgSettings::Multiple);
464+
let ok = second_to_last.is_set(ArgSettings::Multiple) || last.is_set(ArgSettings::Last);
468465
assert!(ok,
469466
"Only the last positional argument, or second to last positional \
470467
argument may be set to .multiple(true)");
471468

472-
self.settings.set(AS::LowIndexMultiplePositional);
469+
let count = self.positionals
470+
.values()
471+
.filter(|p| p.b.settings.is_set(ArgSettings::Multiple) && p.v.num_vals.is_none())
472+
.count();
473+
let ok = count <= 1 ||
474+
(last.is_set(ArgSettings::Last) && last.is_set(ArgSettings::Multiple) &&
475+
second_to_last.is_set(ArgSettings::Multiple) &&
476+
count == 2);
477+
assert!(ok,
478+
"Only one positional argument with .multiple(true) set is allowed per \
479+
command, unless the second one also has .last(true) set");
473480
}
474481

475-
let ok = self.positionals
476-
.values()
477-
.filter(|p| p.b.settings.is_set(ArgSettings::Multiple) && p.v.num_vals.is_none())
478-
.map(|_| 1)
479-
.sum::<u64>() <= 1;
480-
assert!(ok,
481-
"Only one positional argument with .multiple(true) set is allowed per \
482-
command");
483482

484483
if self.is_set(AS::AllowMissingPositional) {
485484
// Check that if a required positional argument is found, all positions with a lower
@@ -677,7 +676,6 @@ impl<'a, 'b> Parser<'a, 'b>
677676

678677
// allow wrong self convention due to self.valid_neg_num = true and it's a private method
679678
#[cfg_attr(feature = "lints", allow(wrong_self_convention))]
680-
#[inline]
681679
fn is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: Option<&'a str>) -> bool {
682680
debugln!("Parser::is_new_arg: arg={:?}, Needs Val of={:?}",
683681
arg_os,
@@ -743,6 +741,13 @@ impl<'a, 'b> Parser<'a, 'b>
743741
debugln!("Parser::get_matches_with;");
744742
// Verify all positional assertions pass
745743
debug_assert!(self.verify_positionals());
744+
if self.positionals.values().any(|a| {
745+
a.b.is_set(ArgSettings::Multiple) &&
746+
(a.index as usize != self.positionals.len())
747+
}) &&
748+
self.positionals.values().last().map_or(false, |p| !p.is_set(ArgSettings::Last)) {
749+
self.settings.set(AS::LowIndexMultiplePositional);
750+
}
746751
let has_args = self.has_args();
747752

748753
// Next we create the `--help` and `--version` arguments and add them if
@@ -761,6 +766,11 @@ impl<'a, 'b> Parser<'a, 'b>
761766
self.unset(AS::ValidNegNumFound);
762767
// Is this a new argument, or values from a previous option?
763768
let starts_new_arg = self.is_new_arg(&arg_os, needs_val_of);
769+
if arg_os.starts_with(b"--") && arg_os.len_() == 2 {
770+
debugln!("Parser::get_matches_with: setting TrailingVals=true");
771+
self.set(AS::TrailingValues);
772+
continue;
773+
}
764774

765775
// Has the user already passed '--'? Meaning only positional args follow
766776
if !self.is_set(AS::TrailingValues) {
@@ -792,15 +802,6 @@ impl<'a, 'b> Parser<'a, 'b>
792802
}
793803
} else {
794804
if arg_os.starts_with(b"--") {
795-
if arg_os.len_() == 2 {
796-
// The user has passed '--' which means only positional args follow no
797-
// matter what they start with
798-
debugln!("Parser::get_matches_with: found '--'");
799-
debugln!("Parser::get_matches_with: setting TrailingVals=true");
800-
self.set(AS::TrailingValues);
801-
continue;
802-
}
803-
804805
needs_val_of = try!(self.parse_long_arg(matcher, &arg_os));
805806
if !(needs_val_of.is_none() && self.is_set(AS::AllowLeadingHyphen)) {
806807
continue;

src/app/validator.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,6 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
4949
}
5050
}
5151

52-
try!(self.validate_blacklist(matcher));
53-
if !(self.0.is_set(AS::SubcommandsNegateReqs) && subcmd_name.is_some()) && !reqs_validated {
54-
try!(self.validate_required(matcher));
55-
}
56-
try!(self.validate_matched_args(matcher));
57-
matcher.usage(usage::create_usage_with_title(self.0, &[]));
58-
5952
if matcher.is_empty() && matcher.subcommand_name().is_none() &&
6053
self.0.is_set(AS::ArgRequiredElseHelp) {
6154
let mut out = vec![];
@@ -66,6 +59,13 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
6659
info: None,
6760
});
6861
}
62+
try!(self.validate_blacklist(matcher));
63+
if !(self.0.is_set(AS::SubcommandsNegateReqs) && subcmd_name.is_some()) && !reqs_validated {
64+
try!(self.validate_required(matcher));
65+
}
66+
try!(self.validate_matched_args(matcher));
67+
matcher.usage(usage::create_usage_with_title(self.0, &[]));
68+
6969
Ok(())
7070
}
7171

tests/app_settings.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,18 @@ fn arg_required_else_help() {
110110
assert_eq!(err.kind, ErrorKind::MissingArgumentOrSubcommand);
111111
}
112112

113+
#[test]
114+
fn arg_required_else_help_over_reqs() {
115+
let result = App::new("arg_required")
116+
.setting(AppSettings::ArgRequiredElseHelp)
117+
.arg(Arg::with_name("test")
118+
.index(1).required(true))
119+
.get_matches_from_safe(vec![""]);
120+
assert!(result.is_err());
121+
let err = result.err().unwrap();
122+
assert_eq!(err.kind, ErrorKind::MissingArgumentOrSubcommand);
123+
}
124+
113125
#[cfg(not(feature = "suggestions"))]
114126
#[test]
115127
fn infer_subcommands_fail_no_args() {

tests/positionals.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,14 @@ fn last_positional_no_double_dash() {
233233
.get_matches_from_safe(vec!["test", "tgt", "crp", "arg"]);
234234
assert!(r.is_err());
235235
assert_eq!(r.unwrap_err().kind, ErrorKind::UnknownArgument);
236+
}
237+
238+
#[test]
239+
fn last_positional_second_to_last_mult() {
240+
let r = App::new("test")
241+
.arg_from_usage("<TARGET> 'some target'")
242+
.arg_from_usage("[CORPUS]... 'some corpus'")
243+
.arg(Arg::from_usage("[ARGS]... 'some file'").last(true))
244+
.get_matches_from_safe(vec!["test", "tgt", "crp1", "crp2", "--", "arg"]);
245+
assert!(r.is_ok(), "{:?}", r.unwrap_err().kind);
236246
}

0 commit comments

Comments
 (0)