Skip to content

Commit c39115a

Browse files
authored
Merge pull request #6237 from epage/conflict
fix(parser): Don't report conflicting arguments in usage
2 parents 2047862 + 578cd43 commit c39115a

File tree

3 files changed

+107
-60
lines changed

3 files changed

+107
-60
lines changed

clap_builder/src/parser/validator.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,7 @@ impl<'cmd> Validator<'cmd> {
132132
}
133133

134134
debug!("Validator::build_conflict_err: name={name:?}");
135-
let mut seen = FlatSet::new();
136-
let conflicts = conflict_ids
135+
let conflict_ids = conflict_ids
137136
.iter()
138137
.flat_map(|c_id| {
139138
if self.cmd.find_group(c_id).is_some() {
@@ -142,16 +141,18 @@ impl<'cmd> Validator<'cmd> {
142141
vec![c_id.clone()]
143142
}
144143
})
145-
.filter_map(|c_id| {
146-
seen.insert(c_id.clone()).then(|| {
147-
let c_arg = self.cmd.find(&c_id).expect(INTERNAL_ERROR_MSG);
148-
c_arg.to_string()
149-
})
144+
.collect::<FlatSet<_>>()
145+
.into_vec();
146+
let conflicts = conflict_ids
147+
.iter()
148+
.map(|c_id| {
149+
let c_arg = self.cmd.find(c_id).expect(INTERNAL_ERROR_MSG);
150+
c_arg.to_string()
150151
})
151152
.collect();
152153

153154
let former_arg = self.cmd.find(name).expect(INTERNAL_ERROR_MSG);
154-
let usg = self.build_conflict_err_usage(matcher, conflict_ids);
155+
let usg = self.build_conflict_err_usage(matcher, &conflict_ids);
155156
Err(Error::argument_conflict(
156157
self.cmd,
157158
former_arg.to_string(),

clap_builder/src/util/flat_set.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ impl<T: PartialEq + Eq> FlatSet<T> {
6060
{
6161
self.inner.sort_by_key(f);
6262
}
63+
64+
pub(crate) fn into_vec(self) -> Vec<T> {
65+
self.inner
66+
}
6367
}
6468

6569
impl<T: PartialEq + Eq> Default for FlatSet<T> {

tests/builder/groups.rs

Lines changed: 94 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgGroup, Command, Id};
2+
use snapbox::str;
23

34
use super::utils;
45

@@ -131,14 +132,6 @@ fn empty_group() {
131132
#[test]
132133
#[cfg(feature = "error-context")]
133134
fn req_group_usage_string() {
134-
static REQ_GROUP_USAGE: &str = "error: the following required arguments were not provided:
135-
<base|--delete>
136-
137-
Usage: clap-test <base|--delete>
138-
139-
For more information, try '--help'.
140-
";
141-
142135
let cmd = Command::new("req_group")
143136
.arg(arg!([base] "Base commit"))
144137
.arg(arg!(
@@ -150,20 +143,25 @@ For more information, try '--help'.
150143
.required(true),
151144
);
152145

153-
utils::assert_output(cmd, "clap-test", REQ_GROUP_USAGE, true);
154-
}
155-
156-
#[test]
157-
#[cfg(feature = "error-context")]
158-
fn req_group_with_conflict_usage_string() {
159-
static REQ_GROUP_CONFLICT_USAGE: &str = "\
160-
error: the argument '--delete' cannot be used with '[base]'
146+
utils::assert_output(
147+
cmd,
148+
"clap-test",
149+
str![[r#"
150+
error: the following required arguments were not provided:
151+
<base|--delete>
161152
162153
Usage: clap-test <base|--delete>
163154
164155
For more information, try '--help'.
165-
";
166156
157+
"#]],
158+
true,
159+
);
160+
}
161+
162+
#[test]
163+
#[cfg(feature = "error-context")]
164+
fn req_group_with_conflict_usage_string() {
167165
let cmd = Command::new("req_group")
168166
.arg(arg!([base] "Base commit").conflicts_with("delete"))
169167
.arg(arg!(
@@ -178,22 +176,21 @@ For more information, try '--help'.
178176
utils::assert_output(
179177
cmd,
180178
"clap-test --delete base",
181-
REQ_GROUP_CONFLICT_USAGE,
179+
str![[r#"
180+
error: the argument '--delete' cannot be used with '[base]'
181+
182+
Usage: clap-test <base|--delete>
183+
184+
For more information, try '--help'.
185+
186+
"#]],
182187
true,
183188
);
184189
}
185190

186191
#[test]
187192
#[cfg(feature = "error-context")]
188193
fn req_group_with_conflict_usage_string_only_options() {
189-
static REQ_GROUP_CONFLICT_ONLY_OPTIONS: &str = "\
190-
error: the argument '--delete' cannot be used with '--all'
191-
192-
Usage: clap-test <--all|--delete>
193-
194-
For more information, try '--help'.
195-
";
196-
197194
let cmd = Command::new("req_group")
198195
.arg(arg!(-a --all "All").conflicts_with("delete"))
199196
.arg(arg!(
@@ -207,7 +204,39 @@ For more information, try '--help'.
207204
utils::assert_output(
208205
cmd,
209206
"clap-test --delete --all",
210-
REQ_GROUP_CONFLICT_ONLY_OPTIONS,
207+
str![[r#"
208+
error: the argument '--delete' cannot be used with '--all'
209+
210+
Usage: clap-test <--all|--delete>
211+
212+
For more information, try '--help'.
213+
214+
"#]],
215+
true,
216+
);
217+
}
218+
219+
#[test]
220+
fn conflict_with_group() {
221+
let cmd = Command::new("prog")
222+
.group(ArgGroup::new("group").multiple(true))
223+
.arg(arg!(--a).group("group"))
224+
.arg(arg!(--b).group("group"))
225+
.arg(arg!(--conflict).conflicts_with("group"));
226+
227+
utils::assert_output(
228+
cmd,
229+
"prog --a --conflict",
230+
str![[r#"
231+
error: the argument '--conflict' cannot be used with:
232+
--a
233+
--b
234+
235+
Usage: prog --conflict
236+
237+
For more information, try '--help'.
238+
239+
"#]],
211240
true,
212241
);
213242
}
@@ -264,19 +293,24 @@ fn group_overrides_required() {
264293

265294
#[test]
266295
fn group_usage_use_val_name() {
267-
static GROUP_USAGE_USE_VAL_NAME: &str = "\
296+
let cmd = Command::new("prog")
297+
.arg(Arg::new("a").value_name("A"))
298+
.group(ArgGroup::new("group").arg("a").required(true));
299+
utils::assert_output(
300+
cmd,
301+
"prog --help",
302+
str![[r#"
268303
Usage: prog <A>
269304
270305
Arguments:
271306
[A]
272307
273308
Options:
274309
-h, --help Print help
275-
";
276-
let cmd = Command::new("prog")
277-
.arg(Arg::new("a").value_name("A"))
278-
.group(ArgGroup::new("group").arg("a").required(true));
279-
utils::assert_output(cmd, "prog --help", GROUP_USAGE_USE_VAL_NAME, false);
310+
311+
"#]],
312+
false,
313+
);
280314
}
281315

282316
#[test]
@@ -304,42 +338,50 @@ fn group_acts_like_arg() {
304338

305339
#[test]
306340
fn conflict_with_overlapping_group_in_error() {
307-
static ERR: &str = "\
308-
error: the argument '--major' cannot be used with '--minor'
309-
310-
Usage: prog --major
311-
312-
For more information, try '--help'.
313-
";
314-
315341
let cmd = Command::new("prog")
316342
.group(ArgGroup::new("all").multiple(true))
317343
.arg(arg!(--major).group("vers").group("all"))
318344
.arg(arg!(--minor).group("vers").group("all"))
319345
.arg(arg!(--other).group("all"));
320346

321-
utils::assert_output(cmd, "prog --major --minor", ERR, true);
322-
}
323-
324-
#[test]
325-
fn requires_group_with_overlapping_group_in_error() {
326-
static ERR: &str = "\
327-
error: the following required arguments were not provided:
328-
<--in|--spec>
347+
utils::assert_output(
348+
cmd,
349+
"prog --major --minor",
350+
str![[r#"
351+
error: the argument '--major' cannot be used with '--minor'
329352
330-
Usage: prog --config <--in|--spec>
353+
Usage: prog --major
331354
332355
For more information, try '--help'.
333-
";
334356
357+
"#]],
358+
true,
359+
);
360+
}
361+
362+
#[test]
363+
fn requires_group_with_overlapping_group_in_error() {
335364
let cmd = Command::new("prog")
336365
.group(ArgGroup::new("all").multiple(true))
337366
.group(ArgGroup::new("input").required(true))
338367
.arg(arg!(--in).group("input").group("all"))
339368
.arg(arg!(--spec).group("input").group("all"))
340369
.arg(arg!(--config).requires("input").group("all"));
341370

342-
utils::assert_output(cmd, "prog --config", ERR, true);
371+
utils::assert_output(
372+
cmd,
373+
"prog --config",
374+
str![[r#"
375+
error: the following required arguments were not provided:
376+
<--in|--spec>
377+
378+
Usage: prog --config <--in|--spec>
379+
380+
For more information, try '--help'.
381+
382+
"#]],
383+
true,
384+
);
343385
}
344386

345387
/* This is used to be fixed in a hack, we need to find a better way to fix it.

0 commit comments

Comments
 (0)