palex/
string_input.rs

1use crate::{Input, TokenKind};
2
3/// The default input type for argument parsing. This is generic over its
4/// iterator type and can be used with [`std::env::args`]. See
5/// [`StringInput::new()`] for more information.
6///
7/// Getting the current token and token kind is very cheap. Bumping the token is
8/// a bit more expensive, since it involves more complicated logic and might
9/// re-allocate.
10pub struct StringInput<I: Iterator<Item = String> = std::env::Args> {
11    current: Option<(usize, usize, TokenKind)>,
12    iter: I,
13    buf: String,
14    ignore_dashes: bool,
15}
16
17impl<I: Iterator<Item = String>> StringInput<I> {
18    /// Creates a new instance of this input.
19    ///
20    /// ### Example:
21    ///
22    /// ```
23    /// # use palex::StringInput;
24    /// let mut _input = StringInput::new(std::env::args());
25    /// ```
26    ///
27    /// You probably want to discard the first argument in this case, which is
28    /// just the path to the executable.
29    pub fn new(mut iter: I) -> Self {
30        match iter.next() {
31            Some(buf) => Self {
32                current: Some(Self::trim_leading_dashes(false, &buf, 0)),
33                iter,
34                buf,
35                ignore_dashes: false,
36            },
37            None => {
38                Self { current: None, iter, buf: String::new(), ignore_dashes: false }
39            }
40        }
41    }
42
43    fn trim_leading_dashes(
44        ignore: bool,
45        string: &str,
46        current: usize,
47    ) -> (usize, usize, TokenKind) {
48        if ignore {
49            (current, current, TokenKind::NoDash)
50        } else if string.starts_with("--") {
51            (current + 2, current, TokenKind::TwoDashes)
52        } else if string.starts_with('-') {
53            (current + 1, current, TokenKind::OneDash)
54        } else {
55            (current, current, TokenKind::NoDash)
56        }
57    }
58
59    fn trim_equals(&self, current: usize, kind: TokenKind) -> (usize, usize, TokenKind) {
60        match kind {
61            TokenKind::NoDash => {}
62            TokenKind::OneDash => {
63                if self.buf[current..].starts_with('=') {
64                    return (current + 1, current + 1, TokenKind::AfterEquals);
65                } else {
66                    return (current, current, TokenKind::AfterOneDash);
67                }
68            }
69            TokenKind::TwoDashes => {
70                if self.buf[current..].starts_with('=') {
71                    return (current + 1, current + 1, TokenKind::AfterEquals);
72                }
73            }
74            TokenKind::AfterOneDash => {
75                if self.buf[current..].starts_with('=') {
76                    return (current + 1, current + 1, TokenKind::AfterEquals);
77                }
78            }
79            TokenKind::AfterEquals => {}
80        }
81        (current, current, kind)
82    }
83}
84
85impl<I: Iterator<Item = String>> Input for StringInput<I> {
86    fn current(&self) -> Option<(&str, TokenKind)> {
87        self.current.map(|(i, _, kind)| (&self.buf[i..], kind))
88    }
89
90    fn current_str_with_leading_dashes(&self) -> Option<&str> {
91        self.current.map(|(_, i, _)| &self.buf[i..])
92    }
93
94    fn bump(&mut self, len: usize) -> &str {
95        if let Some((current, _, kind)) = &mut self.current {
96            let current_len = self.buf.len() - *current;
97            if len > current_len {
98                panic!("index bumped out of bounds: {} > {}", len, current_len);
99            }
100
101            let prev_current = *current;
102            *current += len;
103
104            if current_len == len {
105                match self.iter.next() {
106                    Some(s) => {
107                        self.buf.push_str(&s);
108                        self.current = Some(Self::trim_leading_dashes(
109                            self.ignore_dashes,
110                            &s,
111                            *current,
112                        ));
113                    }
114                    None => self.current = None,
115                }
116            } else {
117                let (current, kind) = (*current, *kind);
118                self.current = Some(self.trim_equals(current, kind));
119            }
120
121            &self.buf[prev_current..prev_current + len]
122        } else {
123            panic!("tried to bump index on empty input by {}", len)
124        }
125    }
126
127    fn bump_with_leading_dashes(&mut self, len: usize) -> &str {
128        if let Some((current, cwd, kind)) = &mut self.current {
129            let current_len = self.buf.len() - *cwd;
130            if len > current_len {
131                panic!("index bumped out of bounds: {} > {}", len, current_len);
132            }
133
134            let prev_current = *cwd;
135            *current += len;
136            *cwd += len;
137
138            if current_len == len {
139                match self.iter.next() {
140                    Some(s) => {
141                        self.buf.push_str(&s);
142                        self.current =
143                            Some(Self::trim_leading_dashes(self.ignore_dashes, &s, *cwd));
144                    }
145                    None => self.current = None,
146                }
147            } else {
148                let (current, kind) = (*current, *kind);
149                self.current = Some(self.trim_equals(current, kind));
150            }
151
152            &self.buf[prev_current..prev_current + len]
153        } else {
154            panic!("tried to bump index on empty input by {}", len)
155        }
156    }
157
158    fn bump_argument(&mut self) -> Option<&str> {
159        if let Some((i, _, _)) = self.current {
160            let len = self.buf.len() - i;
161            Some(self.bump(len))
162        } else {
163            None
164        }
165    }
166
167    fn set_ignore_dashes(&mut self, ignore: bool) {
168        self.ignore_dashes = ignore;
169        if let Some((current, cwd, kind)) = &mut self.current {
170            if ignore {
171                *current = *cwd;
172                *kind = TokenKind::NoDash;
173            } else {
174                self.current =
175                    Some(Self::trim_leading_dashes(ignore, &self.buf[*current..], *cwd));
176            }
177        }
178    }
179
180    fn ignore_dashes(&self) -> bool {
181        self.ignore_dashes
182    }
183}