clap_builder/builder/arg_group.rs
1// Internal
2use crate::builder::IntoResettable;
3use crate::util::Id;
4
5/// Specifies a logical group of [arguments]
6///
7/// You can use this for
8/// - applying validation to an entire group, like [`ArgGroup::multiple`]
9/// - validate relationships between an argument and a group, like [conflicts] or [requirements]
10/// - check which argument in a group was specified on the command-line
11///
12/// For visually grouping arguments in help, see instead
13/// [`Arg::help_heading`][crate::Arg::help_heading].
14///
15/// # Examples
16///
17/// The following example demonstrates using an `ArgGroup` to ensure that one, and only one, of
18/// the arguments from the specified group is present at runtime.
19///
20/// ```rust
21/// # use clap_builder as clap;
22/// # use clap::{Command, arg, ArgGroup, error::ErrorKind};
23/// let result = Command::new("cmd")
24/// .arg(arg!(--"set-ver" <ver> "set the version manually"))
25/// .arg(arg!(--major "auto increase major"))
26/// .arg(arg!(--minor "auto increase minor"))
27/// .arg(arg!(--patch "auto increase patch"))
28/// .group(ArgGroup::new("vers")
29/// .args(["set-ver", "major", "minor", "patch"])
30/// .required(true))
31/// .try_get_matches_from(vec!["cmd", "--major", "--patch"]);
32/// // Because we used two args in the group it's an error
33/// assert!(result.is_err());
34/// let err = result.unwrap_err();
35/// assert_eq!(err.kind(), ErrorKind::ArgumentConflict);
36/// ```
37///
38/// This next example shows a passing parse of the same scenario
39/// ```rust
40/// # use clap_builder as clap;
41/// # use clap::{Command, arg, ArgGroup, Id};
42/// let result = Command::new("cmd")
43/// .arg(arg!(--"set-ver" <ver> "set the version manually"))
44/// .arg(arg!(--major "auto increase major"))
45/// .arg(arg!(--minor "auto increase minor"))
46/// .arg(arg!(--patch "auto increase patch"))
47/// .group(ArgGroup::new("vers")
48/// .args(["set-ver", "major", "minor","patch"])
49/// .required(true))
50/// .try_get_matches_from(vec!["cmd", "--major"]);
51/// assert!(result.is_ok());
52/// let matches = result.unwrap();
53/// // We may not know which of the args was used, so we can test for the group...
54/// assert!(matches.contains_id("vers"));
55/// // We can also ask the group which arg was used
56/// assert_eq!(matches
57/// .get_one::<Id>("vers")
58/// .expect("`vers` is required")
59/// .as_str(),
60/// "major"
61/// );
62/// // we could also alternatively check each arg individually (not shown here)
63/// ```
64/// [arguments]: crate::Arg
65/// [conflicts]: crate::Arg::conflicts_with()
66/// [requirements]: crate::Arg::requires()
67#[derive(Default, Clone, Debug, PartialEq, Eq)]
68pub struct ArgGroup {
69 pub(crate) id: Id,
70 pub(crate) args: Vec<Id>,
71 pub(crate) required: bool,
72 pub(crate) requires: Vec<Id>,
73 pub(crate) conflicts: Vec<Id>,
74 pub(crate) multiple: bool,
75}
76
77/// # Builder
78impl ArgGroup {
79 /// Create a `ArgGroup` using a unique name.
80 ///
81 /// The name will be used to get values from the group or refer to the group inside of conflict
82 /// and requirement rules.
83 ///
84 /// # Examples
85 ///
86 /// ```rust
87 /// # use clap_builder as clap;
88 /// # use clap::{Command, ArgGroup};
89 /// ArgGroup::new("config")
90 /// # ;
91 /// ```
92 pub fn new(id: impl Into<Id>) -> Self {
93 ArgGroup::default().id(id)
94 }
95
96 /// Sets the group name.
97 ///
98 /// # Examples
99 ///
100 /// ```rust
101 /// # use clap_builder as clap;
102 /// # use clap::{Command, ArgGroup};
103 /// ArgGroup::default().id("config")
104 /// # ;
105 /// ```
106 #[must_use]
107 pub fn id(mut self, id: impl Into<Id>) -> Self {
108 self.id = id.into();
109 self
110 }
111
112 /// Adds an [argument] to this group by name
113 ///
114 /// # Examples
115 ///
116 /// ```rust
117 /// # use clap_builder as clap;
118 /// # use clap::{Command, Arg, ArgGroup, ArgAction};
119 /// let m = Command::new("myprog")
120 /// .arg(Arg::new("flag")
121 /// .short('f')
122 /// .action(ArgAction::SetTrue))
123 /// .arg(Arg::new("color")
124 /// .short('c')
125 /// .action(ArgAction::SetTrue))
126 /// .group(ArgGroup::new("req_flags")
127 /// .arg("flag")
128 /// .arg("color"))
129 /// .get_matches_from(vec!["myprog", "-f"]);
130 /// // maybe we don't know which of the two flags was used...
131 /// assert!(m.contains_id("req_flags"));
132 /// // but we can also check individually if needed
133 /// assert!(m.contains_id("flag"));
134 /// ```
135 /// [argument]: crate::Arg
136 #[must_use]
137 pub fn arg(mut self, arg_id: impl IntoResettable<Id>) -> Self {
138 if let Some(arg_id) = arg_id.into_resettable().into_option() {
139 self.args.push(arg_id);
140 } else {
141 self.args.clear();
142 }
143 self
144 }
145
146 /// Adds multiple [arguments] to this group by name
147 ///
148 /// # Examples
149 ///
150 /// ```rust
151 /// # use clap_builder as clap;
152 /// # use clap::{Command, Arg, ArgGroup, ArgAction};
153 /// let m = Command::new("myprog")
154 /// .arg(Arg::new("flag")
155 /// .short('f')
156 /// .action(ArgAction::SetTrue))
157 /// .arg(Arg::new("color")
158 /// .short('c')
159 /// .action(ArgAction::SetTrue))
160 /// .group(ArgGroup::new("req_flags")
161 /// .args(["flag", "color"]))
162 /// .get_matches_from(vec!["myprog", "-f"]);
163 /// // maybe we don't know which of the two flags was used...
164 /// assert!(m.contains_id("req_flags"));
165 /// // but we can also check individually if needed
166 /// assert!(m.contains_id("flag"));
167 /// ```
168 /// [arguments]: crate::Arg
169 #[must_use]
170 pub fn args(mut self, ns: impl IntoIterator<Item = impl Into<Id>>) -> Self {
171 for n in ns {
172 self = self.arg(n);
173 }
174 self
175 }
176
177 /// Getters for all args. It will return a vector of `Id`
178 ///
179 /// # Example
180 ///
181 /// ```rust
182 /// # use clap_builder as clap;
183 /// # use clap::{ArgGroup};
184 /// let args: Vec<&str> = vec!["a1".into(), "a4".into()];
185 /// let grp = ArgGroup::new("program").args(&args);
186 ///
187 /// for (pos, arg) in grp.get_args().enumerate() {
188 /// assert_eq!(*arg, args[pos]);
189 /// }
190 /// ```
191 pub fn get_args(&self) -> impl Iterator<Item = &Id> {
192 self.args.iter()
193 }
194
195 /// Allows more than one of the [`Arg`]s in this group to be used. (Default: `false`)
196 ///
197 /// # Examples
198 ///
199 /// Notice in this example we use *both* the `-f` and `-c` flags which are both part of the
200 /// group
201 ///
202 /// ```rust
203 /// # use clap_builder as clap;
204 /// # use clap::{Command, Arg, ArgGroup, ArgAction};
205 /// let m = Command::new("myprog")
206 /// .arg(Arg::new("flag")
207 /// .short('f')
208 /// .action(ArgAction::SetTrue))
209 /// .arg(Arg::new("color")
210 /// .short('c')
211 /// .action(ArgAction::SetTrue))
212 /// .group(ArgGroup::new("req_flags")
213 /// .args(["flag", "color"])
214 /// .multiple(true))
215 /// .get_matches_from(vec!["myprog", "-f", "-c"]);
216 /// // maybe we don't know which of the two flags was used...
217 /// assert!(m.contains_id("req_flags"));
218 /// ```
219 /// In this next example, we show the default behavior (i.e. `multiple(false)`) which will throw
220 /// an error if more than one of the args in the group was used.
221 ///
222 /// ```rust
223 /// # use clap_builder as clap;
224 /// # use clap::{Command, Arg, ArgGroup, error::ErrorKind, ArgAction};
225 /// let result = Command::new("myprog")
226 /// .arg(Arg::new("flag")
227 /// .short('f')
228 /// .action(ArgAction::SetTrue))
229 /// .arg(Arg::new("color")
230 /// .short('c')
231 /// .action(ArgAction::SetTrue))
232 /// .group(ArgGroup::new("req_flags")
233 /// .args(["flag", "color"]))
234 /// .try_get_matches_from(vec!["myprog", "-f", "-c"]);
235 /// // Because we used both args in the group it's an error
236 /// assert!(result.is_err());
237 /// let err = result.unwrap_err();
238 /// assert_eq!(err.kind(), ErrorKind::ArgumentConflict);
239 /// ```
240 ///
241 /// [`Arg`]: crate::Arg
242 #[inline]
243 #[must_use]
244 pub fn multiple(mut self, yes: bool) -> Self {
245 self.multiple = yes;
246 self
247 }
248
249 /// Return true if the group allows more than one of the arguments
250 /// in this group to be used. (Default: `false`)
251 ///
252 /// # Example
253 ///
254 /// ```rust
255 /// # use clap_builder as clap;
256 /// # use clap::{ArgGroup};
257 /// let mut group = ArgGroup::new("myprog")
258 /// .args(["f", "c"])
259 /// .multiple(true);
260 ///
261 /// assert!(group.is_multiple());
262 /// ```
263 pub fn is_multiple(&mut self) -> bool {
264 self.multiple
265 }
266
267 /// Require an argument from the group to be present when parsing.
268 ///
269 /// This is unless conflicting with another argument. A required group will be displayed in
270 /// the usage string of the application in the format `<arg|arg2|arg3>`.
271 ///
272 /// <div class="warning">
273 ///
274 /// **NOTE:** This setting only applies to the current [`Command`] / [`Subcommand`]s, and not
275 /// globally.
276 ///
277 /// </div>
278 ///
279 /// <div class="warning">
280 ///
281 /// **NOTE:** By default, [`ArgGroup::multiple`] is set to `false` which when combined with
282 /// `ArgGroup::required(true)` states, "One and *only one* arg must be used from this group.
283 /// Use of more than one arg is an error." Vice setting `ArgGroup::multiple(true)` which
284 /// states, '*At least* one arg from this group must be used. Using multiple is OK."
285 ///
286 /// </div>
287 ///
288 /// # Examples
289 ///
290 /// ```rust
291 /// # use clap_builder as clap;
292 /// # use clap::{Command, Arg, ArgGroup, error::ErrorKind, ArgAction};
293 /// let result = Command::new("myprog")
294 /// .arg(Arg::new("flag")
295 /// .short('f')
296 /// .action(ArgAction::SetTrue))
297 /// .arg(Arg::new("color")
298 /// .short('c')
299 /// .action(ArgAction::SetTrue))
300 /// .group(ArgGroup::new("req_flags")
301 /// .args(["flag", "color"])
302 /// .required(true))
303 /// .try_get_matches_from(vec!["myprog"]);
304 /// // Because we didn't use any of the args in the group, it's an error
305 /// assert!(result.is_err());
306 /// let err = result.unwrap_err();
307 /// assert_eq!(err.kind(), ErrorKind::MissingRequiredArgument);
308 /// ```
309 ///
310 /// [`Subcommand`]: crate::Subcommand
311 /// [`ArgGroup::multiple`]: ArgGroup::multiple()
312 /// [`Command`]: crate::Command
313 #[inline]
314 #[must_use]
315 pub fn required(mut self, yes: bool) -> Self {
316 self.required = yes;
317 self
318 }
319
320 /// Specify an argument or group that must be present when this group is.
321 ///
322 /// This is not to be confused with a [required group]. Requirement rules function just like
323 /// [argument requirement rules], you can name other arguments or groups that must be present
324 /// when any one of the arguments from this group is used.
325 ///
326 /// <div class="warning">
327 ///
328 /// **NOTE:** The name provided may be an argument or group name
329 ///
330 /// </div>
331 ///
332 /// # Examples
333 ///
334 /// ```rust
335 /// # use clap_builder as clap;
336 /// # use clap::{Command, Arg, ArgGroup, error::ErrorKind, ArgAction};
337 /// let result = Command::new("myprog")
338 /// .arg(Arg::new("flag")
339 /// .short('f')
340 /// .action(ArgAction::SetTrue))
341 /// .arg(Arg::new("color")
342 /// .short('c')
343 /// .action(ArgAction::SetTrue))
344 /// .arg(Arg::new("debug")
345 /// .short('d')
346 /// .action(ArgAction::SetTrue))
347 /// .group(ArgGroup::new("req_flags")
348 /// .args(["flag", "color"])
349 /// .requires("debug"))
350 /// .try_get_matches_from(vec!["myprog", "-c"]);
351 /// // because we used an arg from the group, and the group requires "-d" to be used, it's an
352 /// // error
353 /// assert!(result.is_err());
354 /// let err = result.unwrap_err();
355 /// assert_eq!(err.kind(), ErrorKind::MissingRequiredArgument);
356 /// ```
357 /// [required group]: ArgGroup::required()
358 /// [argument requirement rules]: crate::Arg::requires()
359 #[must_use]
360 pub fn requires(mut self, id: impl IntoResettable<Id>) -> Self {
361 if let Some(id) = id.into_resettable().into_option() {
362 self.requires.push(id);
363 } else {
364 self.requires.clear();
365 }
366 self
367 }
368
369 /// Specify arguments or groups that must be present when this group is.
370 ///
371 /// This is not to be confused with a [required group]. Requirement rules function just like
372 /// [argument requirement rules], you can name other arguments or groups that must be present
373 /// when one of the arguments from this group is used.
374 ///
375 /// <div class="warning">
376 ///
377 /// **NOTE:** The names provided may be an argument or group name
378 ///
379 /// </div>
380 ///
381 /// # Examples
382 ///
383 /// ```rust
384 /// # use clap_builder as clap;
385 /// # use clap::{Command, Arg, ArgGroup, error::ErrorKind, ArgAction};
386 /// let result = Command::new("myprog")
387 /// .arg(Arg::new("flag")
388 /// .short('f')
389 /// .action(ArgAction::SetTrue))
390 /// .arg(Arg::new("color")
391 /// .short('c')
392 /// .action(ArgAction::SetTrue))
393 /// .arg(Arg::new("debug")
394 /// .short('d')
395 /// .action(ArgAction::SetTrue))
396 /// .arg(Arg::new("verb")
397 /// .short('v')
398 /// .action(ArgAction::SetTrue))
399 /// .group(ArgGroup::new("req_flags")
400 /// .args(["flag", "color"])
401 /// .requires_all(["debug", "verb"]))
402 /// .try_get_matches_from(vec!["myprog", "-c", "-d"]);
403 /// // because we used an arg from the group, and the group requires "-d" and "-v" to be used,
404 /// // yet we only used "-d" it's an error
405 /// assert!(result.is_err());
406 /// let err = result.unwrap_err();
407 /// assert_eq!(err.kind(), ErrorKind::MissingRequiredArgument);
408 /// ```
409 /// [required group]: ArgGroup::required()
410 /// [argument requirement rules]: crate::Arg::requires_ifs()
411 #[must_use]
412 pub fn requires_all(mut self, ns: impl IntoIterator<Item = impl Into<Id>>) -> Self {
413 for n in ns {
414 self = self.requires(n);
415 }
416 self
417 }
418
419 /// Specify an argument or group that must **not** be present when this group is.
420 ///
421 /// Exclusion (aka conflict) rules function just like [argument exclusion rules], you can name
422 /// other arguments or groups that must *not* be present when one of the arguments from this
423 /// group are used.
424 ///
425 /// <div class="warning">
426 ///
427 /// **NOTE:** The name provided may be an argument, or group name
428 ///
429 /// </div>
430 ///
431 /// # Examples
432 ///
433 /// ```rust
434 /// # use clap_builder as clap;
435 /// # use clap::{Command, Arg, ArgGroup, error::ErrorKind, ArgAction};
436 /// let result = Command::new("myprog")
437 /// .arg(Arg::new("flag")
438 /// .short('f')
439 /// .action(ArgAction::SetTrue))
440 /// .arg(Arg::new("color")
441 /// .short('c')
442 /// .action(ArgAction::SetTrue))
443 /// .arg(Arg::new("debug")
444 /// .short('d')
445 /// .action(ArgAction::SetTrue))
446 /// .group(ArgGroup::new("req_flags")
447 /// .args(["flag", "color"])
448 /// .conflicts_with("debug"))
449 /// .try_get_matches_from(vec!["myprog", "-c", "-d"]);
450 /// // because we used an arg from the group, and the group conflicts with "-d", it's an error
451 /// assert!(result.is_err());
452 /// let err = result.unwrap_err();
453 /// assert_eq!(err.kind(), ErrorKind::ArgumentConflict);
454 /// ```
455 /// [argument exclusion rules]: crate::Arg::conflicts_with()
456 #[must_use]
457 pub fn conflicts_with(mut self, id: impl IntoResettable<Id>) -> Self {
458 if let Some(id) = id.into_resettable().into_option() {
459 self.conflicts.push(id);
460 } else {
461 self.conflicts.clear();
462 }
463 self
464 }
465
466 /// Specify arguments or groups that must **not** be present when this group is.
467 ///
468 /// Exclusion rules function just like [argument exclusion rules], you can name other arguments
469 /// or groups that must *not* be present when one of the arguments from this group are used.
470 ///
471 /// <div class="warning">
472 ///
473 /// **NOTE:** The names provided may be an argument, or group name
474 ///
475 /// </div>
476 ///
477 /// # Examples
478 ///
479 /// ```rust
480 /// # use clap_builder as clap;
481 /// # use clap::{Command, Arg, ArgGroup, error::ErrorKind, ArgAction};
482 /// let result = Command::new("myprog")
483 /// .arg(Arg::new("flag")
484 /// .short('f')
485 /// .action(ArgAction::SetTrue))
486 /// .arg(Arg::new("color")
487 /// .short('c')
488 /// .action(ArgAction::SetTrue))
489 /// .arg(Arg::new("debug")
490 /// .short('d')
491 /// .action(ArgAction::SetTrue))
492 /// .arg(Arg::new("verb")
493 /// .short('v')
494 /// .action(ArgAction::SetTrue))
495 /// .group(ArgGroup::new("req_flags")
496 /// .args(["flag", "color"])
497 /// .conflicts_with_all(["debug", "verb"]))
498 /// .try_get_matches_from(vec!["myprog", "-c", "-v"]);
499 /// // because we used an arg from the group, and the group conflicts with either "-v" or "-d"
500 /// // it's an error
501 /// assert!(result.is_err());
502 /// let err = result.unwrap_err();
503 /// assert_eq!(err.kind(), ErrorKind::ArgumentConflict);
504 /// ```
505 ///
506 /// [argument exclusion rules]: crate::Arg::conflicts_with_all()
507 #[must_use]
508 pub fn conflicts_with_all(mut self, ns: impl IntoIterator<Item = impl Into<Id>>) -> Self {
509 for n in ns {
510 self = self.conflicts_with(n);
511 }
512 self
513 }
514}
515
516/// # Reflection
517impl ArgGroup {
518 /// Get the name of the group
519 #[inline]
520 pub fn get_id(&self) -> &Id {
521 &self.id
522 }
523
524 /// Reports whether [`ArgGroup::required`] is set
525 #[inline]
526 pub fn is_required_set(&self) -> bool {
527 self.required
528 }
529}
530
531impl From<&'_ ArgGroup> for ArgGroup {
532 fn from(g: &ArgGroup) -> Self {
533 g.clone()
534 }
535}
536
537#[cfg(test)]
538mod test {
539 use super::*;
540
541 #[test]
542 fn groups() {
543 let g = ArgGroup::new("test")
544 .arg("a1")
545 .arg("a4")
546 .args(["a2", "a3"])
547 .required(true)
548 .conflicts_with("c1")
549 .conflicts_with_all(["c2", "c3"])
550 .conflicts_with("c4")
551 .requires("r1")
552 .requires_all(["r2", "r3"])
553 .requires("r4");
554
555 let args: Vec<Id> = vec!["a1".into(), "a4".into(), "a2".into(), "a3".into()];
556 let reqs: Vec<Id> = vec!["r1".into(), "r2".into(), "r3".into(), "r4".into()];
557 let confs: Vec<Id> = vec!["c1".into(), "c2".into(), "c3".into(), "c4".into()];
558
559 assert_eq!(g.args, args);
560 assert_eq!(g.requires, reqs);
561 assert_eq!(g.conflicts, confs);
562 }
563
564 #[test]
565 fn test_from() {
566 let g = ArgGroup::new("test")
567 .arg("a1")
568 .arg("a4")
569 .args(["a2", "a3"])
570 .required(true)
571 .conflicts_with("c1")
572 .conflicts_with_all(["c2", "c3"])
573 .conflicts_with("c4")
574 .requires("r1")
575 .requires_all(["r2", "r3"])
576 .requires("r4");
577
578 let args: Vec<Id> = vec!["a1".into(), "a4".into(), "a2".into(), "a3".into()];
579 let reqs: Vec<Id> = vec!["r1".into(), "r2".into(), "r3".into(), "r4".into()];
580 let confs: Vec<Id> = vec!["c1".into(), "c2".into(), "c3".into(), "c4".into()];
581
582 let g2 = ArgGroup::from(&g);
583 assert_eq!(g2.args, args);
584 assert_eq!(g2.requires, reqs);
585 assert_eq!(g2.conflicts, confs);
586 }
587
588 // This test will *fail to compile* if ArgGroup is not Send + Sync
589 #[test]
590 fn arg_group_send_sync() {
591 fn foo<T: Send + Sync>(_: T) {}
592 foo(ArgGroup::new("test"));
593 }
594
595 #[test]
596 fn arg_group_expose_is_multiple_helper() {
597 let args: Vec<Id> = vec!["a1".into(), "a4".into()];
598
599 let mut grp_multiple = ArgGroup::new("test_multiple").args(&args).multiple(true);
600 assert!(grp_multiple.is_multiple());
601
602 let mut grp_not_multiple = ArgGroup::new("test_multiple").args(&args).multiple(false);
603 assert!(!grp_not_multiple.is_multiple());
604 }
605
606 #[test]
607 fn arg_group_expose_get_args_helper() {
608 let args: Vec<Id> = vec!["a1".into(), "a4".into()];
609 let grp = ArgGroup::new("program").args(&args);
610
611 for (pos, arg) in grp.get_args().enumerate() {
612 assert_eq!(*arg, args[pos]);
613 }
614 }
615}