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}