zebra_test/command/arguments.rs
1use indexmap::IndexMap;
2
3#[cfg(test)]
4mod tests;
5
6/// Helper type to keep track of arguments for spawning a process.
7///
8/// Stores the arguments in order, but is aware of key-value pairs to make overriding parameters
9/// more predictable.
10///
11/// # Notes
12///
13/// Repeated arguments are not supported.
14#[derive(Clone, Debug, Default, Eq, PartialEq)]
15pub struct Arguments(IndexMap<String, Option<String>>);
16
17impl Arguments {
18 /// Creates a new empty list of arguments.
19 pub fn new() -> Self {
20 Arguments(IndexMap::new())
21 }
22
23 /// Sets a lone `argument`.
24 ///
25 /// If the argument is already in the list, nothing happens.
26 ///
27 /// If there's a key-value pair where the key is identical to the `argument`, the value of the
28 /// pair is removed and the key maintains its position.
29 pub fn set_argument(&mut self, argument: impl Into<String>) {
30 self.0.insert(argument.into(), None);
31 }
32
33 /// Sets a key-value pair.
34 ///
35 /// If there is already a pair in the list with the same `parameter` key, the existing pair
36 /// keeps its position but the value is updated.
37 ///
38 /// If there is a lone argument that is identical to the `parameter` key, the value is inserted
39 /// after it.
40 pub fn set_parameter(&mut self, parameter: impl Into<String>, value: impl Into<String>) {
41 self.0.insert(parameter.into(), Some(value.into()));
42 }
43
44 /// Merges two argument lists.
45 ///
46 /// Existing pairs have their values updated if needed, and new pairs and arguments are
47 /// appended to the end of this list.
48 pub fn merge_with(&mut self, extra_arguments: Arguments) {
49 for (key, value) in extra_arguments.0 {
50 self.0.insert(key, value);
51 }
52 }
53
54 /// Converts this [`Arguments`] instance into a list of strings that can be passed when
55 /// spawning a process.
56 pub fn into_arguments(self) -> impl Iterator<Item = String> {
57 self.0
58 .into_iter()
59 .flat_map(|(key, value)| Some(key).into_iter().chain(value))
60 }
61}
62
63/// Helper macro to create a list of arguments in an [`Arguments`] instance.
64///
65/// Accepts items separated by commas, where an item is either:
66///
67/// - a lone argument obtained from an expression
68/// - a key-value pair in the form `"key": value_expression`
69///
70/// The items are added to a new [`Arguments`] instance in order.
71///
72/// # Implementation details
73///
74/// This macro is called recursively, and can run into a macro recursion limit if the list of items
75/// is very long.
76///
77/// The first step of the macro is to create a new scope with an `args` binding to a new empty
78/// `Arguments` list. That binding is returned from the scope, so the macro's generated code is an
79/// expression that creates the argument list. Once the scope is created, a `@started` tag is
80/// prefixed to the recursive calls, which then individually add each item.
81#[macro_export]
82macro_rules! args {
83 // Either an empty macro call or the end of items in a list.
84 ( @started $args:ident $(,)* ) => {};
85
86 // Handling the last (or sole) item in a list, if it's a key-value pair.
87 (
88 @started $args:ident
89 $parameter:tt : $argument:expr $(,)*
90 ) => {
91 $args.set_parameter($parameter, $argument);
92 };
93
94 // Handling the last (or sole) item in a list, if it's a lone argument.
95 (
96 @started $args:ident
97 $argument:expr $(,)*
98 ) => {
99 $args.set_argument($argument);
100 };
101
102 // Handling the next item in a list, if it's a key-value pair argument.
103 (
104 @started $args:ident
105 $parameter:tt : $argument:expr
106 , $( $remaining_arguments:tt )+
107 ) => {
108 $args.set_parameter($parameter, $argument);
109 args!(@started $args $( $remaining_arguments )*)
110 };
111
112 // Handling the next item in a list, if it's a lone argument.
113 (
114 @started $args:ident
115 $argument:expr
116 , $( $remaining_arguments:tt )*
117 ) => {
118 $args.set_argument($argument);
119 args!(@started $args $( $remaining_arguments )*)
120 };
121
122 // Start of the macro, create a scope to return an `args` binding, and populate it with a
123 // recursive call to this macro. A `@started` tag is used to indicate that the scope has been
124 // set up.
125 ( $( $arguments:tt )* ) => {
126 {
127 let mut args = $crate::command::Arguments::new();
128 args!(@started args $( $arguments )*);
129 args
130 }
131 };
132}