Skip to main content

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}