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