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