goldenscript/
command.rs

1use std::collections::{BTreeSet, HashSet, VecDeque};
2use std::error::Error;
3
4/// A block, consisting of multiple commands.
5#[derive(Clone, Debug, PartialEq)]
6#[non_exhaustive]
7pub(crate) struct Block {
8    /// The commands in the block.
9    pub commands: Vec<Command>,
10    /// The literal string of the input commands. Used to generate the output.
11    pub literal: String,
12    /// The block's line number position in the script.
13    pub line_number: u32,
14}
15
16/// A command.
17#[derive(Clone, PartialEq)]
18#[non_exhaustive]
19pub struct Command {
20    /// The name of the command. Never empty.
21    pub name: String,
22    /// The command's arguments, in the given order.
23    pub args: Vec<Argument>,
24    /// The command prefix, if given.
25    pub prefix: Option<String>,
26    /// Any command tags, if given.
27    pub tags: HashSet<String>,
28    /// Silences the output of this command. This is handled automatically, the
29    /// [`Runner`](crate::Runner) does not have to take this into account.
30    pub silent: bool,
31    /// If true, the command is expected to fail with a panic or error. If the
32    /// command does not fail, the test fails.
33    pub fail: bool,
34    /// The command's line number position in the script.
35    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            // Use a sorted BTreeSet for test determinism.
45            .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    /// Returns an argument consumer, for more convenient argument processing.
55    /// Does not affect [`Command::args`].
56    ///
57    /// See the [module documentation](crate#argument-processing) for usage
58    /// examples.
59    pub fn consume_args(&self) -> ArgumentConsumer<'_> {
60        ArgumentConsumer::new(&self.args)
61    }
62}
63
64/// A command argument.
65#[derive(Clone, Debug, PartialEq)]
66#[non_exhaustive]
67pub struct Argument {
68    /// The argument key, for `key=value` style arguments. Not guaranteed to be
69    /// unique, the [`Runner`](crate::Runner) can handle this as desired.
70    pub key: Option<String>,
71    /// The argument value. Can be empty.
72    pub value: String,
73}
74
75impl Argument {
76    /// Returns a name for the argument -- either the key, if given, or value.
77    pub fn name(&self) -> &str {
78        match self.key.as_deref() {
79            Some(key) => key,
80            None => &self.value,
81        }
82    }
83
84    /// Parses the argument value as a T using core::str::parse(). Convenience
85    /// method that returns an improved error message as a boxed error to ease
86    /// error handling in a [`Runner`](crate::Runner).
87    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
96/// Helper for argument processing, by returning and removing arguments on
97/// demand.
98///
99/// Created by [`Command::consume_args()`]. Implements [`Iterator`], but is also
100/// intended for out-of-order processing, unlike most iterators.
101pub struct ArgumentConsumer<'a> {
102    args: VecDeque<&'a Argument>,
103}
104
105impl<'a> Iterator for ArgumentConsumer<'a> {
106    type Item = &'a Argument;
107
108    /// Returns and removes the next argument, if any.
109    fn next(&mut self) -> Option<Self::Item> {
110        self.args.pop_front()
111    }
112}
113
114impl<'a> ArgumentConsumer<'a> {
115    /// Creates a new argument consumer.
116    fn new(args: &'a [Argument]) -> Self {
117        Self { args: VecDeque::from_iter(args.iter()) }
118    }
119
120    /// Looks up and removes a key/value argument by key. If multiple arguments
121    /// use the same key, the last one is returned (but all are removed).
122    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    /// Looks up and parses a key/value argument by key, removing it. If parsing
131    /// errors, the argument is not removed.
132    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    /// Returns and removes the next key/value argument, if any.
151    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    /// Returns and removes the next positional argument, if any.
156    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    /// Rejects any remaining arguments with an error.
161    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    /// Returns and removes all remaining arguments.
169    pub fn rest(&mut self) -> Vec<&'a Argument> {
170        self.args.drain(..).collect()
171    }
172
173    /// Returns and removes all remaining key/value arguments.
174    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    /// Returns and removes all remaining positional arguments.
183    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    /// Constructs an Argument from a string value or key => value.
197    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    /// Constructs a Command by parsing the given input string.
207    macro_rules! cmd {
208        ($input:expr) => {{
209            crate::parser::parse_command(&format!("{}\n", $input)).expect("invalid command")
210        }};
211    }
212
213    /// Tests Argument.name().
214    #[test]
215    fn argument_name() {
216        assert_eq!(arg!("value").name(), "value");
217        assert_eq!(arg!("key" => "value").name(), "key");
218    }
219
220    /// Basic tests of Argument.parse(). Not comprehensive, since it dispatches
221    /// to core::str::parse().
222    #[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    /// Tests Command.consume_args(). ArgumentConsumer is tested separately.
247    #[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    /// Tests ArgumentConsumer.lookup().
254    #[test]
255    fn argument_consumer_lookup() {
256        let cmd = cmd!("cmd value key=value foo=bar key=other");
257
258        // lookup() returns None on unknown keys, including ones that match a
259        // value argument.
260        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        // lookup() removes duplicate keys, returning the last.
266        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        // lookup() removes single keys.
271        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    /// Tests ArgumentConsumer.lookup_parse().
277    #[test]
278    fn argument_consumer_lookup_parse() {
279        let cmd = cmd!("cmd value key=1 foo=bar key=2");
280
281        // lookup_parse() returns None on unknown keys, including ones that
282        // match a value argument.
283        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        // lookup_parse() parses and removes duplicate keys, returning the last.
289        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        // lookup_parse() parses and removes single keys, with string parsing
294        // being a noop.
295        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        // lookup_parse() does not remove arguments on parse errors, even with
300        // duplicate keys.
301        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    /// Tests ArgumentConsumer.next(), next_pos(), and next_key().
307    #[test]
308    fn argument_consumer_next() {
309        let cmd = cmd!("cmd foo key=1 key=2 bar");
310
311        // next() returns references to all arguments and consumes them.
312        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        // next_key() returns references to key/value arguments and consumes them.
321        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        // next_pos() returns references to key/value arguments and consumes them.
331        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    /// Tests ArgumentConsumer.reject_rest().
342    #[test]
343    fn argument_consumer_reject_rest() {
344        // Empty args return Ok.
345        let cmd = cmd!("cmd");
346        assert!(cmd.consume_args().reject_rest().is_ok());
347
348        // Positional argument fails. It does not consume the arg.
349        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        // Key/value argument fails.
355        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    /// Tests ArgumentConsumer.rest(), rest_pos() and rest_key().
362    #[test]
363    fn argument_consumer_rest() {
364        let cmd = cmd!("cmd foo key=1 key=2 bar");
365
366        // rest() returns references to all arguments and consumes them.
367        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        // rest_pos() returns and consumes positional arguments.
372        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        // rest_key() returns and consumes key/value arguments.
378        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}