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}