clap/args/
group.rs

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