Skip to main content

cli_forge/
matches.rs

1//! The parsed result.
2//!
3//! A [`Matches`] is what the parser produces for one command level: the flags
4//! that were set, the counts of counting flags, the values that options and
5//! positionals received, and — if a subcommand was invoked — the [`Matches`] for
6//! that subcommand, nested. A command's `run` handler receives the `Matches` for
7//! its own level.
8
9use std::collections::{HashMap, HashSet};
10
11/// Parsed arguments for one command level.
12///
13/// Read flags with [`flag`](Matches::flag), counting flags with
14/// [`count`](Matches::count), single values with [`value`](Matches::value),
15/// repeated/variadic values with [`values`](Matches::values), and descend into an
16/// invoked subcommand with [`subcommand`](Matches::subcommand).
17///
18/// # Examples
19///
20/// ```
21/// use cli_forge::{App, Arg, Command};
22///
23/// let mut app = App::new("demo");
24/// app.register(
25///     Command::new("build")
26///         .arg(Arg::flag("release").short('r'))
27///         .arg(Arg::count("verbose").short('v'))
28///         .arg(Arg::option("jobs").short('j').default("1")),
29/// );
30///
31/// let matches = app.try_parse_from(["build", "-r", "-vv", "--jobs", "8"]).unwrap();
32/// let (name, build) = matches.subcommand().unwrap();
33/// assert_eq!(name, "build");
34/// assert!(build.flag("release"));
35/// assert_eq!(build.count("verbose"), 2);
36/// assert_eq!(build.value("jobs"), Some("8"));
37/// ```
38#[derive(Clone, Debug, Default)]
39pub struct Matches {
40    pub(crate) flags: HashSet<String>,
41    pub(crate) counts: HashMap<String, usize>,
42    pub(crate) values: HashMap<String, Vec<String>>,
43    pub(crate) subcommand: Option<(String, Box<Matches>)>,
44}
45
46impl Matches {
47    /// Whether the flag named `name` was set.
48    ///
49    /// Returns `false` for an unset flag or an unknown name. A
50    /// [counting flag](crate::Arg::count) reports `true` once its count reaches
51    /// one, so `flag` works for either kind.
52    ///
53    /// # Examples
54    ///
55    /// ```
56    /// use cli_forge::{App, Arg, Command};
57    ///
58    /// let mut app = App::new("demo");
59    /// app.register(Command::new("run").arg(Arg::flag("verbose").short('v')));
60    ///
61    /// let m = app.try_parse_from(["run", "-v"]).unwrap();
62    /// assert!(m.subcommand().unwrap().1.flag("verbose"));
63    /// ```
64    #[must_use]
65    pub fn flag(&self, name: &str) -> bool {
66        self.flags.contains(name) || self.count(name) > 0
67    }
68
69    /// How many times the [counting flag](crate::Arg::count) named `name` was
70    /// given.
71    ///
72    /// Returns `0` for a flag that was not given or an unknown name.
73    ///
74    /// # Examples
75    ///
76    /// ```
77    /// use cli_forge::{App, Arg, Command};
78    ///
79    /// let mut app = App::new("demo");
80    /// app.register(Command::new("run").arg(Arg::count("verbose").short('v')));
81    ///
82    /// let quiet = app.try_parse_from(["run"]).unwrap();
83    /// assert_eq!(quiet.subcommand().unwrap().1.count("verbose"), 0);
84    ///
85    /// let loud = app.try_parse_from(["run", "-vvv"]).unwrap();
86    /// assert_eq!(loud.subcommand().unwrap().1.count("verbose"), 3);
87    /// ```
88    #[must_use]
89    pub fn count(&self, name: &str) -> usize {
90        self.counts.get(name).copied().unwrap_or(0)
91    }
92
93    /// The value given for an option or positional named `name`, or its default.
94    ///
95    /// Returns `None` if the argument was not provided and has no default, or if
96    /// the name is unknown. For a [`multiple`](crate::Arg::multiple) argument this
97    /// is the first value; use [`values`](Matches::values) for all of them.
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// use cli_forge::{App, Arg, Command};
103    ///
104    /// let mut app = App::new("demo");
105    /// app.register(Command::new("greet").arg(Arg::positional("name").default("world")));
106    ///
107    /// let provided = app.try_parse_from(["greet", "Ada"]).unwrap();
108    /// assert_eq!(provided.subcommand().unwrap().1.value("name"), Some("Ada"));
109    ///
110    /// let defaulted = app.try_parse_from(["greet"]).unwrap();
111    /// assert_eq!(defaulted.subcommand().unwrap().1.value("name"), Some("world"));
112    /// ```
113    #[must_use]
114    pub fn value(&self, name: &str) -> Option<&str> {
115        self.values
116            .get(name)
117            .and_then(|values| values.first())
118            .map(String::as_str)
119    }
120
121    /// Every value collected for `name`, in the order given.
122    ///
123    /// Yields all values of a [`multiple`](crate::Arg::multiple) option or
124    /// variadic positional; for a single-valued argument it yields its one value
125    /// (or its default). The iterator is empty for an argument that was not
126    /// provided or an unknown name.
127    ///
128    /// # Examples
129    ///
130    /// ```
131    /// use cli_forge::{App, Arg, Command};
132    ///
133    /// let mut app = App::new("cc");
134    /// app.register(Command::new("build").arg(Arg::option("define").short('D').multiple(true)));
135    ///
136    /// let m = app.try_parse_from(["build", "-D", "A=1", "-D", "B=2"]).unwrap();
137    /// let defines: Vec<&str> = m.subcommand().unwrap().1.values("define").collect();
138    /// assert_eq!(defines, ["A=1", "B=2"]);
139    /// ```
140    ///
141    /// Iterate directly without collecting:
142    ///
143    /// ```
144    /// # use cli_forge::{App, Arg, Command};
145    /// # let mut app = App::new("cc");
146    /// # app.register(Command::new("build").arg(Arg::positional("files").multiple(true)));
147    /// # let m = app.try_parse_from(["build", "a", "b"]).unwrap();
148    /// # let (_, build) = m.subcommand().unwrap();
149    /// let mut count = 0;
150    /// for file in build.values("files") {
151    ///     let _ = file;
152    ///     count += 1;
153    /// }
154    /// assert_eq!(count, 2);
155    /// ```
156    pub fn values(&self, name: &str) -> impl Iterator<Item = &str> {
157        self.values
158            .get(name)
159            .into_iter()
160            .flatten()
161            .map(String::as_str)
162    }
163
164    /// The invoked subcommand's name and its own [`Matches`], if one was given.
165    ///
166    /// # Examples
167    ///
168    /// ```
169    /// use cli_forge::{App, Command};
170    ///
171    /// let mut app = App::new("demo");
172    /// app.register(Command::new("status"));
173    ///
174    /// let m = app.try_parse_from(["status"]).unwrap();
175    /// assert_eq!(m.subcommand().map(|(name, _)| name), Some("status"));
176    /// ```
177    #[must_use]
178    pub fn subcommand(&self) -> Option<(&str, &Matches)> {
179        self.subcommand
180            .as_ref()
181            .map(|(name, matches)| (name.as_str(), matches.as_ref()))
182    }
183}