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 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}