cli_forge/arg.rs
1//! Argument and flag definitions.
2//!
3//! An [`Arg`] describes one input a [`Command`](crate::Command) accepts. There
4//! are three kinds, each with its own constructor:
5//!
6//! - [`Arg::flag`] — a boolean switch, `--verbose` / `-v`, present or absent.
7//! - [`Arg::option`] — a named value, `--output file` / `-o file` / `--output=file`.
8//! - [`Arg::positional`] — a bare value identified by position.
9//!
10//! The builder methods ([`short`](Arg::short), [`long`](Arg::long),
11//! [`help`](Arg::help), [`required`](Arg::required), [`default`](Arg::default))
12//! refine the definition and chain. The parser reads these definitions to turn
13//! raw tokens into a [`Matches`](crate::Matches).
14
15/// Which form an [`Arg`] takes on the command line.
16#[derive(Clone, Copy, PartialEq, Eq, Debug)]
17pub(crate) enum ArgKind {
18 /// A boolean switch that takes no value.
19 Flag,
20 /// A named argument that takes a value.
21 Option,
22 /// A value identified by its position.
23 Positional,
24}
25
26/// A single argument a command accepts.
27///
28/// Build one with [`Arg::flag`], [`Arg::option`], or [`Arg::positional`], then
29/// attach it with [`Command::arg`](crate::Command::arg). The `name` is the key
30/// used to read the parsed result back out of a [`Matches`](crate::Matches).
31///
32/// # Examples
33///
34/// ```
35/// use cli_forge::Arg;
36///
37/// let verbose = Arg::flag("verbose").short('v').help("print extra detail");
38/// let output = Arg::option("output").short('o').required(true);
39/// let path = Arg::positional("path").default(".");
40/// ```
41#[derive(Clone, Debug)]
42pub struct Arg {
43 pub(crate) name: String,
44 pub(crate) kind: ArgKind,
45 pub(crate) short: Option<char>,
46 pub(crate) long: Option<String>,
47 pub(crate) help: Option<String>,
48 pub(crate) required: bool,
49 pub(crate) default: Option<String>,
50}
51
52impl Arg {
53 fn new(name: impl Into<String>, kind: ArgKind) -> Arg {
54 let name = name.into();
55 // Flags and options match `--name` by default; a positional has no long.
56 let long = match kind {
57 ArgKind::Flag | ArgKind::Option => Some(name.clone()),
58 ArgKind::Positional => None,
59 };
60 Arg {
61 name,
62 kind,
63 short: None,
64 long,
65 help: None,
66 required: false,
67 default: None,
68 }
69 }
70
71 /// Define a boolean flag, e.g. `--verbose`. The long form defaults to the
72 /// name; add a [`short`](Arg::short) for a one-letter alias.
73 ///
74 /// # Examples
75 ///
76 /// ```
77 /// use cli_forge::Arg;
78 /// let force = Arg::flag("force").short('f');
79 /// ```
80 #[must_use]
81 pub fn flag(name: impl Into<String>) -> Arg {
82 Arg::new(name, ArgKind::Flag)
83 }
84
85 /// Define a value-taking option, e.g. `--output file`. Accepts `--name v`,
86 /// `--name=v`, `-x v`, and `-xv` at parse time.
87 ///
88 /// # Examples
89 ///
90 /// ```
91 /// use cli_forge::Arg;
92 /// let out = Arg::option("output").short('o').required(true);
93 /// ```
94 #[must_use]
95 pub fn option(name: impl Into<String>) -> Arg {
96 Arg::new(name, ArgKind::Option)
97 }
98
99 /// Define a positional argument, filled by bare values in order.
100 ///
101 /// # Examples
102 ///
103 /// ```
104 /// use cli_forge::Arg;
105 /// let path = Arg::positional("path").default(".");
106 /// ```
107 #[must_use]
108 pub fn positional(name: impl Into<String>) -> Arg {
109 Arg::new(name, ArgKind::Positional)
110 }
111
112 /// Set a one-letter short form (`-x`). Ignored for positionals.
113 #[must_use]
114 pub fn short(mut self, short: char) -> Arg {
115 self.short = Some(short);
116 self
117 }
118
119 /// Override the long form (`--name`). Defaults to the argument's name.
120 /// Ignored for positionals.
121 #[must_use]
122 pub fn long(mut self, long: impl Into<String>) -> Arg {
123 self.long = Some(long.into());
124 self
125 }
126
127 /// Attach help text. Surfaced by the help engine (v0.4.0); stored now so
128 /// definitions are complete.
129 #[must_use]
130 pub fn help(mut self, help: impl Into<String>) -> Arg {
131 self.help = Some(help.into());
132 self
133 }
134
135 /// Require the argument. Parsing fails with
136 /// [`ParseError::MissingRequired`](crate::ParseError::MissingRequired) if it
137 /// is absent and has no default. Has no effect on flags (a flag is simply
138 /// present or not).
139 #[must_use]
140 pub fn required(mut self, required: bool) -> Arg {
141 self.required = required;
142 self
143 }
144
145 /// Provide a default value used when an option or positional is omitted. A
146 /// default makes the argument effectively optional even if
147 /// [`required`](Arg::required) was set.
148 #[must_use]
149 pub fn default(mut self, value: impl Into<String>) -> Arg {
150 self.default = Some(value.into());
151 self
152 }
153
154 /// The long form to match, if any.
155 pub(crate) fn long_name(&self) -> Option<&str> {
156 self.long.as_deref()
157 }
158}