reifydb_testing/testscript/
command.rs1use std::{
13 collections::{BTreeSet, HashSet, VecDeque},
14 error::Error,
15 fmt,
16 str::FromStr,
17};
18
19#[derive(Clone, Debug, PartialEq)]
21#[non_exhaustive]
22pub(crate) struct Block {
23 pub commands: Vec<Command>,
25 pub literal: String,
28 pub line_number: u32,
30}
31
32#[derive(Clone, PartialEq)]
34#[non_exhaustive]
35pub struct Command {
36 pub name: String,
38 pub args: Vec<Argument>,
40 pub prefix: Option<String>,
42 pub tags: HashSet<String>,
44 pub silent: bool,
48 pub fail: bool,
51 pub line_number: u32,
53}
54
55impl fmt::Debug for Command {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 f.debug_struct("Command")
58 .field("name", &self.name)
59 .field("args", &self.args)
60 .field("prefix", &self.prefix)
61 .field("tags", &BTreeSet::from_iter(&self.tags))
63 .field("silent", &self.silent)
64 .field("fail", &self.fail)
65 .field("line_number", &self.line_number)
66 .finish()
67 }
68}
69
70impl Command {
71 pub fn consume_args(&self) -> ArgumentConsumer<'_> {
77 ArgumentConsumer::new(&self.args)
78 }
79}
80
81#[derive(Clone, Debug, PartialEq)]
83#[non_exhaustive]
84pub struct Argument {
85 pub key: Option<String>,
89 pub value: String,
91}
92
93impl Argument {
94 pub fn name(&self) -> &str {
97 match self.key.as_deref() {
98 Some(key) => key,
99 None => &self.value,
100 }
101 }
102
103 pub fn parse<T>(&self) -> Result<T, Box<dyn Error>>
107 where
108 T: FromStr,
109 <T as FromStr>::Err: fmt::Display,
110 {
111 self.value.parse().map_err(|e| format!("invalid argument '{}': {e}", self.value).into())
112 }
113}
114
115pub struct ArgumentConsumer<'a> {
121 args: VecDeque<&'a Argument>,
122}
123
124impl<'a> Iterator for ArgumentConsumer<'a> {
125 type Item = &'a Argument;
126
127 fn next(&mut self) -> Option<Self::Item> {
129 self.args.pop_front()
130 }
131}
132
133impl<'a> ArgumentConsumer<'a> {
134 fn new(args: &'a [Argument]) -> Self {
136 Self {
137 args: VecDeque::from_iter(args.iter()),
138 }
139 }
140
141 pub fn lookup(&mut self, key: &str) -> Option<&'a Argument> {
145 let arg = self.args.iter().rev().find(|a| a.key.as_deref() == Some(key)).copied();
146 if arg.is_some() {
147 self.args.retain(|a| a.key.as_deref() != Some(key))
148 }
149 arg
150 }
151
152 pub fn lookup_parse<T>(&mut self, key: &str) -> Result<Option<T>, Box<dyn Error>>
155 where
156 T: FromStr,
157 <T as FromStr>::Err: fmt::Display,
158 {
159 let value = self
160 .args
161 .iter()
162 .rev()
163 .find(|a| a.key.as_deref() == Some(key))
164 .map(|a| a.parse())
165 .transpose()?;
166 if value.is_some() {
167 self.args.retain(|a| a.key.as_deref() != Some(key))
168 }
169 Ok(value)
170 }
171
172 pub fn next_key(&mut self) -> Option<&'a Argument> {
174 self.args.iter().position(|a| a.key.is_some()).map(|i| self.args.remove(i).unwrap())
175 }
176
177 pub fn next_pos(&mut self) -> Option<&'a Argument> {
179 self.args.iter().position(|a| a.key.is_none()).map(|i| self.args.remove(i).unwrap())
180 }
181
182 pub fn reject_rest(&self) -> Result<(), Box<dyn Error>> {
184 if let Some(arg) = self.args.front() {
185 return Err(format!("invalid argument '{}'", arg.name()).into());
186 }
187 Ok(())
188 }
189
190 pub fn rest(&mut self) -> Vec<&'a Argument> {
192 self.args.drain(..).collect()
193 }
194
195 pub fn rest_key(&mut self) -> Vec<&'a Argument> {
197 let keyed: Vec<_> = self.args.iter().filter(|a| a.key.is_some()).copied().collect();
198 if !keyed.is_empty() {
199 self.args.retain(|a| a.key.is_none());
200 }
201 keyed
202 }
203
204 pub fn rest_pos(&mut self) -> Vec<&'a Argument> {
206 let pos: Vec<_> = self.args.iter().filter(|a| a.key.is_none()).copied().collect();
207 if !pos.is_empty() {
208 self.args.retain(|a| a.key.is_some());
209 }
210 pos
211 }
212}
213
214#[cfg(test)]
215pub mod tests {
216 use super::*;
217
218 macro_rules! arg {
220 ($value:expr) => {
221 Argument {
222 key: None,
223 value: $value.to_string(),
224 }
225 };
226 ($key:expr => $value:expr) => {
227 Argument {
228 key: Some($key.to_string()),
229 value: $value.to_string(),
230 }
231 };
232 }
233
234 macro_rules! cmd {
236 ($input:expr) => {{ crate::testscript::parser::parse_command(&format!("{}\n", $input)).expect("invalid command") }};
237 }
238
239 #[test]
241 fn test_argument_name() {
242 assert_eq!(arg!("value").name(), "value");
243 assert_eq!(arg!("key" => "value").name(), "key");
244 }
245
246 #[test]
249 fn test_argument_parse() {
250 assert_eq!(arg!("-1").parse::<i64>().unwrap(), -1_i64);
251 assert_eq!(arg!("0").parse::<i64>().unwrap(), 0_i64);
252 assert_eq!(arg!("1").parse::<i64>().unwrap(), 1_i64);
253
254 assert_eq!(
255 arg!("").parse::<i64>().unwrap_err().to_string(),
256 "invalid argument '': cannot parse integer from empty string"
257 );
258 assert_eq!(
259 arg!("foo").parse::<i64>().unwrap_err().to_string(),
260 "invalid argument 'foo': invalid digit found in string"
261 );
262
263 assert!(!arg!("false").parse::<bool>().unwrap());
264 assert!(arg!("true").parse::<bool>().unwrap());
265
266 assert_eq!(
267 arg!("").parse::<bool>().unwrap_err().to_string(),
268 "invalid argument '': provided string was not `true` or `false`"
269 );
270 }
271
272 #[test]
274 fn test_command_consume_args() {
275 let cmd = cmd!("cmd foo key=value bar");
276 assert_eq!(cmd.consume_args().rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[2]]);
277 }
278
279 #[test]
281 fn test_argument_consumer_lookup() {
282 let cmd = cmd!("cmd value key=value foo=bar key=other");
283
284 let mut args = cmd.consume_args();
287 assert_eq!(args.lookup("unknown"), None);
288 assert_eq!(args.lookup("value"), None);
289 assert_eq!(args.rest().len(), 4);
290
291 let mut args = cmd.consume_args();
293 assert_eq!(args.lookup("key"), Some(&cmd.args[3]));
294 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[2]]);
295
296 let mut args = cmd.consume_args();
298 assert_eq!(args.lookup("foo"), Some(&cmd.args[2]));
299 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[3]]);
300 }
301
302 #[test]
304 fn test_argument_consumer_lookup_parse() {
305 let cmd = cmd!("cmd value key=1 foo=bar key=2");
306
307 let mut args = cmd.consume_args();
310 assert_eq!(args.lookup_parse::<String>("unknown").unwrap(), None);
311 assert_eq!(args.lookup_parse::<String>("value").unwrap(), None);
312 assert_eq!(args.rest().len(), 4);
313
314 let mut args = cmd.consume_args();
317 assert_eq!(args.lookup_parse("key").unwrap(), Some(2));
318 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[2]]);
319
320 let mut args = cmd.consume_args();
323 assert_eq!(args.lookup_parse("foo").unwrap(), Some("bar".to_string()));
324 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[3]]);
325
326 let mut args = cmd.consume_args();
329 assert!(args.lookup_parse::<bool>("key").is_err());
330 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[2], &cmd.args[3]]);
331 }
332
333 #[test]
335 fn test_argument_consumer_next() {
336 let cmd = cmd!("cmd foo key=1 key=2 bar");
337
338 let mut args = cmd.consume_args();
340 assert_eq!(args.next(), Some(&cmd.args[0]));
341 assert_eq!(args.next(), Some(&cmd.args[1]));
342 assert_eq!(args.next(), Some(&cmd.args[2]));
343 assert_eq!(args.next(), Some(&cmd.args[3]));
344 assert_eq!(args.next(), None);
345 assert!(args.rest().is_empty());
346
347 let mut args = cmd.consume_args();
350 assert_eq!(args.next_key(), Some(&cmd.args[1]));
351 assert_eq!(args.next_key(), Some(&cmd.args[2]));
352 assert_eq!(args.next_key(), None);
353 assert_eq!(args.next(), Some(&cmd.args[0]));
354 assert_eq!(args.next(), Some(&cmd.args[3]));
355 assert_eq!(args.next(), None);
356 assert!(args.rest().is_empty());
357
358 let mut args = cmd.consume_args();
361 assert_eq!(args.next_pos(), Some(&cmd.args[0]));
362 assert_eq!(args.next_pos(), Some(&cmd.args[3]));
363 assert_eq!(args.next_pos(), None);
364 assert_eq!(args.next(), Some(&cmd.args[1]));
365 assert_eq!(args.next(), Some(&cmd.args[2]));
366 assert_eq!(args.next(), None);
367 assert!(args.rest().is_empty());
368 }
369
370 #[test]
372 fn test_argument_consumer_reject_rest() {
373 let cmd = cmd!("cmd");
375 assert!(cmd.consume_args().reject_rest().is_ok());
376
377 let cmd = cmd!("cmd value");
379 let mut args = cmd.consume_args();
380 assert_eq!(args.reject_rest().unwrap_err().to_string(), "invalid argument 'value'");
381 assert!(!args.rest().is_empty());
382
383 let cmd = cmd!("cmd key=value");
385 let mut args = cmd.consume_args();
386 assert_eq!(args.reject_rest().unwrap_err().to_string(), "invalid argument 'key'");
387 assert!(!args.rest().is_empty());
388 }
389
390 #[test]
392 fn test_argument_consumer_rest() {
393 let cmd = cmd!("cmd foo key=1 key=2 bar");
394
395 let mut args = cmd.consume_args();
397 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[2], &cmd.args[3]]);
398 assert!(args.rest().is_empty());
399
400 let mut args = cmd.consume_args();
402 assert_eq!(args.rest_pos(), vec![&cmd.args[0], &cmd.args[3]]);
403 assert!(args.rest_pos().is_empty());
404 assert_eq!(args.rest(), vec![&cmd.args[1], &cmd.args[2]]);
405
406 let mut args = cmd.consume_args();
408 assert_eq!(args.rest_key(), vec![&cmd.args[1], &cmd.args[2]]);
409 assert!(args.rest_key().is_empty());
410 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[3]]);
411 }
412}