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)]
20#[non_exhaustive]
21pub(crate) struct Block {
22 pub commands: Vec<Command>,
23
24 pub literal: String,
25
26 pub line_number: u32,
27}
28
29#[derive(Clone, PartialEq)]
30#[non_exhaustive]
31pub struct Command {
32 pub name: String,
33
34 pub args: Vec<Argument>,
35
36 pub prefix: Option<String>,
37
38 pub tags: HashSet<String>,
39
40 pub silent: bool,
41
42 pub fail: bool,
43
44 pub line_number: u32,
45}
46
47impl fmt::Debug for Command {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 f.debug_struct("Command")
50 .field("name", &self.name)
51 .field("args", &self.args)
52 .field("prefix", &self.prefix)
53 .field("tags", &BTreeSet::from_iter(&self.tags))
54 .field("silent", &self.silent)
55 .field("fail", &self.fail)
56 .field("line_number", &self.line_number)
57 .finish()
58 }
59}
60
61impl Command {
62 pub fn consume_args(&self) -> ArgumentConsumer<'_> {
63 ArgumentConsumer::new(&self.args)
64 }
65}
66
67#[derive(Clone, Debug, PartialEq)]
68#[non_exhaustive]
69pub struct Argument {
70 pub key: Option<String>,
71
72 pub value: String,
73}
74
75impl Argument {
76 pub fn name(&self) -> &str {
77 match self.key.as_deref() {
78 Some(key) => key,
79 None => &self.value,
80 }
81 }
82
83 pub fn parse<T>(&self) -> Result<T, Box<dyn Error>>
84 where
85 T: FromStr,
86 <T as FromStr>::Err: fmt::Display,
87 {
88 self.value.parse().map_err(|e| format!("invalid argument '{}': {e}", self.value).into())
89 }
90}
91
92pub struct ArgumentConsumer<'a> {
93 args: VecDeque<&'a Argument>,
94}
95
96impl<'a> Iterator for ArgumentConsumer<'a> {
97 type Item = &'a Argument;
98
99 fn next(&mut self) -> Option<Self::Item> {
100 self.args.pop_front()
101 }
102}
103
104impl<'a> ArgumentConsumer<'a> {
105 fn new(args: &'a [Argument]) -> Self {
106 Self {
107 args: VecDeque::from_iter(args.iter()),
108 }
109 }
110
111 pub fn lookup(&mut self, key: &str) -> Option<&'a Argument> {
112 let arg = self.args.iter().rev().find(|a| a.key.as_deref() == Some(key)).copied();
113 if arg.is_some() {
114 self.args.retain(|a| a.key.as_deref() != Some(key))
115 }
116 arg
117 }
118
119 pub fn lookup_parse<T>(&mut self, key: &str) -> Result<Option<T>, Box<dyn Error>>
120 where
121 T: FromStr,
122 <T as FromStr>::Err: fmt::Display,
123 {
124 let value = self
125 .args
126 .iter()
127 .rev()
128 .find(|a| a.key.as_deref() == Some(key))
129 .map(|a| a.parse())
130 .transpose()?;
131 if value.is_some() {
132 self.args.retain(|a| a.key.as_deref() != Some(key))
133 }
134 Ok(value)
135 }
136
137 pub fn next_key(&mut self) -> Option<&'a Argument> {
138 self.args.iter().position(|a| a.key.is_some()).map(|i| self.args.remove(i).unwrap())
139 }
140
141 pub fn next_pos(&mut self) -> Option<&'a Argument> {
142 self.args.iter().position(|a| a.key.is_none()).map(|i| self.args.remove(i).unwrap())
143 }
144
145 pub fn reject_rest(&self) -> Result<(), Box<dyn Error>> {
146 if let Some(arg) = self.args.front() {
147 return Err(format!("invalid argument '{}'", arg.name()).into());
148 }
149 Ok(())
150 }
151
152 pub fn rest(&mut self) -> Vec<&'a Argument> {
153 self.args.drain(..).collect()
154 }
155
156 pub fn rest_key(&mut self) -> Vec<&'a Argument> {
157 let keyed: Vec<_> = self.args.iter().filter(|a| a.key.is_some()).copied().collect();
158 if !keyed.is_empty() {
159 self.args.retain(|a| a.key.is_none());
160 }
161 keyed
162 }
163
164 pub fn rest_pos(&mut self) -> Vec<&'a Argument> {
165 let pos: Vec<_> = self.args.iter().filter(|a| a.key.is_none()).copied().collect();
166 if !pos.is_empty() {
167 self.args.retain(|a| a.key.is_some());
168 }
169 pos
170 }
171}
172
173#[cfg(test)]
174pub mod tests {
175 use super::*;
176
177 macro_rules! arg {
179 ($value:expr) => {
180 Argument {
181 key: None,
182 value: $value.to_string(),
183 }
184 };
185 ($key:expr => $value:expr) => {
186 Argument {
187 key: Some($key.to_string()),
188 value: $value.to_string(),
189 }
190 };
191 }
192
193 macro_rules! cmd {
195 ($input:expr) => {{ crate::testscript::parser::parse_command(&format!("{}\n", $input)).expect("invalid command") }};
196 }
197
198 #[test]
200 fn test_argument_name() {
201 assert_eq!(arg!("value").name(), "value");
202 assert_eq!(arg!("key" => "value").name(), "key");
203 }
204
205 #[test]
208 fn test_argument_parse() {
209 assert_eq!(arg!("-1").parse::<i64>().unwrap(), -1_i64);
210 assert_eq!(arg!("0").parse::<i64>().unwrap(), 0_i64);
211 assert_eq!(arg!("1").parse::<i64>().unwrap(), 1_i64);
212
213 assert_eq!(
214 arg!("").parse::<i64>().unwrap_err().to_string(),
215 "invalid argument '': cannot parse integer from empty string"
216 );
217 assert_eq!(
218 arg!("foo").parse::<i64>().unwrap_err().to_string(),
219 "invalid argument 'foo': invalid digit found in string"
220 );
221
222 assert!(!arg!("false").parse::<bool>().unwrap());
223 assert!(arg!("true").parse::<bool>().unwrap());
224
225 assert_eq!(
226 arg!("").parse::<bool>().unwrap_err().to_string(),
227 "invalid argument '': provided string was not `true` or `false`"
228 );
229 }
230
231 #[test]
233 fn test_command_consume_args() {
234 let cmd = cmd!("cmd foo key=value bar");
235 assert_eq!(cmd.consume_args().rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[2]]);
236 }
237
238 #[test]
240 fn test_argument_consumer_lookup() {
241 let cmd = cmd!("cmd value key=value foo=bar key=other");
242
243 let mut args = cmd.consume_args();
246 assert_eq!(args.lookup("unknown"), None);
247 assert_eq!(args.lookup("value"), None);
248 assert_eq!(args.rest().len(), 4);
249
250 let mut args = cmd.consume_args();
252 assert_eq!(args.lookup("key"), Some(&cmd.args[3]));
253 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[2]]);
254
255 let mut args = cmd.consume_args();
257 assert_eq!(args.lookup("foo"), Some(&cmd.args[2]));
258 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[3]]);
259 }
260
261 #[test]
263 fn test_argument_consumer_lookup_parse() {
264 let cmd = cmd!("cmd value key=1 foo=bar key=2");
265
266 let mut args = cmd.consume_args();
269 assert_eq!(args.lookup_parse::<String>("unknown").unwrap(), None);
270 assert_eq!(args.lookup_parse::<String>("value").unwrap(), None);
271 assert_eq!(args.rest().len(), 4);
272
273 let mut args = cmd.consume_args();
276 assert_eq!(args.lookup_parse("key").unwrap(), Some(2));
277 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[2]]);
278
279 let mut args = cmd.consume_args();
282 assert_eq!(args.lookup_parse("foo").unwrap(), Some("bar".to_string()));
283 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[3]]);
284
285 let mut args = cmd.consume_args();
288 assert!(args.lookup_parse::<bool>("key").is_err());
289 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[2], &cmd.args[3]]);
290 }
291
292 #[test]
294 fn test_argument_consumer_next() {
295 let cmd = cmd!("cmd foo key=1 key=2 bar");
296
297 let mut args = cmd.consume_args();
299 assert_eq!(args.next(), Some(&cmd.args[0]));
300 assert_eq!(args.next(), Some(&cmd.args[1]));
301 assert_eq!(args.next(), Some(&cmd.args[2]));
302 assert_eq!(args.next(), Some(&cmd.args[3]));
303 assert_eq!(args.next(), None);
304 assert!(args.rest().is_empty());
305
306 let mut args = cmd.consume_args();
309 assert_eq!(args.next_key(), Some(&cmd.args[1]));
310 assert_eq!(args.next_key(), Some(&cmd.args[2]));
311 assert_eq!(args.next_key(), None);
312 assert_eq!(args.next(), Some(&cmd.args[0]));
313 assert_eq!(args.next(), Some(&cmd.args[3]));
314 assert_eq!(args.next(), None);
315 assert!(args.rest().is_empty());
316
317 let mut args = cmd.consume_args();
320 assert_eq!(args.next_pos(), Some(&cmd.args[0]));
321 assert_eq!(args.next_pos(), Some(&cmd.args[3]));
322 assert_eq!(args.next_pos(), None);
323 assert_eq!(args.next(), Some(&cmd.args[1]));
324 assert_eq!(args.next(), Some(&cmd.args[2]));
325 assert_eq!(args.next(), None);
326 assert!(args.rest().is_empty());
327 }
328
329 #[test]
331 fn test_argument_consumer_reject_rest() {
332 let cmd = cmd!("cmd");
334 assert!(cmd.consume_args().reject_rest().is_ok());
335
336 let cmd = cmd!("cmd value");
338 let mut args = cmd.consume_args();
339 assert_eq!(args.reject_rest().unwrap_err().to_string(), "invalid argument 'value'");
340 assert!(!args.rest().is_empty());
341
342 let cmd = cmd!("cmd key=value");
344 let mut args = cmd.consume_args();
345 assert_eq!(args.reject_rest().unwrap_err().to_string(), "invalid argument 'key'");
346 assert!(!args.rest().is_empty());
347 }
348
349 #[test]
351 fn test_argument_consumer_rest() {
352 let cmd = cmd!("cmd foo key=1 key=2 bar");
353
354 let mut args = cmd.consume_args();
356 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[1], &cmd.args[2], &cmd.args[3]]);
357 assert!(args.rest().is_empty());
358
359 let mut args = cmd.consume_args();
361 assert_eq!(args.rest_pos(), vec![&cmd.args[0], &cmd.args[3]]);
362 assert!(args.rest_pos().is_empty());
363 assert_eq!(args.rest(), vec![&cmd.args[1], &cmd.args[2]]);
364
365 let mut args = cmd.consume_args();
367 assert_eq!(args.rest_key(), vec![&cmd.args[1], &cmd.args[2]]);
368 assert!(args.rest_key().is_empty());
369 assert_eq!(args.rest(), vec![&cmd.args[0], &cmd.args[3]]);
370 }
371}