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