palex/
input.rs

1use crate::part::{InputPart, InputPartLD};
2use crate::TokenKind;
3
4/// The trait for types that can produce tokens from a list of command-line
5/// arguments.
6///
7/// To implement this trait efficiently, accessing the current token and its
8/// [`TokenKind`] should be cheap.
9///
10/// This trait is implemented for [crate::StringInput].
11pub trait Input {
12    /// Returns the current token as string slice and the [`TokenKind`] of the
13    /// current token, or [None] if the input is empty.
14    ///
15    /// This function skips the leading dashes of arguments. If you don't want
16    /// that, use [`Input::current_str_with_leading_dashes()`] instead.
17    fn current(&self) -> Option<(&str, TokenKind)>;
18
19    /// Returns the current token (including the leading dashes) as string
20    /// slice, or [None] if the input is empty.
21    fn current_str_with_leading_dashes(&self) -> Option<&str>;
22
23    /// Bumps the current token by `len` bytes.
24    ///
25    /// Leading dashes are ignored, e.g. bumping the argument `--foo` by one
26    /// byte returns `f`; the rest of the token is `oo`. If you don't want
27    /// this, use [`Input::bump_with_leading_dashes()`] instead.
28    ///
29    /// If the bytes are followed by an equals sign and the current
30    /// [`TokenKind`] is `OneDash`, `TwoDashes` or `AfterOneDash`, the
31    /// equals sign is skipped.
32    ///
33    /// If afterwards the current argument is empty, a new argument is read and
34    /// becomes the "current token"
35    fn bump(&mut self, len: usize) -> &str;
36
37    /// Bumps the current token (including leading dashes) by `len` bytes.
38    ///
39    /// If the bytes are followed by an equals sign and the current
40    /// [`TokenKind`] is `OneDash`, `TwoDashes` or `AfterOneDash`, the
41    /// equals sign is skipped.
42    ///
43    /// If afterwards the current argument is empty, a new argument is read and
44    /// becomes the "current token"
45    fn bump_with_leading_dashes(&mut self, len: usize) -> &str;
46
47    /// Bumps the current argument (including leading dashes) completely.
48    fn bump_argument(&mut self) -> Option<&str>;
49
50    /// Sets the parsing mode. When `true`, all arguments are considered
51    /// positional, i.e. leading dashes are ignored.
52    fn set_ignore_dashes(&mut self, ignore: bool);
53
54    /// Returns the parsing mode. When `true`, all arguments are considered
55    /// positional, i.e. leading dashes are ignored.
56    fn ignore_dashes(&self) -> bool;
57
58    /// Returns `true` if the input is empty. This means that all arguments have
59    /// been fully parsed.
60    fn is_empty(&self) -> bool {
61        self.current().is_none()
62    }
63
64    /// Returns `true` if the input is not empty. This means that all arguments
65    /// have been fully parsed.
66    fn is_not_empty(&self) -> bool {
67        self.current().is_some()
68    }
69
70    /// Returns `true` if a value within the same argument is expected. Or in
71    /// other words, if we just consumed a single-dash flag or an equals sign
72    /// and there are remaining bytes in the same argument.
73    fn can_parse_value_no_whitespace(&self) -> bool {
74        if let Some((_, current)) = self.current() {
75            matches!(current, TokenKind::AfterOneDash | TokenKind::AfterEquals)
76        } else {
77            false
78        }
79    }
80
81    /// Returns `true` if the current token can be parsed as a flag or named
82    /// argument (e.g. `-h`, `--help=config`).
83    fn can_parse_dash_argument(&self) -> bool {
84        if let Some((_, current)) = self.current() {
85            matches!(
86                current,
87                TokenKind::OneDash | TokenKind::TwoDashes | TokenKind::AfterOneDash
88            )
89        } else {
90            false
91        }
92    }
93
94    /// Eat the current token if the argument doesn't start with dashes and
95    /// matches `token` exactly.
96    fn eat_no_dash<'a>(&mut self, token: &'a str) -> Option<&str> {
97        if let Some((s, TokenKind::NoDash)) = self.current() {
98            if token == s {
99                return Some(self.bump(token.len()));
100            }
101        }
102        None
103    }
104
105    /// Eat the current token if the argument starts with a single dash, and the
106    /// current token starts with `token`.
107    ///
108    /// Does not work if the token appears after an equals sign has already been
109    /// parsed.
110    fn eat_one_dash<'a>(&mut self, token: &'a str) -> Option<&str> {
111        if let Some((s, TokenKind::OneDash)) | Some((s, TokenKind::AfterOneDash)) =
112            self.current()
113        {
114            if s.starts_with(token) {
115                return Some(self.bump(token.len()));
116            }
117        }
118        None
119    }
120
121    /// Eat the current token if the argument starts with (at least) two dashes,
122    /// and the current token either matches `token` exactly, or starts with
123    /// `token` followed by an equals sign.
124    ///
125    /// Does not work if the token appears after an equals sign has already been
126    /// parsed.
127    fn eat_two_dashes<'a>(&mut self, token: &'a str) -> Option<&str> {
128        if let Some((s, TokenKind::TwoDashes)) = self.current() {
129            if let Some(rest) = s.strip_prefix(token) {
130                if rest.is_empty() || rest.starts_with('=') {
131                    return Some(self.bump(token.len()));
132                }
133            }
134        }
135        None
136    }
137
138    /// Eat the current token if it matches `token` exactly.
139    ///
140    /// This method only works if the current [`TokenKind`] is either `NoDash`,
141    /// `AfterOneDash` or `AfterEquals`.
142    fn eat_value<'a>(&mut self, token: &'a str) -> Option<&str> {
143        if let Some((s, kind)) = self.current() {
144            match kind {
145                TokenKind::TwoDashes | TokenKind::OneDash => return None,
146
147                | TokenKind::NoDash
148                | TokenKind::AfterOneDash
149                | TokenKind::AfterEquals => {
150                    if let Some(rest) = s.strip_prefix(token) {
151                        if rest.is_empty() {
152                            return Some(self.bump(token.len()));
153                        }
154                    }
155                }
156            }
157        }
158        None
159    }
160
161    /// Eat the current token (including any leading dashes) if it matches
162    /// `token` exactly.
163    fn eat_value_allows_leading_dashes<'a>(&mut self, token: &'a str) -> Option<&str> {
164        if let Some(s) = self.current_str_with_leading_dashes() {
165            if let Some(rest) = s.strip_prefix(token) {
166                if rest.is_empty() {
167                    return Some(self.bump_with_leading_dashes(token.len()));
168                }
169            }
170        }
171        None
172    }
173
174    /// If the argument doesn't start with dashes, returns a helper struct for
175    /// obtaining, validating and eating the next token.
176    fn no_dash(&mut self) -> Option<InputPart<'_, Self>>
177    where
178        Self: Sized,
179    {
180        match self.current() {
181            Some((s, TokenKind::NoDash)) => Some(InputPart::new(s.len(), self)),
182            _ => None,
183        }
184    }
185
186    /// If the argument starts with a single dash, returns a helper struct for
187    /// obtaining, validating and eating the next token.
188    fn one_dash(&mut self) -> Option<InputPart<'_, Self>>
189    where
190        Self: Sized,
191    {
192        match self.current() {
193            Some((s, TokenKind::OneDash)) => Some(InputPart::new(s.len(), self)),
194            _ => None,
195        }
196    }
197
198    /// If the argument starts with two (or more) dashes, returns a helper
199    /// struct for obtaining, validating and eating the next token.
200    fn two_dashes(&mut self) -> Option<InputPart<'_, Self>>
201    where
202        Self: Sized,
203    {
204        match self.current() {
205            Some((s, TokenKind::TwoDashes)) => Some(InputPart::new(s.len(), self)),
206            _ => None,
207        }
208    }
209
210    /// Returns a helper struct for obtaining, validating and eating the next
211    /// token. Works only if the current [`TokenKind`] is either `NoDash`,
212    /// `AfterOneDash` or `AfterEquals`.
213    ///
214    /// The value is not allowed to start with a dash, unless the dash is not at
215    /// the start of the current argument.
216    fn value(&mut self) -> Option<InputPart<'_, Self>>
217    where
218        Self: Sized,
219    {
220        match self.current() {
221            | Some((s, TokenKind::NoDash))
222            | Some((s, TokenKind::AfterOneDash))
223            | Some((s, TokenKind::AfterEquals)) => Some(InputPart::new(s.len(), self)),
224            _ => None,
225        }
226    }
227
228    /// Returns a helper struct for obtaining, validating and eating the next
229    /// token. The value is allowed to start with a dash.
230    fn value_allows_leading_dashes(&mut self) -> Option<InputPartLD<'_, Self>>
231    where
232        Self: Sized,
233    {
234        match self.current_str_with_leading_dashes() {
235            Some(s) => Some(InputPartLD::new(s.len(), self)),
236            None => None,
237        }
238    }
239}