test_dsl/
macros.rs

1#[rustfmt::skip]
2macro_rules! all_the_tuples {
3    ($name:ident) => {
4        $name!([], T1);
5        $name!([T1], T2);
6        $name!([T1, T2], T3);
7        $name!([T1, T2, T3], T4);
8        $name!([T1, T2, T3, T4], T5);
9        $name!([T1, T2, T3, T4, T5], T6);
10        $name!([T1, T2, T3, T4, T5, T6], T7);
11        $name!([T1, T2, T3, T4, T5, T6, T7], T8);
12        $name!([T1, T2, T3, T4, T5, T6, T7, T8], T9);
13        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9], T10);
14        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T11);
15        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], T12);
16        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], T13);
17        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], T14);
18        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], T15);
19        $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], T16);
20    };
21}
22
23/// Define a new type that implements [`ParseArguments`](crate::argument::ParseArguments)
24///
25/// This can then be used in your custom [`Verb`](crate::Verb) or [`TestCondition`](crate::condition::TestCondition) implementations.
26///
27/// **Note:** The definition uses `=` instead of the usual `:` to delimit fields and their types.
28/// This is on purpose, as this may later be expanded to allow for positional arguments as well.
29///
30/// ```
31/// use test_dsl::named_parameters;
32///
33/// named_parameters! {
34///     Frobnicator {
35///         foo = usize,
36///         name = String
37///     }
38/// }
39/// ```
40#[macro_export]
41macro_rules! named_parameters {
42    ( $vis:vis $param_name:ident { $($key:ident = $value:ty),* $(,)? }) => {
43        #[derive(Debug, Clone)]
44        $vis struct $param_name {
45            $($key: $value),*
46        }
47
48        impl<H> $crate::argument::ParseArguments<H> for $param_name {
49            fn parse(_: &$crate::TestDsl<H>, node: &$crate::kdl::KdlNode) -> Result<Self, $crate::error::TestErrorCase> {
50                $(
51                    let $key: $value = $crate::argument::VerbArgument::from_value(node.entry(stringify!($key)).unwrap()).unwrap();
52                )*
53
54                Ok($param_name {
55                    $(
56                        $key
57                    ),*
58                })
59            }
60        }
61    };
62}
63
64#[macro_export]
65#[cfg(not(doc))]
66#[expect(missing_docs, reason = "This is documented further below")]
67macro_rules! named_parameters_verb {
68    (@define_args $struct_name:ident => { |$_name:ident : $_ty:ty $(, $name:ident : $kind:ty)* $(,)?| $rest:block }) => {
69        #[derive(Debug, Clone)]
70        struct $struct_name {
71            $($name : $kind),*
72        }
73    };
74
75    (@get_args $struct_name:ident => |$_name:ident : $_ty:ty $(, $name:ident : $kind:ty)* $(,)?| $rest:block) => {
76        $struct_name {
77            $($name),*
78        }
79    };
80
81    (@parse_args $node:ident => |$_name:ident : $_ty:ty $(, $name:ident : $kind:ty)* $(,)?| $rest:block) => {
82        $(
83            let $name: $kind = $crate::argument::VerbArgument::from_value($node.entry(stringify!($name)).unwrap()).unwrap();
84        )*
85    };
86
87    (@extract $node:ident => |$_name:ident : $_ty:ty $(, $name:ident : $kind:ty)* $(,)?| $rest:block) => {
88        $(
89            let $name: $kind = $node.$name.clone();
90        )*
91    };
92
93    (@verb_params $verb:ident $harness:ident => |$_name:ident : $_ty:ty $(, $name:ident : $kind:ty)* $(,)?| $rest:block) => {
94        $verb($harness, $($name),*)
95    };
96
97    (@call $struct_name:ident => { $($rest:tt)* } => { |$_name:ident : &mut $ty:ty $(,$_:ident : $__:ty)*| $_rest:block }) => {{
98        #[derive(Clone)]
99        struct __Caller;
100
101        impl $crate::verb::CallableVerb<$ty, $struct_name> for __Caller {
102            fn call(&self, harness: &mut $ty, node: &$struct_name) -> $crate::miette::Result<()> {
103
104                let verb = $($rest)*;
105
106                $crate::named_parameters_verb!(@extract node => $($rest)*);
107
108                $crate::named_parameters_verb!(@verb_params verb harness => $($rest)*)
109            }
110        }
111
112        __Caller
113    }};
114
115    ($($input:tt)*) => {{
116        let verb = $crate::verb::FunctionVerb::<_, __NamedVerb>::new(
117            $crate::named_parameters_verb!(@call __NamedVerb => { $($input)* } => { $($input)* })
118        );
119
120        $crate::named_parameters_verb!(@define_args __NamedVerb => { $($input)* });
121
122        impl<H> $crate::argument::ParseArguments<H> for __NamedVerb {
123            fn parse(_: &$crate::TestDsl<H>, node: &$crate::kdl::KdlNode) -> Result<Self, $crate::error::TestErrorCase> {
124
125                $crate::named_parameters_verb!(@parse_args node => $($input)*);
126
127                Ok($crate::named_parameters_verb!(@get_args __NamedVerb => $($input)*))
128            }
129        }
130
131        verb
132    }};
133}
134
135/// Define a verb using a closure, where the argument names are used as the key names
136///
137/// ```
138/// # use test_dsl::{TestDsl, named_parameters_verb};
139/// let mut dsl = TestDsl::<()>::new();
140///
141/// dsl.add_verb(
142///     "test",
143///     named_parameters_verb!(|_harness: &mut (), name: String, pi: usize| {
144///         println!("{name} = {pi}");
145///         Ok(())
146///     }),
147/// );
148/// ```
149#[cfg(doc)]
150#[macro_export]
151macro_rules! named_parameters_verb {
152    ($($input:tt)*) => {};
153}
154
155#[cfg(test)]
156mod tests {
157    use crate::TestDsl;
158    use crate::argument::ParseArguments;
159
160    #[test]
161    fn simple_kv() {
162        named_parameters!(CoolIntegers {
163            pi = usize,
164            name = String
165        });
166
167        let dsl = TestDsl::<()>::new();
168
169        let node = kdl::KdlNode::parse("foo pi=4 name=PI { other stuff }").unwrap();
170
171        let ints = CoolIntegers::parse(&dsl, &node).unwrap();
172
173        assert_eq!(ints.pi, 4);
174        assert_eq!(ints.name, "PI");
175    }
176
177    #[test]
178    fn simple_named_closure() {
179        let mut dsl = TestDsl::<()>::new();
180
181        dsl.add_verb(
182            "test",
183            named_parameters_verb!(|_harness: &mut (), name: String, pi: usize| {
184                println!("{name} = {pi}");
185                Ok(())
186            }),
187        );
188    }
189}