1use std::fmt::Display;
2use std::str::FromStr;
3
4pub trait Param {
7 type Output;
9
10 fn name(&self) -> &str;
12
13 fn default(&self) -> Option<Self::Output>;
16
17 fn extract(&self, raw: &str) -> Option<Self::Output>;
20
21 fn serialize(&self, value: Self::Output) -> Option<String>;
23}
24
25pub struct GenParam<'a, T> {
40 name: &'a str,
42
43 default: Option<T>,
45}
46
47impl<'a, T> GenParam<'a, T> {
48 pub fn new(name: &'a str) -> GenParam<'a, T> {
50 GenParam {
51 name,
52 default: None,
53 }
54 }
55
56 pub fn with_default(name: &'a str, default: T) -> GenParam<'a, T> {
58 GenParam {
59 name,
60 default: Some(default),
61 }
62 }
63}
64
65impl<'a, T> Param for GenParam<'a, T>
66where
67 T: Clone + Display + FromStr + PartialEq,
68{
69 type Output = T;
70
71 fn name(&self) -> &str {
72 &self.name
73 }
74
75 fn default(&self) -> Option<Self::Output> {
76 self.default.clone()
77 }
78
79 fn extract(&self, raw: &str) -> Option<Self::Output> {
80 raw.parse().ok()
81 }
82
83 fn serialize(&self, value: Self::Output) -> Option<String> {
84 match &self.default {
85 None => Some(value.to_string()),
86 Some(default) => if value != *default {
87 Some(value.to_string())
88 } else {
89 None
90 },
91 }
92 }
93}
94
95#[macro_export]
97macro_rules! param {
98 ($name:ident) => {
99 $crate::GenParam::new(stringify!($name))
100 };
101 ($name:ident: $t:ty) => {
102 $crate::GenParam::<$t>::new(stringify!($name))
103 };
104 ($name:ident = $def:expr) => {
105 $crate::GenParam::with_default(stringify!($name), $def)
106 };
107 ($name:ident: $t:ty = $def:expr) => {
108 $crate::GenParam::<$t>::with_default(stringify!($name), $def)
109 };
110}
111
112pub struct RawParamSlice<'a>(&'a [(&'a str, &'a str)]);
113
114impl<'a> RawParamSlice<'a> {
115 fn next_if_key(&mut self, key: &str) -> Option<&str> {
116 match self.0.split_first() {
117 Some(((ref k, ref v), rest)) if *k == key => {
118 self.0 = rest;
119 Some(v)
120 }
121 _ => None,
122 }
123 }
124
125 pub fn extract_single<P>(&mut self, param: &P) -> Option<P::Output>
126 where
127 P: Param,
128 {
129 match self.next_if_key(param.name()) {
130 None => param.default(),
131 Some(val) => param.extract(val),
132 }
133 }
134}
135
136pub trait ParamSet {
137 type Params;
138
139 fn extract(&self, raw: RawParamSlice) -> Option<Self::Params>;
141}
142
143pub(crate) fn extract_param_set<P>(param_set: &P, raw: &[(&str, &str)]) -> Option<P::Params>
144where
145 P: ParamSet,
146{
147 param_set.extract(RawParamSlice(raw))
148}
149
150impl<P> ParamSet for P
151where
152 P: Param,
153{
154 type Params = P::Output;
155
156 fn extract(&self, mut raw: RawParamSlice) -> Option<Self::Params> {
157 raw.extract_single(self)
158 }
159}
160
161macro_rules! tuple_param_set {
162 ($( $name:ident: $t:ident ),+ $(,)*) => {
163 impl<$( $t ),+> ParamSet for ($( $t,)+)
164 where $(
165 $t: Param,
166 )* {
167 type Params = ($( $t::Output, )+);
168
169 fn extract(&self, mut raw: RawParamSlice) -> Option<Self::Params> {
170 let ($( $name, )+) = self;
171 Some(($(raw.extract_single($name)?,)+))
172 }
173 }
174 }
175}
176
177tuple_param_set!(a: A);
178tuple_param_set!(a: A, b: B);
179tuple_param_set!(a: A, b: B, c: C);
180tuple_param_set!(a: A, b: B, c: C, d: D);
181tuple_param_set!(a: A, b: B, c: C, d: D, e: E);
182tuple_param_set!(a: A, b: B, c: C, d: D, e: E, f: F);
183tuple_param_set!(a: A, b: B, c: C, d: D, e: E, f: F, g: G);
184tuple_param_set!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H);
185
186#[macro_export]
187macro_rules! param_set {
188 (@($name:ident, $( $inp:tt )*) -> ($( $body:tt )*)) => {
189 param_set!(@($( $inp )*) -> ($( $body )* param!($name),))
190 };
191 (@($name:ident: $t:ty, $( $inp:tt )*) -> ($( $body:tt )*)) => {
192 param_set!(@($( $inp )*) -> ($( $body )* param!($name: $t),))
193 };
194 (@($name:ident = $def:expr, $( $inp:tt )*) -> ($( $body:tt )*)) => {
195 param_set!(@($( $inp )*) -> ($( $body )* param!($name = $def),))
196 };
197 (@($name:ident: $t:ty = $def:expr, $( $inp:tt )*) -> ($( $body:tt )*)) => {
198 param_set!(@($( $inp )*) -> ($( $body )* param!($name: $t = $def)))
199 };
200 (@($name:ident) -> ($( $body:tt )*)) => {
201 param_set!(@() -> ($( $body )* param!($name)))
202 };
203 (@($name:ident: $t:ty) -> ($( $body:tt )*)) => {
204 param_set!(@() -> ($( $body )* param!($name: $t)))
205 };
206 (@($name:ident = $def:expr) -> ($( $body:tt )*)) => {
207 param_set!(@() -> ($( $body )* param!($name = $def)))
208 };
209 (@($name:ident: $t:ty = $def:expr) -> ($( $body:tt )*)) => {
210 param_set!(@() -> ($( $body )* param!($name: $t = $def)))
211 };
212 (@() -> $body:expr) => {
213 $body
214 };
215 ($( $inp:tt )*) => {
216 param_set!(@($( $inp )*) -> ())
217 };
218}
219
220#[cfg(test)]
221mod tests {
222 use super::extract_param_set;
223
224 #[test]
225 fn param_set_variants() {
226 let params = param_set!(a: u8, b: u32, c = true, y: &str = "Hello!");
227 }
228
229 #[test]
230 fn two_param_set_two_parses() {
231 let params = param_set!(i: u32, mem = true);
232 assert_eq!(
233 extract_param_set(¶ms, &[("i", "10000")]),
234 Some((10000, true))
235 );
236 }
237}