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}