cli_forge/arg.rs
1//! Argument and flag definitions.
2//!
3//! An [`Arg`] describes one input a [`Command`](crate::Command) accepts. There
4//! are four kinds, each with its own constructor:
5//!
6//! - [`Arg::flag`] — a boolean switch, `--verbose` / `-v`, present or absent.
7//! - [`Arg::count`] — a repeatable flag whose occurrences are counted, `-vvv`.
8//! - [`Arg::option`] — a named value, `--output file` / `-o file` / `--output=file`.
9//! - [`Arg::positional`] — a bare value identified by position.
10//!
11//! Options and positionals may be marked [`multiple`](Arg::multiple) to collect
12//! every occurrence into a list instead of keeping one value. The builder methods
13//! ([`short`](Arg::short), [`long`](Arg::long), [`help`](Arg::help),
14//! [`required`](Arg::required), [`default`](Arg::default),
15//! [`multiple`](Arg::multiple)) refine the definition and chain. The parser reads
16//! these definitions to turn raw tokens into a [`Matches`](crate::Matches).
17
18/// Which form an [`Arg`] takes on the command line.
19#[derive(Clone, Copy, PartialEq, Eq, Debug)]
20pub(crate) enum ArgKind {
21 /// A boolean switch that takes no value.
22 Flag,
23 /// A repeatable switch whose occurrences are counted.
24 Count,
25 /// A named argument that takes a value.
26 Option,
27 /// A value identified by its position.
28 Positional,
29}
30
31/// A single argument a command accepts.
32///
33/// Build one with [`Arg::flag`], [`Arg::option`], or [`Arg::positional`], then
34/// attach it with [`Command::arg`](crate::Command::arg). The `name` is the key
35/// used to read the parsed result back out of a [`Matches`](crate::Matches).
36///
37/// # Examples
38///
39/// ```
40/// use cli_forge::Arg;
41///
42/// let verbose = Arg::count("verbose").short('v').help("increase verbosity");
43/// let define = Arg::option("define").short('D').multiple(true);
44/// let files = Arg::positional("files").multiple(true).required(true);
45/// ```
46#[derive(Clone, Debug)]
47pub struct Arg {
48 pub(crate) name: String,
49 pub(crate) kind: ArgKind,
50 pub(crate) short: Option<char>,
51 pub(crate) long: Option<String>,
52 pub(crate) help: Option<String>,
53 pub(crate) required: bool,
54 pub(crate) multiple: bool,
55 pub(crate) default: Option<String>,
56}
57
58impl Arg {
59 fn new(name: impl Into<String>, kind: ArgKind) -> Arg {
60 let name = name.into();
61 // Flags, counts, and options match `--name` by default; a positional has
62 // no long form.
63 let long = match kind {
64 ArgKind::Flag | ArgKind::Count | ArgKind::Option => Some(name.clone()),
65 ArgKind::Positional => None,
66 };
67 Arg {
68 name,
69 kind,
70 short: None,
71 long,
72 help: None,
73 required: false,
74 multiple: false,
75 default: None,
76 }
77 }
78
79 /// Define a boolean flag, e.g. `--verbose`. The long form defaults to the
80 /// name; add a [`short`](Arg::short) for a one-letter alias.
81 ///
82 /// # Examples
83 ///
84 /// ```
85 /// use cli_forge::Arg;
86 /// let force = Arg::flag("force").short('f');
87 /// ```
88 #[must_use]
89 pub fn flag(name: impl Into<String>) -> Arg {
90 Arg::new(name, ArgKind::Flag)
91 }
92
93 /// Define a counting flag: a switch that may be repeated, whose occurrences
94 /// are tallied. `-v`, `-vv`, `-vvv` (or `-v -v -v`, or `--verbose --verbose`)
95 /// count 1, 2, 3. Read the count with
96 /// [`Matches::count`](crate::Matches::count).
97 ///
98 /// # Examples
99 ///
100 /// ```
101 /// use cli_forge::{App, Arg, Command};
102 ///
103 /// let mut app = App::new("demo");
104 /// app.register(Command::new("run").arg(Arg::count("verbose").short('v')));
105 ///
106 /// let m = app.try_parse_from(["run", "-vvv"]).unwrap();
107 /// assert_eq!(m.subcommand().unwrap().1.count("verbose"), 3);
108 /// ```
109 #[must_use]
110 pub fn count(name: impl Into<String>) -> Arg {
111 Arg::new(name, ArgKind::Count)
112 }
113
114 /// Define a value-taking option, e.g. `--output file`. Accepts `--name v`,
115 /// `--name=v`, `-x v`, and `-xv` at parse time. Mark it
116 /// [`multiple`](Arg::multiple) to accept it more than once.
117 ///
118 /// # Examples
119 ///
120 /// ```
121 /// use cli_forge::Arg;
122 /// let out = Arg::option("output").short('o').required(true);
123 /// ```
124 #[must_use]
125 pub fn option(name: impl Into<String>) -> Arg {
126 Arg::new(name, ArgKind::Option)
127 }
128
129 /// Define a positional argument, filled by bare values in order.
130 ///
131 /// # Examples
132 ///
133 /// ```
134 /// use cli_forge::Arg;
135 /// let path = Arg::positional("path").default(".");
136 /// ```
137 #[must_use]
138 pub fn positional(name: impl Into<String>) -> Arg {
139 Arg::new(name, ArgKind::Positional)
140 }
141
142 /// Set a one-letter short form (`-x`). Ignored for positionals.
143 #[must_use]
144 pub fn short(mut self, short: char) -> Arg {
145 self.short = Some(short);
146 self
147 }
148
149 /// Override the long form (`--name`). Defaults to the argument's name.
150 /// Ignored for positionals.
151 #[must_use]
152 pub fn long(mut self, long: impl Into<String>) -> Arg {
153 self.long = Some(long.into());
154 self
155 }
156
157 /// Attach help text, shown in generated help.
158 #[must_use]
159 pub fn help(mut self, help: impl Into<String>) -> Arg {
160 self.help = Some(help.into());
161 self
162 }
163
164 /// Require the argument. Parsing fails with
165 /// [`ParseError::MissingRequired`](crate::ParseError::MissingRequired) if it
166 /// is absent and has no default. Has no effect on flags or counts (they are
167 /// simply present or not).
168 #[must_use]
169 pub fn required(mut self, required: bool) -> Arg {
170 self.required = required;
171 self
172 }
173
174 /// Collect every occurrence into a list instead of keeping a single value.
175 ///
176 /// For an [`option`](Arg::option), each `--name v` appends a value:
177 /// `-D A -D B` yields `["A", "B"]`. For a [`positional`](Arg::positional), it
178 /// becomes variadic and absorbs every remaining bare value: `a b c` yields
179 /// `["a", "b", "c"]` (put it last). Read the values with
180 /// [`Matches::values`](crate::Matches::values). Ignored for flags and counts.
181 ///
182 /// # Examples
183 ///
184 /// ```
185 /// use cli_forge::{App, Arg, Command};
186 ///
187 /// let mut app = App::new("cc");
188 /// app.register(
189 /// Command::new("build")
190 /// .arg(Arg::option("include").short('I').multiple(true))
191 /// .arg(Arg::positional("sources").multiple(true)),
192 /// );
193 ///
194 /// let m = app.try_parse_from(["build", "-I", "a", "-I", "b", "x.c", "y.c"]).unwrap();
195 /// let (_, build) = m.subcommand().unwrap();
196 /// assert_eq!(build.values("include").collect::<Vec<_>>(), ["a", "b"]);
197 /// assert_eq!(build.values("sources").collect::<Vec<_>>(), ["x.c", "y.c"]);
198 /// ```
199 #[must_use]
200 pub fn multiple(mut self, multiple: bool) -> Arg {
201 self.multiple = multiple;
202 self
203 }
204
205 /// Provide a default value used when an option or positional is omitted. A
206 /// default makes the argument effectively optional even if
207 /// [`required`](Arg::required) was set.
208 #[must_use]
209 pub fn default(mut self, value: impl Into<String>) -> Arg {
210 self.default = Some(value.into());
211 self
212 }
213
214 /// The long form to match, if any.
215 pub(crate) fn long_name(&self) -> Option<&str> {
216 self.long.as_deref()
217 }
218}