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