reifydb_testing/testscript/
command.rs1use std::{
13 collections::{BTreeSet, HashSet, VecDeque},
14 error::Error,
15};
16
17#[derive(Clone, Debug, PartialEq)]
19#[non_exhaustive]
20pub(crate) struct Block {
21 pub commands: Vec<Command>,
23 pub literal: String,
26 pub line_number: u32,
28}
29
30#[derive(Clone, PartialEq)]
32#[non_exhaustive]
33pub struct Command {
34 pub name: String,
36 pub args: Vec<Argument>,
38 pub prefix: Option<String>,
40 pub tags: HashSet<String>,
42 pub silent: bool,
46 pub fail: bool,
49 pub line_number: u32,
51}
52
53impl std::fmt::Debug for Command {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 f.debug_struct("Command")
56 .field("name", &self.name)
57 .field("args", &self.args)
58 .field("prefix", &self.prefix)
59 .field("tags", &BTreeSet::from_iter(&self.tags))
61 .field("silent", &self.silent)
62 .field("fail", &self.fail)
63 .field("line_number", &self.line_number)
64 .finish()
65 }
66}
67
68impl Command {
69 pub fn consume_args(&self) -> ArgumentConsumer<'_> {
75 ArgumentConsumer::new(&self.args)
76 }
77}
78
79#[derive(Clone, Debug, PartialEq)]
81#[non_exhaustive]
82pub struct Argument {
83 pub key: Option<String>,
87 pub value: String,
89}
90
91impl Argument {
92 pub fn name(&self) -> &str {
95 match self.key.as_deref() {
96 Some(key) => key,
97 None => &self.value,
98 }
99 }
100
101 pub fn parse<T>(&self) -> Result<T, Box<dyn Error>>
105 where
106 T: std::str::FromStr,
107 <T as std::str::FromStr>::Err: std::fmt::Display,
108 {
109 self.value.parse().map_err(|e| format!("invalid argument '{}': {e}", self.value).into())
110 }
111}
112
113pub struct ArgumentConsumer<'a> {
119 args: VecDeque<&'a Argument>,
120}
121
122impl<'a> Iterator for ArgumentConsumer<'a> {
123 type Item = &'a Argument;
124
125 fn next(&mut self) -> Option<Self::Item> {
127 self.args.pop_front()
128 }
129}
130
131impl<'a> ArgumentConsumer<'a> {
132 fn new(args: &'a [Argument]) -> Self {
134 Self {
135 args: VecDeque::from_iter(args.iter()),
136 }
137 }
138
139 pub fn lookup(&mut self, key: &str) -> Option<&'a Argument> {
143 let arg = self.args.iter().rev().find(|a| a.key.as_deref() == Some(key)).copied();
144 if arg.is_some() {
145 self.args.retain(|a| a.key.as_deref() != Some(key))
146 }
147 arg
148 }
149
150 pub fn lookup_parse<T>(&mut self, key: &str) -> Result<Option<T>, Box<dyn Error>>
153 where
154 T: std::str::FromStr,
155 <T as std::str::FromStr>::Err: std::fmt::Display,
156 {
157 let value = self
158 .args
159 .iter()
160 .rev()
161 .find(|a| a.key.as_deref() == Some(key))
162 .map(|a| a.parse())
163 .transpose()?;
164 if value.is_some() {
165 self.args.retain(|a| a.key.as_deref() != Some(key))
166 }
167 Ok(value)
168 }
169
170 pub fn next_key(&mut self) -> Option<&'a Argument> {
172 self.args.iter().position(|a| a.key.is_some()).map(|i| self.args.remove(i).unwrap())
173 }
174
175 pub fn next_pos(&mut self) -> Option<&'a Argument> {
177 self.args.iter().position(|a| a.key.is_none()).map(|i| self.args.remove(i).unwrap())
178 }
179
180 pub fn reject_rest(&self) -> Result<(), Box<dyn Error>> {
182 if let Some(arg) = self.args.front() {
183 return Err(format!("invalid argument '{}'", arg.name()).into());
184 }
185 Ok(())
186 }
187
188 pub fn rest(&mut self) -> Vec<&'a Argument> {
190 self.args.drain(..).collect()
191 }
192
193 pub fn rest_key(&mut self) -> Vec<&'a Argument> {
195 let keyed: Vec<_> = self.args.iter().filter(|a| a.key.is_some()).copied().collect();
196 if !keyed.is_empty() {
197 self.args.retain(|a| a.key.is_none());
198 }
199 keyed
200 }
201
202 pub fn rest_pos(&mut self) -> Vec<&'a Argument> {
204 let pos: Vec<_> = self.args.iter().filter(|a| a.key.is_none()).copied().collect();
205 if !pos.is_empty() {
206 self.args.retain(|a| a.key.is_some());
207 }
208 pos
209 }
210}
211
212#[cfg(test)]
213pub mod tests {
214 use super::*;
215
216 macro_rules! arg {
218 ($value:expr) => {
219 Argument {
220 key: None,
221 value: $value.to_string(),
222 }
223 };
224 ($key:expr => $value:expr) => {
225 Argument {
226 key: Some($key.to_string()),
227 value: $value.to_string(),
228 }
229 };
230 }
231
232 macro_rules! cmd {
234 ($input:expr) => {{ crate::testscript::parser::parse_command(&format!("{}\n", $input)).expect("invalid command") }};
235 }
236
237 #[test]
239 fn test_argument_name() {
240 assert_eq!(arg!("value").name(), "value");
241 assert_eq!(arg!("key" => "value").name(), "key");
242 }
243
244 #[test]
247 fn test_argument_parse() {
248 assert_eq!(arg!("-1").parse::<i64>().unwrap(), -1_i64);
249 assert_eq!(arg!("0").parse::<i64>().unwrap(), 0_i64);
250 assert_eq!(arg!("1").parse::<i64>().unwrap(), 1_i64);
251
252 assert_eq!(
253 arg!("").parse::<i64>().unwrap_err().to_string(),
254 "invalid argument '': cannot parse integer from empty string"
255 );
256 assert_eq!(
257 arg!("foo").parse::<i64>().unwrap_err().to_string(),
258 "invalid argument 'foo': invalid digit found in string"
259 );
260
261 assert!(!arg!("false").parse::<bool>().unwrap());
262 assert!(arg!("true").parse::<bool>().unwrap());
263
264 assert_eq!(
265 arg!("").parse::<bool>().unwrap_err().to_string(),
266 "invalid argument '': provided string was not `true` or `false`"
267 );
268 }
269
270 #[test]
272 fn test_command_consume_args() {
273 let cmd = cmd!("cmd foo key=value bar");
274 assert_eq!(cmd.consume_args().rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[2]]);
275 }
276
277 #[test]
279 fn test_argument_consumer_lookup() {
280 let cmd = cmd!("cmd value key=value foo=bar key=other");
281
282 let mut args = cmd.consume_args();
285 assert_eq!(args.lookup("unknown"), None);
286 assert_eq!(args.lookup("value"), None);
287 assert_eq!(args.rest().len(), 4);
288
289 let mut args = cmd.consume_args();
291 assert_eq!(args.lookup("key"), Some(&cmd.args[3]));
292 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[2]]);
293
294 let mut args = cmd.consume_args();
296 assert_eq!(args.lookup("foo"), Some(&cmd.args[2]));
297 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[3]]);
298 }
299
300 #[test]
302 fn test_argument_consumer_lookup_parse() {
303 let cmd = cmd!("cmd value key=1 foo=bar key=2");
304
305 let mut args = cmd.consume_args();
308 assert_eq!(args.lookup_parse::<String>("unknown").unwrap(), None);
309 assert_eq!(args.lookup_parse::<String>("value").unwrap(), None);
310 assert_eq!(args.rest().len(), 4);
311
312 let mut args = cmd.consume_args();
315 assert_eq!(args.lookup_parse("key").unwrap(), Some(2));
316 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[2]]);
317
318 let mut args = cmd.consume_args();
321 assert_eq!(args.lookup_parse("foo").unwrap(), Some("bar".to_string()));
322 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[3]]);
323
324 let mut args = cmd.consume_args();
327 assert!(args.lookup_parse::<bool>("key").is_err());
328 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[2], &cmd.args[3]]);
329 }
330
331 #[test]
333 fn test_argument_consumer_next() {
334 let cmd = cmd!("cmd foo key=1 key=2 bar");
335
336 let mut args = cmd.consume_args();
338 assert_eq!(args.next(), Some(&cmd.args[0]));
339 assert_eq!(args.next(), Some(&cmd.args[1]));
340 assert_eq!(args.next(), Some(&cmd.args[2]));
341 assert_eq!(args.next(), Some(&cmd.args[3]));
342 assert_eq!(args.next(), None);
343 assert!(args.rest().is_empty());
344
345 let mut args = cmd.consume_args();
348 assert_eq!(args.next_key(), Some(&cmd.args[1]));
349 assert_eq!(args.next_key(), Some(&cmd.args[2]));
350 assert_eq!(args.next_key(), None);
351 assert_eq!(args.next(), Some(&cmd.args[0]));
352 assert_eq!(args.next(), Some(&cmd.args[3]));
353 assert_eq!(args.next(), None);
354 assert!(args.rest().is_empty());
355
356 let mut args = cmd.consume_args();
359 assert_eq!(args.next_pos(), Some(&cmd.args[0]));
360 assert_eq!(args.next_pos(), Some(&cmd.args[3]));
361 assert_eq!(args.next_pos(), None);
362 assert_eq!(args.next(), Some(&cmd.args[1]));
363 assert_eq!(args.next(), Some(&cmd.args[2]));
364 assert_eq!(args.next(), None);
365 assert!(args.rest().is_empty());
366 }
367
368 #[test]
370 fn test_argument_consumer_reject_rest() {
371 let cmd = cmd!("cmd");
373 assert!(cmd.consume_args().reject_rest().is_ok());
374
375 let cmd = cmd!("cmd value");
377 let mut args = cmd.consume_args();
378 assert_eq!(args.reject_rest().unwrap_err().to_string(), "invalid argument 'value'");
379 assert!(!args.rest().is_empty());
380
381 let cmd = cmd!("cmd key=value");
383 let mut args = cmd.consume_args();
384 assert_eq!(args.reject_rest().unwrap_err().to_string(), "invalid argument 'key'");
385 assert!(!args.rest().is_empty());
386 }
387
388 #[test]
390 fn test_argument_consumer_rest() {
391 let cmd = cmd!("cmd foo key=1 key=2 bar");
392
393 let mut args = cmd.consume_args();
395 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[2], &cmd.args[3]]);
396 assert!(args.rest().is_empty());
397
398 let mut args = cmd.consume_args();
400 assert_eq!(args.rest_pos(), vec![&cmd.args[0], &cmd.args[3]]);
401 assert!(args.rest_pos().is_empty());
402 assert_eq!(args.rest(), vec![&cmd.args[1], &cmd.args[2]]);
403
404 let mut args = cmd.consume_args();
406 assert_eq!(args.rest_key(), vec![&cmd.args[1], &cmd.args[2]]);
407 assert!(args.rest_key().is_empty());
408 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[3]]);
409 }
410}