template_cli/
function.rs

1use std::ops::Index;
2
3use chrono::{DateTime, Local};
4use itertools::Itertools;
5use regex::Regex;
6use serde_json::{Map, Value};
7
8use crate::error::TemplateRenderError;
9
10fn type_of<T>(_: &T) -> String {
11    format!("{}", std::any::type_name::<T>())
12}
13
14pub fn to_boolean(value: &Value) -> bool {
15    match value {
16        Value::Null => false,
17        Value::Bool(bool) => *bool,
18        Value::Number(number) => {
19            if number.is_i64() {
20                number.as_i64().unwrap() != 0
21            } else if number.is_u64() {
22                number.as_u64().unwrap() != 0
23            } else if number.is_f64() {
24                number.as_f64().unwrap() != 0.0
25            } else {
26                unreachable!();
27            }
28        }
29        Value::String(string) => !string.trim().is_empty(),
30        Value::Array(items) => !items.is_empty(),
31        Value::Object(object) => !object.is_empty(),
32    }
33}
34
35fn kebab_case(value: &String) -> String {
36    let re = Regex::new(r"[^a-zA-Z0-9_]+").unwrap();
37    let lower = value.to_lowercase();
38    return re.replace_all(lower.as_str(), "-").to_string();
39}
40
41fn snake_case(value: &String) -> String {
42    let re = Regex::new(r"[^a-zA-Z0-9-]+").unwrap();
43    let lower = value.to_lowercase();
44    return re.replace_all(lower.as_str(), "_").to_string();
45}
46
47fn camel_case(value: &String) -> String {
48    let mut result = String::new();
49    let mut to_upper = false;
50    for c in value.chars() {
51        if c.is_alphanumeric() {
52            result.push(if to_upper { c.to_ascii_uppercase() } else { c });
53            to_upper = false;
54        } else {
55            to_upper = true;
56        }
57    }
58    return result;
59}
60
61fn pascal_case(value: &String) -> String {
62    let mut result = String::new();
63    let mut to_upper = true;
64    for c in value.chars() {
65        if c.is_alphanumeric() {
66            result.push(if to_upper { c.to_ascii_uppercase() } else { c });
67            to_upper = false;
68        } else {
69            to_upper = true;
70        }
71    }
72    return result;
73}
74
75fn capitalize(value: &String) -> String {
76    if value.is_empty() {
77        value.as_str().to_string()
78    } else {
79        let (first, rest) = value.split_at(1);
80        format!("{}{}", first.to_uppercase(), rest)
81    }
82}
83
84fn capitalize_words(value: &String) -> String {
85    let mut result = String::new();
86    let mut to_upper = true;
87    for c in value.chars() {
88        if c.is_whitespace() {
89            to_upper = true;
90            result.push(c);
91        } else {
92            result.push(if to_upper { c.to_ascii_uppercase() } else { c });
93            to_upper = false;
94        }
95    }
96    return result;
97}
98
99fn environment(value: &String) -> Option<String> {
100    return std::env::var(value.as_str()).ok();
101}
102
103fn default(value: &Value, default: &Value) -> Value {
104    if to_boolean(value) {
105        value.clone()
106    } else {
107        default.clone()
108    }
109}
110
111fn require_string_value(value: &Value) -> Result<&String, TemplateRenderError> {
112    match value {
113        Value::String(string) => Ok(string),
114        _ => Err(TemplateRenderError::TypeError(type_of(&value)))
115    }
116}
117
118fn require_u64_value(value: &Value) -> Result<u64, TemplateRenderError> {
119    value.as_u64()
120        .ok_or_else(|| TemplateRenderError::TypeError(type_of(&value)))
121}
122
123fn require_array_value(value: &Value) -> Result<&Vec<Value>, TemplateRenderError> {
124    value.as_array()
125        .ok_or_else(|| TemplateRenderError::TypeError(type_of(&value)))
126}
127
128fn require_object_value(value: &Value) -> Result<&Map<String, Value>, TemplateRenderError> {
129    value.as_object()
130        .ok_or_else(|| TemplateRenderError::TypeError(type_of(&value)))
131}
132
133fn require_argument<'a>(function: &'a str, arguments: &'a Vec<Value>, index: usize) -> Result<&'a Value, TemplateRenderError> {
134    arguments.get(index)
135        .ok_or_else(||
136            TemplateRenderError::RequiredArgumentMissing(format!("Argument {} is missing for function '{}'", index + 1, function.to_string()))
137        )
138}
139
140pub fn apply_function(value: &Value, function: &str, arguments: &Vec<Value>) -> Result<Value, TemplateRenderError> {
141    return match function {
142        "lowerCase" => {
143            let string = require_string_value(value)?;
144            Ok(Value::String(string.to_lowercase()))
145        }
146        "upperCase" => {
147            let string = require_string_value(value)?;
148            Ok(Value::String(string.to_uppercase()))
149        }
150        "kebabCase" => {
151            let string = require_string_value(value)?;
152            Ok(Value::String(kebab_case(string)))
153        }
154        "snakeCase" => {
155            let string = require_string_value(value)?;
156            Ok(Value::String(snake_case(string)))
157        }
158        "camelCase" => {
159            let string = require_string_value(value)?;
160            Ok(Value::String(camel_case(string)))
161        }
162        "pascalCase" => {
163            let string = require_string_value(value)?;
164            Ok(Value::String(pascal_case(string)))
165        }
166        "capitalize" => {
167            let string = require_string_value(value)?;
168            Ok(Value::String(capitalize(string)))
169        }
170        "capitalizeWords" => {
171            let string = require_string_value(value)?;
172            Ok(Value::String(capitalize_words(string)))
173        }
174        "length" => {
175            match value {
176                Value::String(string) => Ok(Value::from(string.len())),
177                Value::Array(array) => Ok(Value::from(array.len())),
178                Value::Object(dictionary) => Ok(Value::from(dictionary.len())),
179                _ => Err(TemplateRenderError::TypeError(type_of(&value)))
180            }
181        }
182        "environment" => {
183            let string = require_string_value(value)?;
184            Ok(environment(string)
185                .map(|value| Value::from(value))
186                .unwrap_or(Value::Null))
187        }
188        "default" => {
189            let default_value = require_argument(function, arguments, 0)?;
190            Ok(default(value, &default_value))
191        }
192        "coalesce" => {
193            let default_value = require_argument(function, arguments, 0)?;
194            let result = match value {
195                Value::Null => default_value,
196                _ => value,
197            };
198            Ok(result.clone())
199        }
200        "reverse" => {
201            match value {
202                Value::String(string) => Ok(Value::String(String::from_iter(string.chars().rev()))),
203                Value::Array(array) => {
204                    let mut reverted = array.clone();
205                    reverted.reverse();
206                    Ok(Value::Array(reverted))
207                }
208                _ => Err(TemplateRenderError::TypeError(type_of(&value)))
209            }
210        }
211        "split" => {
212            let string = require_string_value(value)?;
213            let splitter = require_argument(function, arguments, 0)?;
214            let splitter_string = require_string_value(splitter)?;
215            let split_strings = string.split(splitter_string).map(|split| Value::String(split.to_string())).collect();
216            Ok(Value::Array(split_strings))
217        }
218        "lines" => {
219            let string = require_string_value(value)?;
220            let lines: Vec<Value> = string.trim().lines().map(|item| Value::String(item.to_string())).collect();
221            Ok(Value::Array(lines))
222        }
223        "matches" => {
224            let string = require_string_value(value)?;
225            let regex = require_argument(function, arguments, 0)?;
226            let regex_string = require_string_value(regex)?;
227            let re = Regex::new(regex_string).map_err(|_err| TemplateRenderError::InvalidRegexError(regex_string.to_string()))?;
228            Ok(Value::Bool(re.is_match(string.as_str())))
229        }
230        "substring" => {
231            let string = require_string_value(value)?;
232            let from = require_argument(function, arguments, 0)?;
233            let from_value = require_u64_value(from)?;
234            let to = arguments.get(1);
235            if to.is_some() {
236                let to_value = require_u64_value(to.unwrap())?;
237                Ok(Value::String(string[usize::try_from(from_value).unwrap().max(0).min(string.len())..usize::try_from(to_value).unwrap().max(0).min(string.len())].to_string()))
238            } else {
239                Ok(Value::String(string[usize::try_from(from_value).unwrap().max(0).min(string.len())..].to_string()))
240            }
241        }
242        "take" => {
243            let n = require_argument(function, arguments, 0)?;
244            let n_value = require_u64_value(n)?;
245            let to_index = usize::try_from(n_value).unwrap();
246            match value {
247                Value::String(string) => Ok(Value::String(string[..to_index.max(0).min(string.len())].to_string())),
248                Value::Array(array) => Ok(Value::Array(array[..to_index.max(0).min(array.len())].to_vec())),
249                _ => Err(TemplateRenderError::TypeError(type_of(&value)))
250            }
251        }
252        "drop" => {
253            let n = require_argument(function, arguments, 0)?;
254            let n_value = require_u64_value(n)?;
255            let from_index = usize::try_from(n_value).unwrap();
256            match value {
257                Value::String(string) => Ok(Value::String(string[from_index.max(0).min(string.len())..].to_string())),
258                Value::Array(array) => Ok(Value::Array(array[from_index.max(0).min(array.len())..].to_vec())),
259                _ => Err(TemplateRenderError::TypeError(type_of(&value)))
260            }
261        }
262        "first" => {
263            let array = require_array_value(value)?;
264            Ok(array.first().unwrap_or(&Value::Null).clone())
265        }
266        "last" => {
267            let array = require_array_value(value)?;
268            Ok(array.last().unwrap_or(&Value::Null).clone())
269        }
270        "index" => {
271            let index = require_argument(function, arguments, 0)?;
272            let index_number = require_u64_value(index)?;
273            let array = require_array_value(value)?;
274            let result = if index_number < array.len() as u64 {
275                array.index(index_number as usize).clone()
276            } else {
277                Value::Null
278            };
279            Ok(result)
280        }
281        "contains" => {
282            let needle = require_argument(function, arguments, 0)?;
283            match value {
284                Value::String(substring) => {
285                    let needle_string = require_string_value(needle)?;
286                    Ok(Value::Bool(substring.contains(needle_string)))
287                }
288                Value::Array(array) => Ok(Value::Bool(array.contains(needle))),
289                _ => Err(TemplateRenderError::TypeError(type_of(&needle))),
290            }
291        }
292        "containsKey" => {
293            let key = require_argument(function, arguments, 0)?;
294            let object = require_object_value(value)?;
295            let key_value = require_string_value(key)?;
296            Ok(Value::Bool(object.contains_key(key_value)))
297        }
298        "containsValue" => {
299            let needle = require_argument(function, arguments, 0)?;
300            let object = require_object_value(value)?;
301            Ok(Value::Bool(object.values().any(|val| val == needle)))
302        }
303        "startsWith" => {
304            let string = require_string_value(value)?;
305            let start = require_argument(function, arguments, 0)?;
306            let start_string = require_string_value(start)?;
307            Ok(Value::Bool(string.starts_with(start_string)))
308        }
309        "endsWith" => {
310            let string = require_string_value(value)?;
311            let end = require_argument(function, arguments, 0)?;
312            let end_string = require_string_value(end)?;
313            Ok(Value::Bool(string.ends_with(end_string)))
314        }
315        "empty" => {
316            Ok(Value::Bool(!to_boolean(value)))
317        }
318        "unique" => {
319            let array = require_array_value(value)?;
320            let unique = array.clone().into_iter()
321                .unique_by(|item| format!("{item}"))
322                .collect::<Vec<_>>();
323            Ok(Value::Array(unique))
324        }
325        "keys" => {
326            let object = require_object_value(value)?;
327            let keys = object.keys().into_iter().map(|key| Value::String(key.clone())).collect::<Vec<_>>();
328            Ok(Value::Array(keys))
329        }
330        "values" => {
331            let object = require_object_value(value)?;
332            Ok(Value::Array(object.values().cloned().into_iter().collect::<Vec<_>>()))
333        }
334        "invert" => {
335            let object = require_object_value(value)?;
336            if let Some(item) = object.values().into_iter().find(|value| !value.is_string()) {
337                Err(TemplateRenderError::TypeError(type_of(&item)))
338            } else {
339                let inverted = object.clone().into_iter().map(|(key, value)| (value.as_str().unwrap().to_string(), Value::String(key))).collect();
340                Ok(Value::Object(inverted))
341            }
342        }
343        "toJson" => {
344            let result = serde_json::to_string(value)
345                .map_err(|_| TemplateRenderError::JsonSerializationError)?;
346            Ok(Value::String(result))
347        }
348        "toPrettyJson" => {
349            let result = serde_json::to_string_pretty(value)
350                .map_err(|_| TemplateRenderError::JsonSerializationError)?;
351            Ok(Value::String(result))
352        }
353        "fromJson" => {
354            let string = require_string_value(value)?;
355            serde_json::from_str(string.as_str())
356                .map_err(|error| TemplateRenderError::JsonParseError(error.to_string()))
357        }
358        "abbreviate" => {
359            let n = require_argument(function, arguments, 0)?;
360            let string = require_string_value(value)?;
361            let n_value = require_u64_value(n)?;
362            if string.len() <= n_value as usize {
363                Ok(Value::String(string.clone()))
364            } else {
365                Ok(Value::String(format!("{}…", string.as_str()[0..((n_value.max(1) as usize) - 1)].to_string())))
366            }
367        }
368        "trimLeft" => {
369            let string = require_string_value(value)?;
370            Ok(Value::String(string.trim_start().to_string()))
371        }
372        "trimRight" => {
373            let string = require_string_value(value)?;
374            Ok(Value::String(string.trim_end().to_string()))
375        }
376        "trim" => {
377            let string = require_string_value(value)?;
378            Ok(Value::String(string.trim().to_string()))
379        }
380        "replace" => {
381            let string = require_string_value(value)?;
382            let search = require_argument(function, arguments, 0)?;
383            let search_string = require_string_value(search)?;
384            let replacement = require_argument(function, arguments, 1)?;
385            let replacement_string = require_string_value(replacement)?;
386            Ok(Value::String(string.replace(search_string, replacement_string)))
387        }
388        "regexReplace" => {
389            let string = require_string_value(value)?;
390            let regex = require_argument(function, arguments, 0)?;
391            let regex_string = require_string_value(regex)?;
392            let parsed_regex = Regex::new(regex_string)
393                .map_err(|_| TemplateRenderError::InvalidRegexError(regex_string.to_string()))?;
394            let replacement = require_argument(function, arguments, 1)?;
395            let replacement_string = require_string_value(replacement)?;
396            Ok(Value::String(parsed_regex.replace_all(string.as_str(), replacement_string).to_string()))
397        }
398        "negate" => {
399            Ok(Value::Bool(!to_boolean(value)))
400        }
401        "all" => {
402            let result = require_array_value(value)?;
403            Ok(Value::Bool(result.into_iter().all(|item| to_boolean(item))))
404        }
405        "any" => {
406            let result = require_array_value(value)?;
407            Ok(Value::Bool(result.into_iter().any(|item| to_boolean(item))))
408        }
409        "none" => {
410            let result = require_array_value(value)?;
411            Ok(Value::Bool(result.into_iter().all(|item| !to_boolean(item))))
412        }
413        "some" => {
414            let result = require_array_value(value)?;
415            Ok(Value::Bool(result.into_iter().any(|item| !to_boolean(item))))
416        }
417        "chunked" => {
418            let array = require_array_value(value)?;
419            let chunk_size = require_argument(function, arguments, 0)?;
420            let chunk_size_number = require_u64_value(chunk_size)? as usize;
421            let overlap = require_argument(function, arguments, 1)?;
422            let overlap_number = require_u64_value(overlap)? as usize;
423
424            if overlap_number >= chunk_size_number {
425                Err(TemplateRenderError::ArgumentValueError(format!("The overlap ({overlap_number}) cannot be equal or larger than the chunk size ({chunk_size_number})")))
426            } else {
427                let mut result: Vec<Value> = vec![];
428
429                for i in (0..(array.len())).step_by(chunk_size_number - overlap_number) {
430                    // step 3, overlap 1
431                    // 0 to 3
432                    // 0+3-1 to 0+3-1+3
433                    result.push(Value::Array(array[i..(i + chunk_size_number - overlap_number).min(array.len())].to_vec()))
434                }
435                Ok(Value::Array(result))
436            }
437        }
438        "parseFormatDateTime" => {
439            let string = require_string_value(value)?;
440
441            let parse_result = if string == "now" {
442                // First argument is ignored
443                DateTime::from(Local::now())
444            } else {
445                let parse_format = require_argument(function, arguments, 0)?;
446                let parse_format_string = require_string_value(parse_format)?;
447                DateTime::parse_from_str(string, parse_format_string)
448                    .map_err(|err| TemplateRenderError::ArgumentValueError(format!("Could not parse date-time with value '{string}' and parse format string '{parse_format_string}': {err}")))?
449            };
450
451            let format = require_argument(function, arguments, 1)?;
452            let format_string = require_string_value(format)?;
453            let formatted = parse_result.format(format_string).to_string();
454            Ok(Value::String(formatted))
455        }
456        "alternate" => {
457            let index = require_u64_value(value)?;
458            let items = require_argument(function, arguments, 0)?;
459            let items_array = require_array_value(items)?;
460
461            if items_array.is_empty() {
462                Ok(Value::Null)
463            } else {
464                let array_index: usize = index.try_into()
465                    .map_err(|error| TemplateRenderError::ArgumentValueError(format!("{} cannot be cast to usize, {}", index, error)))?;
466
467                Ok(items_array[array_index % items_array.len()].clone())
468            }
469        }
470        "assert" => {
471            let expected_value = require_argument(function, arguments, 0)?;
472            let message = require_argument(function, arguments, 1)?;
473            let message_string = require_string_value(message)?;
474
475            if value != expected_value {
476                Err(TemplateRenderError::AssertionError(format!("Expected value '{}' but found '{}': {}", expected_value, value, message_string)))
477            } else {
478                Ok(Value::Null)
479            }
480        }
481        _ => Err(TemplateRenderError::UnknownFunctionError(function.to_string()))
482    };
483}