aboutsummaryrefslogtreecommitdiff
path: root/clap/src/args/group.rs
diff options
context:
space:
mode:
Diffstat (limited to 'clap/src/args/group.rs')
-rw-r--r--clap/src/args/group.rs635
1 files changed, 635 insertions, 0 deletions
diff --git a/clap/src/args/group.rs b/clap/src/args/group.rs
new file mode 100644
index 0000000..f8bfb7a
--- /dev/null
+++ b/clap/src/args/group.rs
@@ -0,0 +1,635 @@
+#[cfg(feature = "yaml")]
+use std::collections::BTreeMap;
+use std::fmt::{Debug, Formatter, Result};
+
+#[cfg(feature = "yaml")]
+use yaml_rust::Yaml;
+
+/// `ArgGroup`s are a family of related [arguments] and way for you to express, "Any of these
+/// arguments". By placing arguments in a logical group, you can create easier requirement and
+/// exclusion rules instead of having to list each argument individually, or when you want a rule
+/// to apply "any but not all" arguments.
+///
+/// For instance, you can make an entire `ArgGroup` required. If [`ArgGroup::multiple(true)`] is
+/// set, this means that at least one argument from that group must be present. If
+/// [`ArgGroup::multiple(false)`] is set (the default), one and *only* one must be present.
+///
+/// You can also do things such as name an entire `ArgGroup` as a [conflict] or [requirement] for
+/// another argument, meaning any of the arguments that belong to that group will cause a failure
+/// if present, or must present respectively.
+///
+/// Perhaps the most common use of `ArgGroup`s is to require one and *only* one argument to be
+/// present out of a given set. Imagine that you had multiple arguments, and you want one of them
+/// to be required, but making all of them required isn't feasible because perhaps they conflict
+/// with each other. For example, lets say that you were building an application where one could
+/// set a given version number by supplying a string with an option argument, i.e.
+/// `--set-ver v1.2.3`, you also wanted to support automatically using a previous version number
+/// and simply incrementing one of the three numbers. So you create three flags `--major`,
+/// `--minor`, and `--patch`. All of these arguments shouldn't be used at one time but you want to
+/// specify that *at least one* of them is used. For this, you can create a group.
+///
+/// Finally, you may use `ArgGroup`s to pull a value from a group of arguments when you don't care
+/// exactly which argument was actually used at runtime.
+///
+/// # Examples
+///
+/// The following example demonstrates using an `ArgGroup` to ensure that one, and only one, of
+/// the arguments from the specified group is present at runtime.
+///
+/// ```rust
+/// # use clap::{App, ArgGroup, ErrorKind};
+/// let result = App::new("app")
+/// .args_from_usage(
+/// "--set-ver [ver] 'set the version manually'
+/// --major 'auto increase major'
+/// --minor 'auto increase minor'
+/// --patch 'auto increase patch'")
+/// .group(ArgGroup::with_name("vers")
+/// .args(&["set-ver", "major", "minor", "patch"])
+/// .required(true))
+/// .get_matches_from_safe(vec!["app", "--major", "--patch"]);
+/// // Because we used two args in the group it's an error
+/// assert!(result.is_err());
+/// let err = result.unwrap_err();
+/// assert_eq!(err.kind, ErrorKind::ArgumentConflict);
+/// ```
+/// This next example shows a passing parse of the same scenario
+///
+/// ```rust
+/// # use clap::{App, ArgGroup};
+/// let result = App::new("app")
+/// .args_from_usage(
+/// "--set-ver [ver] 'set the version manually'
+/// --major 'auto increase major'
+/// --minor 'auto increase minor'
+/// --patch 'auto increase patch'")
+/// .group(ArgGroup::with_name("vers")
+/// .args(&["set-ver", "major", "minor","patch"])
+/// .required(true))
+/// .get_matches_from_safe(vec!["app", "--major"]);
+/// assert!(result.is_ok());
+/// let matches = result.unwrap();
+/// // We may not know which of the args was used, so we can test for the group...
+/// assert!(matches.is_present("vers"));
+/// // we could also alternatively check each arg individually (not shown here)
+/// ```
+/// [`ArgGroup::multiple(true)`]: ./struct.ArgGroup.html#method.multiple
+/// [arguments]: ./struct.Arg.html
+/// [conflict]: ./struct.Arg.html#method.conflicts_with
+/// [requirement]: ./struct.Arg.html#method.requires
+#[derive(Default)]
+pub struct ArgGroup<'a> {
+ #[doc(hidden)] pub name: &'a str,
+ #[doc(hidden)] pub args: Vec<&'a str>,
+ #[doc(hidden)] pub required: bool,
+ #[doc(hidden)] pub requires: Option<Vec<&'a str>>,
+ #[doc(hidden)] pub conflicts: Option<Vec<&'a str>>,
+ #[doc(hidden)] pub multiple: bool,
+}
+
+impl<'a> ArgGroup<'a> {
+ /// Creates a new instance of `ArgGroup` using a unique string name. The name will be used to
+ /// get values from the group or refer to the group inside of conflict and requirement rules.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use clap::{App, ArgGroup};
+ /// ArgGroup::with_name("config")
+ /// # ;
+ /// ```
+ pub fn with_name(n: &'a str) -> Self {
+ ArgGroup {
+ name: n,
+ required: false,
+ args: vec![],
+ requires: None,
+ conflicts: None,
+ multiple: false,
+ }
+ }
+
+ /// Creates a new instance of `ArgGroup` from a .yml (YAML) file.
+ ///
+ /// # Examples
+ ///
+ /// ```ignore
+ /// # #[macro_use]
+ /// # extern crate clap;
+ /// # use clap::ArgGroup;
+ /// # fn main() {
+ /// let yml = load_yaml!("group.yml");
+ /// let ag = ArgGroup::from_yaml(yml);
+ /// # }
+ /// ```
+ #[cfg(feature = "yaml")]
+ pub fn from_yaml(y: &'a Yaml) -> ArgGroup<'a> { ArgGroup::from(y.as_hash().unwrap()) }
+
+ /// Adds an [argument] to this group by name
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use clap::{App, Arg, ArgGroup};
+ /// let m = App::new("myprog")
+ /// .arg(Arg::with_name("flag")
+ /// .short("f"))
+ /// .arg(Arg::with_name("color")
+ /// .short("c"))
+ /// .group(ArgGroup::with_name("req_flags")
+ /// .arg("flag")
+ /// .arg("color"))
+ /// .get_matches_from(vec!["myprog", "-f"]);
+ /// // maybe we don't know which of the two flags was used...
+ /// assert!(m.is_present("req_flags"));
+ /// // but we can also check individually if needed
+ /// assert!(m.is_present("flag"));
+ /// ```
+ /// [argument]: ./struct.Arg.html
+ #[cfg_attr(feature = "lints", allow(should_assert_eq))]
+ pub fn arg(mut self, n: &'a str) -> Self {
+ assert!(
+ self.name != n,
+ "ArgGroup '{}' can not have same name as arg inside it",
+ &*self.name
+ );
+ self.args.push(n);
+ self
+ }
+
+ /// Adds multiple [arguments] to this group by name
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use clap::{App, Arg, ArgGroup};
+ /// let m = App::new("myprog")
+ /// .arg(Arg::with_name("flag")
+ /// .short("f"))
+ /// .arg(Arg::with_name("color")
+ /// .short("c"))
+ /// .group(ArgGroup::with_name("req_flags")
+ /// .args(&["flag", "color"]))
+ /// .get_matches_from(vec!["myprog", "-f"]);
+ /// // maybe we don't know which of the two flags was used...
+ /// assert!(m.is_present("req_flags"));
+ /// // but we can also check individually if needed
+ /// assert!(m.is_present("flag"));
+ /// ```
+ /// [arguments]: ./struct.Arg.html
+ pub fn args(mut self, ns: &[&'a str]) -> Self {
+ for n in ns {
+ self = self.arg(n);
+ }
+ self
+ }
+
+ /// Allows more than one of the ['Arg']s in this group to be used. (Default: `false`)
+ ///
+ /// # Examples
+ ///
+ /// Notice in this example we use *both* the `-f` and `-c` flags which are both part of the
+ /// group
+ ///
+ /// ```rust
+ /// # use clap::{App, Arg, ArgGroup};
+ /// let m = App::new("myprog")
+ /// .arg(Arg::with_name("flag")
+ /// .short("f"))
+ /// .arg(Arg::with_name("color")
+ /// .short("c"))
+ /// .group(ArgGroup::with_name("req_flags")
+ /// .args(&["flag", "color"])
+ /// .multiple(true))
+ /// .get_matches_from(vec!["myprog", "-f", "-c"]);
+ /// // maybe we don't know which of the two flags was used...
+ /// assert!(m.is_present("req_flags"));
+ /// ```
+ /// In this next example, we show the default behavior (i.e. `multiple(false)) which will throw
+ /// an error if more than one of the args in the group was used.
+ ///
+ /// ```rust
+ /// # use clap::{App, Arg, ArgGroup, ErrorKind};
+ /// let result = App::new("myprog")
+ /// .arg(Arg::with_name("flag")
+ /// .short("f"))
+ /// .arg(Arg::with_name("color")
+ /// .short("c"))
+ /// .group(ArgGroup::with_name("req_flags")
+ /// .args(&["flag", "color"]))
+ /// .get_matches_from_safe(vec!["myprog", "-f", "-c"]);
+ /// // Because we used both args in the group it's an error
+ /// assert!(result.is_err());
+ /// let err = result.unwrap_err();
+ /// assert_eq!(err.kind, ErrorKind::ArgumentConflict);
+ /// ```
+ /// ['Arg']: ./struct.Arg.html
+ pub fn multiple(mut self, m: bool) -> Self {
+ self.multiple = m;
+ self
+ }
+
+ /// Sets the group as required or not. A required group will be displayed in the usage string
+ /// of the application in the format `<arg|arg2|arg3>`. A required `ArgGroup` simply states
+ /// that one argument from this group *must* be present at runtime (unless
+ /// conflicting with another argument).
+ ///
+ /// **NOTE:** This setting only applies to the current [`App`] / [`SubCommand`], and not
+ /// globally.
+ ///
+ /// **NOTE:** By default, [`ArgGroup::multiple`] is set to `false` which when combined with
+ /// `ArgGroup::required(true)` states, "One and *only one* arg must be used from this group.
+ /// Use of more than one arg is an error." Vice setting `ArgGroup::multiple(true)` which
+ /// states, '*At least* one arg from this group must be used. Using multiple is OK."
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use clap::{App, Arg, ArgGroup, ErrorKind};
+ /// let result = App::new("myprog")
+ /// .arg(Arg::with_name("flag")
+ /// .short("f"))
+ /// .arg(Arg::with_name("color")
+ /// .short("c"))
+ /// .group(ArgGroup::with_name("req_flags")
+ /// .args(&["flag", "color"])
+ /// .required(true))
+ /// .get_matches_from_safe(vec!["myprog"]);
+ /// // Because we didn't use any of the args in the group, it's an error
+ /// assert!(result.is_err());
+ /// let err = result.unwrap_err();
+ /// assert_eq!(err.kind, ErrorKind::MissingRequiredArgument);
+ /// ```
+ /// [`App`]: ./struct.App.html
+ /// [`SubCommand`]: ./struct.SubCommand.html
+ /// [`ArgGroup::multiple`]: ./struct.ArgGroup.html#method.multiple
+ pub fn required(mut self, r: bool) -> Self {
+ self.required = r;
+ self
+ }
+
+ /// Sets the requirement rules of this group. This is not to be confused with a
+ /// [required group]. Requirement rules function just like [argument requirement rules], you
+ /// can name other arguments or groups that must be present when any one of the arguments from
+ /// this group is used.
+ ///
+ /// **NOTE:** The name provided may be an argument, or group name
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use clap::{App, Arg, ArgGroup, ErrorKind};
+ /// let result = App::new("myprog")
+ /// .arg(Arg::with_name("flag")
+ /// .short("f"))
+ /// .arg(Arg::with_name("color")
+ /// .short("c"))
+ /// .arg(Arg::with_name("debug")
+ /// .short("d"))
+ /// .group(ArgGroup::with_name("req_flags")
+ /// .args(&["flag", "color"])
+ /// .requires("debug"))
+ /// .get_matches_from_safe(vec!["myprog", "-c"]);
+ /// // because we used an arg from the group, and the group requires "-d" to be used, it's an
+ /// // error
+ /// assert!(result.is_err());
+ /// let err = result.unwrap_err();
+ /// assert_eq!(err.kind, ErrorKind::MissingRequiredArgument);
+ /// ```
+ /// [required group]: ./struct.ArgGroup.html#method.required
+ /// [argument requirement rules]: ./struct.Arg.html#method.requires
+ pub fn requires(mut self, n: &'a str) -> Self {
+ if let Some(ref mut reqs) = self.requires {
+ reqs.push(n);
+ } else {
+ self.requires = Some(vec![n]);
+ }
+ self
+ }
+
+ /// Sets the requirement rules of this group. This is not to be confused with a
+ /// [required group]. Requirement rules function just like [argument requirement rules], you
+ /// can name other arguments or groups that must be present when one of the arguments from this
+ /// group is used.
+ ///
+ /// **NOTE:** The names provided may be an argument, or group name
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use clap::{App, Arg, ArgGroup, ErrorKind};
+ /// let result = App::new("myprog")
+ /// .arg(Arg::with_name("flag")
+ /// .short("f"))
+ /// .arg(Arg::with_name("color")
+ /// .short("c"))
+ /// .arg(Arg::with_name("debug")
+ /// .short("d"))
+ /// .arg(Arg::with_name("verb")
+ /// .short("v"))
+ /// .group(ArgGroup::with_name("req_flags")
+ /// .args(&["flag", "color"])
+ /// .requires_all(&["debug", "verb"]))
+ /// .get_matches_from_safe(vec!["myprog", "-c", "-d"]);
+ /// // because we used an arg from the group, and the group requires "-d" and "-v" to be used,
+ /// // yet we only used "-d" it's an error
+ /// assert!(result.is_err());
+ /// let err = result.unwrap_err();
+ /// assert_eq!(err.kind, ErrorKind::MissingRequiredArgument);
+ /// ```
+ /// [required group]: ./struct.ArgGroup.html#method.required
+ /// [argument requirement rules]: ./struct.Arg.html#method.requires_all
+ pub fn requires_all(mut self, ns: &[&'a str]) -> Self {
+ for n in ns {
+ self = self.requires(n);
+ }
+ self
+ }
+
+ /// Sets the exclusion rules of this group. Exclusion (aka conflict) rules function just like
+ /// [argument exclusion rules], you can name other arguments or groups that must *not* be
+ /// present when one of the arguments from this group are used.
+ ///
+ /// **NOTE:** The name provided may be an argument, or group name
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use clap::{App, Arg, ArgGroup, ErrorKind};
+ /// let result = App::new("myprog")
+ /// .arg(Arg::with_name("flag")
+ /// .short("f"))
+ /// .arg(Arg::with_name("color")
+ /// .short("c"))
+ /// .arg(Arg::with_name("debug")
+ /// .short("d"))
+ /// .group(ArgGroup::with_name("req_flags")
+ /// .args(&["flag", "color"])
+ /// .conflicts_with("debug"))
+ /// .get_matches_from_safe(vec!["myprog", "-c", "-d"]);
+ /// // because we used an arg from the group, and the group conflicts with "-d", it's an error
+ /// assert!(result.is_err());
+ /// let err = result.unwrap_err();
+ /// assert_eq!(err.kind, ErrorKind::ArgumentConflict);
+ /// ```
+ /// [argument exclusion rules]: ./struct.Arg.html#method.conflicts_with
+ pub fn conflicts_with(mut self, n: &'a str) -> Self {
+ if let Some(ref mut confs) = self.conflicts {
+ confs.push(n);
+ } else {
+ self.conflicts = Some(vec![n]);
+ }
+ self
+ }
+
+ /// Sets the exclusion rules of this group. Exclusion rules function just like
+ /// [argument exclusion rules], you can name other arguments or groups that must *not* be
+ /// present when one of the arguments from this group are used.
+ ///
+ /// **NOTE:** The names provided may be an argument, or group name
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use clap::{App, Arg, ArgGroup, ErrorKind};
+ /// let result = App::new("myprog")
+ /// .arg(Arg::with_name("flag")
+ /// .short("f"))
+ /// .arg(Arg::with_name("color")
+ /// .short("c"))
+ /// .arg(Arg::with_name("debug")
+ /// .short("d"))
+ /// .arg(Arg::with_name("verb")
+ /// .short("v"))
+ /// .group(ArgGroup::with_name("req_flags")
+ /// .args(&["flag", "color"])
+ /// .conflicts_with_all(&["debug", "verb"]))
+ /// .get_matches_from_safe(vec!["myprog", "-c", "-v"]);
+ /// // because we used an arg from the group, and the group conflicts with either "-v" or "-d"
+ /// // it's an error
+ /// assert!(result.is_err());
+ /// let err = result.unwrap_err();
+ /// assert_eq!(err.kind, ErrorKind::ArgumentConflict);
+ /// ```
+ /// [argument exclusion rules]: ./struct.Arg.html#method.conflicts_with_all
+ pub fn conflicts_with_all(mut self, ns: &[&'a str]) -> Self {
+ for n in ns {
+ self = self.conflicts_with(n);
+ }
+ self
+ }
+}
+
+impl<'a> Debug for ArgGroup<'a> {
+ fn fmt(&self, f: &mut Formatter) -> Result {
+ write!(
+ f,
+ "{{\n\
+ \tname: {:?},\n\
+ \targs: {:?},\n\
+ \trequired: {:?},\n\
+ \trequires: {:?},\n\
+ \tconflicts: {:?},\n\
+ }}",
+ self.name,
+ self.args,
+ self.required,
+ self.requires,
+ self.conflicts
+ )
+ }
+}
+
+impl<'a, 'z> From<&'z ArgGroup<'a>> for ArgGroup<'a> {
+ fn from(g: &'z ArgGroup<'a>) -> Self {
+ ArgGroup {
+ name: g.name,
+ required: g.required,
+ args: g.args.clone(),
+ requires: g.requires.clone(),
+ conflicts: g.conflicts.clone(),
+ multiple: g.multiple,
+ }
+ }
+}
+
+#[cfg(feature = "yaml")]
+impl<'a> From<&'a BTreeMap<Yaml, Yaml>> for ArgGroup<'a> {
+ fn from(b: &'a BTreeMap<Yaml, Yaml>) -> Self {
+ // We WANT this to panic on error...so expect() is good.
+ let mut a = ArgGroup::default();
+ let group_settings = if b.len() == 1 {
+ let name_yml = b.keys().nth(0).expect("failed to get name");
+ let name_str = name_yml
+ .as_str()
+ .expect("failed to convert arg YAML name to str");
+ a.name = name_str;
+ b.get(name_yml)
+ .expect("failed to get name_str")
+ .as_hash()
+ .expect("failed to convert to a hash")
+ } else {
+ b
+ };
+
+ for (k, v) in group_settings {
+ a = match k.as_str().unwrap() {
+ "required" => a.required(v.as_bool().unwrap()),
+ "multiple" => a.multiple(v.as_bool().unwrap()),
+ "args" => yaml_vec_or_str!(v, a, arg),
+ "arg" => {
+ if let Some(ys) = v.as_str() {
+ a = a.arg(ys);
+ }
+ a
+ }
+ "requires" => yaml_vec_or_str!(v, a, requires),
+ "conflicts_with" => yaml_vec_or_str!(v, a, conflicts_with),
+ "name" => {
+ if let Some(ys) = v.as_str() {
+ a.name = ys;
+ }
+ a
+ }
+ s => panic!(
+ "Unknown ArgGroup setting '{}' in YAML file for \
+ ArgGroup '{}'",
+ s,
+ a.name
+ ),
+ }
+ }
+
+ a
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::ArgGroup;
+ #[cfg(feature = "yaml")]
+ use yaml_rust::YamlLoader;
+
+ #[test]
+ fn groups() {
+ let g = ArgGroup::with_name("test")
+ .arg("a1")
+ .arg("a4")
+ .args(&["a2", "a3"])
+ .required(true)
+ .conflicts_with("c1")
+ .conflicts_with_all(&["c2", "c3"])
+ .conflicts_with("c4")
+ .requires("r1")
+ .requires_all(&["r2", "r3"])
+ .requires("r4");
+
+ let args = vec!["a1", "a4", "a2", "a3"];
+ let reqs = vec!["r1", "r2", "r3", "r4"];
+ let confs = vec!["c1", "c2", "c3", "c4"];
+
+ assert_eq!(g.args, args);
+ assert_eq!(g.requires, Some(reqs));
+ assert_eq!(g.conflicts, Some(confs));
+ }
+
+ #[test]
+ fn test_debug() {
+ let g = ArgGroup::with_name("test")
+ .arg("a1")
+ .arg("a4")
+ .args(&["a2", "a3"])
+ .required(true)
+ .conflicts_with("c1")
+ .conflicts_with_all(&["c2", "c3"])
+ .conflicts_with("c4")
+ .requires("r1")
+ .requires_all(&["r2", "r3"])
+ .requires("r4");
+
+ let args = vec!["a1", "a4", "a2", "a3"];
+ let reqs = vec!["r1", "r2", "r3", "r4"];
+ let confs = vec!["c1", "c2", "c3", "c4"];
+
+ let debug_str = format!(
+ "{{\n\
+ \tname: \"test\",\n\
+ \targs: {:?},\n\
+ \trequired: {:?},\n\
+ \trequires: {:?},\n\
+ \tconflicts: {:?},\n\
+ }}",
+ args,
+ true,
+ Some(reqs),
+ Some(confs)
+ );
+ assert_eq!(&*format!("{:?}", g), &*debug_str);
+ }
+
+ #[test]
+ fn test_from() {
+ let g = ArgGroup::with_name("test")
+ .arg("a1")
+ .arg("a4")
+ .args(&["a2", "a3"])
+ .required(true)
+ .conflicts_with("c1")
+ .conflicts_with_all(&["c2", "c3"])
+ .conflicts_with("c4")
+ .requires("r1")
+ .requires_all(&["r2", "r3"])
+ .requires("r4");
+
+ let args = vec!["a1", "a4", "a2", "a3"];
+ let reqs = vec!["r1", "r2", "r3", "r4"];
+ let confs = vec!["c1", "c2", "c3", "c4"];
+
+ let g2 = ArgGroup::from(&g);
+ assert_eq!(g2.args, args);
+ assert_eq!(g2.requires, Some(reqs));
+ assert_eq!(g2.conflicts, Some(confs));
+ }
+
+ #[cfg(feature = "yaml")]
+ #[cfg_attr(feature = "yaml", test)]
+ fn test_yaml() {
+ let g_yaml = "name: test
+args:
+- a1
+- a4
+- a2
+- a3
+conflicts_with:
+- c1
+- c2
+- c3
+- c4
+requires:
+- r1
+- r2
+- r3
+- r4";
+ let yml = &YamlLoader::load_from_str(g_yaml).expect("failed to load YAML file")[0];
+ let g = ArgGroup::from_yaml(yml);
+ let args = vec!["a1", "a4", "a2", "a3"];
+ let reqs = vec!["r1", "r2", "r3", "r4"];
+ let confs = vec!["c1", "c2", "c3", "c4"];
+ assert_eq!(g.args, args);
+ assert_eq!(g.requires, Some(reqs));
+ assert_eq!(g.conflicts, Some(confs));
+ }
+}
+
+impl<'a> Clone for ArgGroup<'a> {
+ fn clone(&self) -> Self {
+ ArgGroup {
+ name: self.name,
+ required: self.required,
+ args: self.args.clone(),
+ requires: self.requires.clone(),
+ conflicts: self.conflicts.clone(),
+ multiple: self.multiple,
+ }
+ }
+}