1use crate::format::functions::call_function;
2use crate::format::parser::Token;
3
4pub trait MetadataProvider {
5 fn get_field(&self, name: &str) -> Option<String>;
6}
7
8impl<F: Fn(&str) -> Option<String>> MetadataProvider for F {
9 fn get_field(&self, name: &str) -> Option<String> {
10 self(name)
11 }
12}
13
14pub fn evaluate(tokens: &[Token], provider: &dyn MetadataProvider) -> String {
16 let (result, _) = eval_inner(tokens, provider);
17 result
18}
19
20fn eval_inner(tokens: &[Token], provider: &dyn MetadataProvider) -> (String, bool) {
22 let mut output = String::new();
23 let mut all_resolved = true;
24
25 for token in tokens {
26 match token {
27 Token::Literal(s) => output.push_str(s),
28 Token::Field(name) => match provider.get_field(name) {
29 Some(val) => output.push_str(&val),
30 None => {
31 all_resolved = false;
32 }
33 },
34 Token::Conditional(inner) => {
35 let (inner_output, inner_resolved) = eval_inner(inner, provider);
36 if inner_resolved {
37 output.push_str(&inner_output);
38 }
39 }
41 Token::Function { name, args } => {
42 let mut eval_args = Vec::new();
43 let mut func_resolved = true;
44 for arg_tokens in args {
45 let (arg_val, arg_ok) = eval_inner(arg_tokens, provider);
46 if !arg_ok {
47 func_resolved = false;
48 }
49 eval_args.push(arg_val);
50 }
51 if !func_resolved {
52 all_resolved = false;
53 }
54 if let Some(result) = call_function(name, &eval_args) {
55 output.push_str(&result);
56 }
57 }
58 }
59 }
60
61 (output, all_resolved)
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use std::collections::HashMap;
68
69 fn make_provider(data: &[(&str, &str)]) -> HashMap<String, String> {
70 data.iter()
71 .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
72 .collect()
73 }
74
75 impl MetadataProvider for HashMap<String, String> {
76 fn get_field(&self, name: &str) -> Option<String> {
77 self.get(name).cloned()
78 }
79 }
80
81 #[test]
82 fn basic_field() {
83 let provider = make_provider(&[("title", "Song")]);
84 let tokens = vec![Token::Field("title".into())];
85 assert_eq!(evaluate(&tokens, &provider), "Song");
86 }
87
88 #[test]
89 fn missing_field() {
90 let provider = make_provider(&[]);
91 let tokens = vec![Token::Field("title".into())];
92 assert_eq!(evaluate(&tokens, &provider), "");
93 }
94
95 #[test]
96 fn conditional_present() {
97 let provider = make_provider(&[("artist", "Radiohead")]);
98 let tokens = vec![
99 Token::Conditional(vec![
100 Token::Field("artist".into()),
101 Token::Literal(" - ".into()),
102 ]),
103 Token::Literal("OK Computer".into()),
104 ];
105 assert_eq!(evaluate(&tokens, &provider), "Radiohead - OK Computer");
106 }
107
108 #[test]
109 fn conditional_missing() {
110 let provider = make_provider(&[]);
111 let tokens = vec![
112 Token::Conditional(vec![
113 Token::Field("artist".into()),
114 Token::Literal(" - ".into()),
115 ]),
116 Token::Literal("OK Computer".into()),
117 ];
118 assert_eq!(evaluate(&tokens, &provider), "OK Computer");
119 }
120
121 #[test]
122 fn nested_conditional() {
123 let provider = make_provider(&[("artist", "Radiohead"), ("date", "1997")]);
124 let tokens = vec![Token::Conditional(vec![
125 Token::Field("artist".into()),
126 Token::Conditional(vec![
127 Token::Literal(" (".into()),
128 Token::Field("date".into()),
129 Token::Literal(")".into()),
130 ]),
131 ])];
132 assert_eq!(evaluate(&tokens, &provider), "Radiohead (1997)");
133 }
134
135 #[test]
136 fn nested_conditional_inner_missing() {
137 let provider = make_provider(&[("artist", "Radiohead")]);
138 let tokens = vec![Token::Conditional(vec![
139 Token::Field("artist".into()),
140 Token::Conditional(vec![
141 Token::Literal(" (".into()),
142 Token::Field("date".into()),
143 Token::Literal(")".into()),
144 ]),
145 ])];
146 assert_eq!(evaluate(&tokens, &provider), "Radiohead");
148 }
149
150 #[test]
151 fn function_in_conditional() {
152 let provider = make_provider(&[("genre", "Rock")]);
153 let tokens = vec![Token::Conditional(vec![Token::Function {
154 name: "if".into(),
155 args: vec![
156 vec![Token::Field("genre".into())],
157 vec![Token::Field("genre".into())],
158 vec![Token::Literal("Unknown".into())],
159 ],
160 }])];
161 assert_eq!(evaluate(&tokens, &provider), "Rock");
162 }
163
164 #[test]
165 fn function_in_conditional_field_missing() {
166 let provider = make_provider(&[]);
167 let tokens = vec![Token::Conditional(vec![Token::Function {
168 name: "if".into(),
169 args: vec![
170 vec![Token::Field("genre".into())],
171 vec![Token::Field("genre".into())],
172 vec![Token::Literal("Unknown".into())],
173 ],
174 }])];
175 assert_eq!(evaluate(&tokens, &provider), "");
177 }
178
179 #[test]
180 fn literal_passthrough() {
181 let provider = make_provider(&[]);
182 let tokens = vec![Token::Literal("hello".into())];
183 assert_eq!(evaluate(&tokens, &provider), "hello");
184 }
185
186 #[test]
187 fn multiple_fields() {
188 let provider = make_provider(&[("artist", "Radiohead"), ("title", "Creep")]);
189 let tokens = vec![
190 Token::Field("artist".into()),
191 Token::Literal(" - ".into()),
192 Token::Field("title".into()),
193 ];
194 assert_eq!(evaluate(&tokens, &provider), "Radiohead - Creep");
195 }
196
197 #[test]
198 fn missing_field_produces_empty() {
199 let provider = make_provider(&[("title", "Creep")]);
200 let tokens = vec![
201 Token::Field("artist".into()),
202 Token::Literal(" - ".into()),
203 Token::Field("title".into()),
204 ];
205 assert_eq!(evaluate(&tokens, &provider), " - Creep");
207 }
208
209 #[test]
210 fn conditional_does_not_poison_parent() {
211 let provider = make_provider(&[("title", "Creep")]);
212 let tokens = vec![
213 Token::Conditional(vec![
214 Token::Field("artist".into()),
215 Token::Literal(" - ".into()),
216 ]),
217 Token::Field("title".into()),
218 ];
219 assert_eq!(evaluate(&tokens, &provider), "Creep");
221 }
222
223 #[test]
224 fn multiple_failed_conditionals() {
225 let provider = make_provider(&[("title", "Creep")]);
226 let tokens = vec![
227 Token::Conditional(vec![Token::Field("artist".into())]),
228 Token::Conditional(vec![Token::Field("album".into())]),
229 Token::Conditional(vec![Token::Field("date".into())]),
230 Token::Field("title".into()),
231 ];
232 assert_eq!(evaluate(&tokens, &provider), "Creep");
233 }
234
235 #[test]
236 fn deeply_nested_conditionals() {
237 let provider = make_provider(&[("a", "1"), ("b", "2"), ("c", "3")]);
238 let tokens = vec![Token::Conditional(vec![
239 Token::Field("a".into()),
240 Token::Conditional(vec![
241 Token::Field("b".into()),
242 Token::Conditional(vec![Token::Field("c".into())]),
243 ]),
244 ])];
245 assert_eq!(evaluate(&tokens, &provider), "123");
246 }
247
248 #[test]
249 fn function_with_empty_arg() {
250 let provider = make_provider(&[]);
251 let tokens = vec![Token::Function {
253 name: "if".into(),
254 args: vec![
255 vec![Token::Literal("truthy".into())],
256 vec![], vec![Token::Literal("else".into())],
258 ],
259 }];
260 assert_eq!(evaluate(&tokens, &provider), "");
262 }
263
264 #[test]
265 fn stricmp_with_if_empty_then() {
266 let provider = make_provider(&[("x", "hello")]);
267 let tokens = vec![Token::Function {
269 name: "if".into(),
270 args: vec![
271 vec![Token::Function {
272 name: "stricmp".into(),
273 args: vec![
274 vec![Token::Field("x".into())],
275 vec![Token::Literal("hello".into())],
276 ],
277 }],
278 vec![], vec![Token::Literal("not hello".into())],
280 ],
281 }];
282 assert_eq!(evaluate(&tokens, &provider), "");
284 }
285
286 #[test]
287 fn stricmp_with_if_else() {
288 let provider = make_provider(&[("x", "world")]);
289 let tokens = vec![Token::Function {
290 name: "if".into(),
291 args: vec![
292 vec![Token::Function {
293 name: "stricmp".into(),
294 args: vec![
295 vec![Token::Field("x".into())],
296 vec![Token::Literal("hello".into())],
297 ],
298 }],
299 vec![],
300 vec![Token::Literal("not hello".into())],
301 ],
302 }];
303 assert_eq!(evaluate(&tokens, &provider), "not hello");
305 }
306
307 #[test]
308 fn conditional_with_only_literals_always_renders() {
309 let provider = make_provider(&[]);
310 let tokens = vec![Token::Conditional(vec![Token::Literal("always".into())])];
311 assert_eq!(evaluate(&tokens, &provider), "always");
313 }
314
315 #[test]
316 fn conditional_with_function_returning_empty() {
317 let provider = make_provider(&[("x", "nope")]);
318 let tokens = vec![Token::Conditional(vec![Token::Function {
319 name: "stricmp".into(),
320 args: vec![
321 vec![Token::Field("x".into())],
322 vec![Token::Literal("hello".into())],
323 ],
324 }])];
325 assert_eq!(evaluate(&tokens, &provider), "");
327 }
328}