1use std::collections::{BTreeSet, HashSet, VecDeque};
2use std::error::Error;
3
4#[derive(Clone, Debug, PartialEq)]
6#[non_exhaustive]
7pub(crate) struct Block {
8 pub commands: Vec<Command>,
10 pub literal: String,
12 pub line_number: u32,
14}
15
16#[derive(Clone, PartialEq)]
18#[non_exhaustive]
19pub struct Command {
20 pub name: String,
22 pub args: Vec<Argument>,
24 pub prefix: Option<String>,
26 pub tags: HashSet<String>,
28 pub silent: bool,
31 pub fail: bool,
34 pub line_number: u32,
36}
37
38impl std::fmt::Debug for Command {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("Command")
41 .field("name", &self.name)
42 .field("args", &self.args)
43 .field("prefix", &self.prefix)
44 .field("tags", &BTreeSet::from_iter(&self.tags))
46 .field("silent", &self.silent)
47 .field("fail", &self.fail)
48 .field("line_number", &self.line_number)
49 .finish()
50 }
51}
52
53impl Command {
54 pub fn consume_args(&self) -> ArgumentConsumer<'_> {
60 ArgumentConsumer::new(&self.args)
61 }
62}
63
64#[derive(Clone, Debug, PartialEq)]
66#[non_exhaustive]
67pub struct Argument {
68 pub key: Option<String>,
71 pub value: String,
73}
74
75impl Argument {
76 pub fn name(&self) -> &str {
78 match self.key.as_deref() {
79 Some(key) => key,
80 None => &self.value,
81 }
82 }
83
84 pub fn parse<T>(&self) -> Result<T, Box<dyn Error>>
88 where
89 T: std::str::FromStr,
90 <T as std::str::FromStr>::Err: std::fmt::Display,
91 {
92 self.value.parse().map_err(|e| format!("invalid argument '{}': {e}", self.value).into())
93 }
94}
95
96pub struct ArgumentConsumer<'a> {
102 args: VecDeque<&'a Argument>,
103}
104
105impl<'a> Iterator for ArgumentConsumer<'a> {
106 type Item = &'a Argument;
107
108 fn next(&mut self) -> Option<Self::Item> {
110 self.args.pop_front()
111 }
112}
113
114impl<'a> ArgumentConsumer<'a> {
115 fn new(args: &'a [Argument]) -> Self {
117 Self { args: VecDeque::from_iter(args.iter()) }
118 }
119
120 pub fn lookup(&mut self, key: &str) -> Option<&'a Argument> {
123 let arg = self.args.iter().rev().find(|a| a.key.as_deref() == Some(key)).copied();
124 if arg.is_some() {
125 self.args.retain(|a| a.key.as_deref() != Some(key))
126 }
127 arg
128 }
129
130 pub fn lookup_parse<T>(&mut self, key: &str) -> Result<Option<T>, Box<dyn Error>>
133 where
134 T: std::str::FromStr,
135 <T as std::str::FromStr>::Err: std::fmt::Display,
136 {
137 let value = self
138 .args
139 .iter()
140 .rev()
141 .find(|a| a.key.as_deref() == Some(key))
142 .map(|a| a.parse())
143 .transpose()?;
144 if value.is_some() {
145 self.args.retain(|a| a.key.as_deref() != Some(key))
146 }
147 Ok(value)
148 }
149
150 pub fn next_key(&mut self) -> Option<&'a Argument> {
152 self.args.iter().position(|a| a.key.is_some()).map(|i| self.args.remove(i).unwrap())
153 }
154
155 pub fn next_pos(&mut self) -> Option<&'a Argument> {
157 self.args.iter().position(|a| a.key.is_none()).map(|i| self.args.remove(i).unwrap())
158 }
159
160 pub fn reject_rest(&self) -> Result<(), Box<dyn Error>> {
162 if let Some(arg) = self.args.front() {
163 return Err(format!("invalid argument '{}'", arg.name()).into());
164 }
165 Ok(())
166 }
167
168 pub fn rest(&mut self) -> Vec<&'a Argument> {
170 self.args.drain(..).collect()
171 }
172
173 pub fn rest_key(&mut self) -> Vec<&'a Argument> {
175 let keyed: Vec<_> = self.args.iter().filter(|a| a.key.is_some()).copied().collect();
176 if !keyed.is_empty() {
177 self.args.retain(|a| a.key.is_none());
178 }
179 keyed
180 }
181
182 pub fn rest_pos(&mut self) -> Vec<&'a Argument> {
184 let pos: Vec<_> = self.args.iter().filter(|a| a.key.is_none()).copied().collect();
185 if !pos.is_empty() {
186 self.args.retain(|a| a.key.is_some());
187 }
188 pos
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 macro_rules! arg {
198 ($value:expr) => {
199 Argument { key: None, value: $value.to_string() }
200 };
201 ($key:expr => $value:expr) => {
202 Argument { key: Some($key.to_string()), value: $value.to_string() }
203 };
204 }
205
206 macro_rules! cmd {
208 ($input:expr) => {{
209 crate::parser::parse_command(&format!("{}\n", $input)).expect("invalid command")
210 }};
211 }
212
213 #[test]
215 fn argument_name() {
216 assert_eq!(arg!("value").name(), "value");
217 assert_eq!(arg!("key" => "value").name(), "key");
218 }
219
220 #[test]
223 fn argument_parse() {
224 assert_eq!(arg!("-1").parse::<i64>().unwrap(), -1_i64);
225 assert_eq!(arg!("0").parse::<i64>().unwrap(), 0_i64);
226 assert_eq!(arg!("1").parse::<i64>().unwrap(), 1_i64);
227
228 assert_eq!(
229 arg!("").parse::<i64>().unwrap_err().to_string(),
230 "invalid argument '': cannot parse integer from empty string"
231 );
232 assert_eq!(
233 arg!("foo").parse::<i64>().unwrap_err().to_string(),
234 "invalid argument 'foo': invalid digit found in string"
235 );
236
237 assert!(!arg!("false").parse::<bool>().unwrap());
238 assert!(arg!("true").parse::<bool>().unwrap());
239
240 assert_eq!(
241 arg!("").parse::<bool>().unwrap_err().to_string(),
242 "invalid argument '': provided string was not `true` or `false`"
243 );
244 }
245
246 #[test]
248 fn command_consume_args() {
249 let cmd = cmd!("cmd foo key=value bar");
250 assert_eq!(cmd.consume_args().rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[2]]);
251 }
252
253 #[test]
255 fn argument_consumer_lookup() {
256 let cmd = cmd!("cmd value key=value foo=bar key=other");
257
258 let mut args = cmd.consume_args();
261 assert_eq!(args.lookup("unknown"), None);
262 assert_eq!(args.lookup("value"), None);
263 assert_eq!(args.rest().len(), 4);
264
265 let mut args = cmd.consume_args();
267 assert_eq!(args.lookup("key"), Some(&cmd.args[3]));
268 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[2]]);
269
270 let mut args = cmd.consume_args();
272 assert_eq!(args.lookup("foo"), Some(&cmd.args[2]));
273 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[3]]);
274 }
275
276 #[test]
278 fn argument_consumer_lookup_parse() {
279 let cmd = cmd!("cmd value key=1 foo=bar key=2");
280
281 let mut args = cmd.consume_args();
284 assert_eq!(args.lookup_parse::<String>("unknown").unwrap(), None);
285 assert_eq!(args.lookup_parse::<String>("value").unwrap(), None);
286 assert_eq!(args.rest().len(), 4);
287
288 let mut args = cmd.consume_args();
290 assert_eq!(args.lookup_parse("key").unwrap(), Some(2));
291 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[2]]);
292
293 let mut args = cmd.consume_args();
296 assert_eq!(args.lookup_parse("foo").unwrap(), Some("bar".to_string()));
297 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[3]]);
298
299 let mut args = cmd.consume_args();
302 assert!(args.lookup_parse::<bool>("key").is_err());
303 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[2], &cmd.args[3]]);
304 }
305
306 #[test]
308 fn argument_consumer_next() {
309 let cmd = cmd!("cmd foo key=1 key=2 bar");
310
311 let mut args = cmd.consume_args();
313 assert_eq!(args.next(), Some(&cmd.args[0]));
314 assert_eq!(args.next(), Some(&cmd.args[1]));
315 assert_eq!(args.next(), Some(&cmd.args[2]));
316 assert_eq!(args.next(), Some(&cmd.args[3]));
317 assert_eq!(args.next(), None);
318 assert!(args.rest().is_empty());
319
320 let mut args = cmd.consume_args();
322 assert_eq!(args.next_key(), Some(&cmd.args[1]));
323 assert_eq!(args.next_key(), Some(&cmd.args[2]));
324 assert_eq!(args.next_key(), None);
325 assert_eq!(args.next(), Some(&cmd.args[0]));
326 assert_eq!(args.next(), Some(&cmd.args[3]));
327 assert_eq!(args.next(), None);
328 assert!(args.rest().is_empty());
329
330 let mut args = cmd.consume_args();
332 assert_eq!(args.next_pos(), Some(&cmd.args[0]));
333 assert_eq!(args.next_pos(), Some(&cmd.args[3]));
334 assert_eq!(args.next_pos(), None);
335 assert_eq!(args.next(), Some(&cmd.args[1]));
336 assert_eq!(args.next(), Some(&cmd.args[2]));
337 assert_eq!(args.next(), None);
338 assert!(args.rest().is_empty());
339 }
340
341 #[test]
343 fn argument_consumer_reject_rest() {
344 let cmd = cmd!("cmd");
346 assert!(cmd.consume_args().reject_rest().is_ok());
347
348 let cmd = cmd!("cmd value");
350 let mut args = cmd.consume_args();
351 assert_eq!(args.reject_rest().unwrap_err().to_string(), "invalid argument 'value'");
352 assert!(!args.rest().is_empty());
353
354 let cmd = cmd!("cmd key=value");
356 let mut args = cmd.consume_args();
357 assert_eq!(args.reject_rest().unwrap_err().to_string(), "invalid argument 'key'");
358 assert!(!args.rest().is_empty());
359 }
360
361 #[test]
363 fn argument_consumer_rest() {
364 let cmd = cmd!("cmd foo key=1 key=2 bar");
365
366 let mut args = cmd.consume_args();
368 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[2], &cmd.args[3]]);
369 assert!(args.rest().is_empty());
370
371 let mut args = cmd.consume_args();
373 assert_eq!(args.rest_pos(), vec![&cmd.args[0], &cmd.args[3]]);
374 assert!(args.rest_pos().is_empty());
375 assert_eq!(args.rest(), vec![&cmd.args[1], &cmd.args[2]]);
376
377 let mut args = cmd.consume_args();
379 assert_eq!(args.rest_key(), vec![&cmd.args[1], &cmd.args[2]]);
380 assert!(args.rest_key().is_empty());
381 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[3]]);
382 }
383}