string_template_plus/
transformers.rs

1/// Transformers for the template
2use std::ops::{Bound, RangeBounds};
3
4use crate::errors::TransformerError;
5use crate::VAR_TRANSFORM_SEP_CHAR;
6use lazy_static::lazy_static;
7use regex::Regex;
8use titlecase::titlecase;
9
10/// Applies any tranformations to the variable, you can chain the
11/// transformers called whenever you use [`VAR_TRANSFORM_SEP_CHAR`] to
12/// provide a transformer in the template.
13pub fn apply_tranformers(val: &str, transformations: &str) -> Result<String, TransformerError> {
14    let mut val: String = val.to_string();
15    for tstr in transformations.split(VAR_TRANSFORM_SEP_CHAR) {
16        if tstr.is_empty() {
17            continue;
18        }
19        let (name, args) = tstr.split_once('(').ok_or(TransformerError::InvalidSyntax(
20            tstr.to_string(),
21            "No opening paranthesis".to_string(),
22        ))?;
23        let args: Vec<&str> = args
24            .strip_suffix(')')
25            .ok_or(TransformerError::InvalidSyntax(
26                tstr.to_string(),
27                "No closing paranthesis".to_string(),
28            ))?
29            .split(',')
30            .collect();
31        val = match name {
32            "f" => float_format(&val, args)?,
33            "case" => string_case(&val, args)?,
34            "calc" => calc(&val, args)?,
35            "count" => count(&val, args)?,
36            "repl" => replace(&val, args)?,
37            "take" => take(&val, args)?,
38            "trim" => trim(&val, args)?,
39            "comma" => comma(&val, args)?,
40            "group" => group(&val, args)?,
41            "q" => quote(&val, args)?,
42            _ => {
43                return Err(TransformerError::UnknownTranformer(
44                    name.to_string(),
45                    val.to_string(),
46                ))
47            }
48        };
49    }
50    Ok(val)
51}
52
53/// Gets the bound of a rust range object
54///
55/// ```rust
56/// # use std::error::Error;
57/// # use string_template_plus::transformers::*;
58/// # use std::ops::RangeBounds;
59/// #
60/// # fn main() -> Result<(), Box<dyn Error>> {
61///     assert_eq!(bound((2..).end_bound(), true), None);
62///     assert_eq!(bound((..2).end_bound(), false), Some(1));
63///     assert_eq!(bound((..=2).end_bound(), false), Some(2));
64///     assert_eq!(bound((..2).start_bound(), true), None);
65///     assert_eq!(bound((0..).start_bound(), false), Some(0));
66/// # Ok(())
67/// # }
68pub fn bound(b: Bound<&usize>, lower: bool) -> Option<usize> {
69    match b {
70        Bound::Unbounded => None,
71        Bound::Included(v) => Some(*v),
72        Bound::Excluded(v) => Some(if lower { v + 1 } else { v - 1 }),
73    }
74}
75
76/// Checks whether the arguments lenth matches what is required
77fn check_arguments_len<R: RangeBounds<usize>>(
78    func_name: &'static str,
79    req: R,
80    given: usize,
81) -> Result<(), TransformerError> {
82    if req.contains(&given) {
83        Ok(())
84    } else {
85        match (
86            bound(req.start_bound(), true),
87            bound(req.end_bound(), false),
88        ) {
89            (None, Some(r)) => Err(TransformerError::TooManyArguments(func_name, r, given)),
90            (Some(r), None) => Err(TransformerError::TooFewArguments(func_name, r, given)),
91            (Some(r1), Some(r2)) => {
92                if given < r1 {
93                    Err(TransformerError::TooFewArguments(func_name, r1, given))
94                } else {
95                    Err(TransformerError::TooManyArguments(func_name, r2, given))
96                }
97            }
98            _ => Ok(()),
99        }
100    }
101}
102
103/// format the float (numbers). For example with `val=1.123`, `{val:f(2)}` or `{val:f(.2)}` gives `1.12`
104///
105/// ```rust
106/// # use std::error::Error;
107/// # use string_template_plus::transformers::*;
108/// #
109/// # fn main() -> Result<(), Box<dyn Error>> {
110///     assert_eq!(float_format("1.12", vec![".1"])?, "1.1");
111///     assert_eq!(float_format("1.12", vec!["2"])?, "1.12");
112///     assert_eq!(float_format("1.12", vec!["0"])?, "1");
113/// # Ok(())
114/// # }
115pub fn float_format(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
116    let func_name = "f";
117    check_arguments_len(func_name, 1..=1, args.len())?;
118    let format = args[0];
119    let val = val
120        .parse::<f64>()
121        .map_err(|_| TransformerError::InvalidValueType(func_name, "float"))?;
122    let mut start = 0usize;
123    let mut decimal = 6usize;
124    if let Some((d, f)) = format.split_once('.') {
125        if !d.is_empty() {
126            start = d.parse().map_err(|_| {
127                TransformerError::InvalidArgumentType(func_name, d.to_string(), "uint")
128            })?;
129        }
130        if f.is_empty() {
131            decimal = 0;
132        } else {
133            decimal = f.parse().map_err(|_| {
134                TransformerError::InvalidArgumentType(func_name, f.to_string(), "uint")
135            })?;
136        }
137    } else if !format.is_empty() {
138        decimal = format.parse().map_err(|_| {
139            TransformerError::InvalidArgumentType(func_name, format.to_string(), "uint")
140        })?;
141    }
142    Ok(format!("{0:1$.2$}", val, start, decimal))
143}
144
145/// Format the string. Supports `up`=> UPCASE, `down`=> downcase, `proper` => first character UPCASE all others downcase, `title` => title case according to [`titlecase::titlecase`]. e.g. `{var:case(up)}`.
146///
147/// ```rust
148/// # use std::error::Error;
149/// # use string_template_plus::transformers::*;
150/// #
151/// # fn main() -> Result<(), Box<dyn Error>> {
152///     assert_eq!(string_case("na", vec!["up"])?, "NA");
153///     assert_eq!(string_case("nA", vec!["down"])?, "na");
154///     assert_eq!(string_case("nA", vec!["proper"])?, "Na");
155///     assert_eq!(string_case("here, an apple", vec!["title"])?, "Here, an Apple");
156/// # Ok(())
157/// # }
158pub fn string_case(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
159    let func_name = "case";
160    check_arguments_len(func_name, 1..=1, args.len())?;
161    let format = args[0];
162    match format.to_lowercase().as_str() {
163        "up" => Ok(val.to_uppercase()),
164        "down" => Ok(val.to_lowercase()),
165        "title" => Ok(titlecase(val)),
166        "proper" => Ok({
167            let mut c = val.chars();
168            match c.next() {
169                None => String::new(),
170                Some(f) => {
171                    f.to_uppercase().collect::<String>() + c.as_str().to_lowercase().as_str()
172                }
173            }
174        }),
175        _ => Err(TransformerError::InvalidArgumentType(
176            func_name,
177            format.to_string(),
178            "{up;down;proper;title}",
179        )),
180    }
181}
182
183lazy_static! {
184    static ref CALC_NUMBERS: Regex = Regex::new("[0-9.]+").unwrap();
185}
186
187/// Airthmatic calculations, the value needs to be float. e.g. `{val:calc(+1)}` will add 1 to the value. The order of calculation is left to right.
188///
189/// ```rust
190/// # use std::error::Error;
191/// # use string_template_plus::transformers::*;
192/// #
193/// # fn main() -> Result<(), Box<dyn Error>> {
194///     assert_eq!(calc("1.24", vec!["+1"])?, "2.24");
195///     assert_eq!(calc("1", vec!["+1*2^2"])?, "16");
196///     assert_eq!(calc("1.24", vec!["+1", "-1"])?, "2.24,0.24");
197/// # Ok(())
198/// # }
199pub fn calc(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
200    let func_name = "calc";
201    check_arguments_len(func_name, 1.., args.len())?;
202
203    let val: f64 = val
204        .parse()
205        .map_err(|_| TransformerError::InvalidValueType(func_name, "float"))?;
206    let mut results: Vec<String> = Vec::new();
207    for expr in args {
208        let mut last_match = 0usize;
209        let mut result = val;
210        for cap in CALC_NUMBERS.captures_iter(expr) {
211            let m = cap.get(0).unwrap();
212            let curr_val = m.as_str().parse().map_err(|_| {
213                TransformerError::InvalidArgumentType(func_name, m.as_str().to_string(), "float")
214            })?;
215            if m.start() == 0 {
216                result = curr_val;
217            } else {
218                match &expr[last_match..m.start()] {
219                    "+" => result += curr_val,
220                    "-" => result -= curr_val,
221                    "/" => result /= curr_val,
222                    "*" => result *= curr_val,
223                    "^" => result = result.powf(curr_val),
224                    s => {
225                        return Err(TransformerError::InvalidArgumentType(
226                            func_name,
227                            s.to_string(),
228                            "{+,-,*,/,^}",
229                        ))
230                    }
231                };
232            }
233            last_match = m.end();
234        }
235        results.push(result.to_string());
236    }
237    Ok(results.join(","))
238}
239
240/// Count the number of occurances of a pattern in the string. You can chain it with [`calc`] to get the number of word like: `{val:count( ):calc(+1)}`
241///
242/// ```rust
243/// # use std::error::Error;
244/// # use string_template_plus::transformers::*;
245/// #
246/// # fn main() -> Result<(), Box<dyn Error>> {
247///     assert_eq!(count("nata", vec!["a"])?, "2");
248///     assert_eq!(count("nata", vec!["a", "t"])?, "2,1");
249///     assert_eq!(count("nata", vec![" "])?, "0");
250///     assert_eq!(count("hi there fellow", vec![" "])?, "2");
251/// # Ok(())
252/// # }
253pub fn count(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
254    let func_name = "count";
255    check_arguments_len(func_name, 1.., args.len())?;
256    let counts: Vec<String> = args
257        .iter()
258        .map(|sep| val.matches(sep).count().to_string())
259        .collect();
260    Ok(counts.join(","))
261}
262
263/// Replace text in the string, by another text
264///
265/// ```rust
266/// # use std::error::Error;
267/// # use string_template_plus::transformers::*;
268/// #
269/// # fn main() -> Result<(), Box<dyn Error>> {
270///     assert_eq!(replace("nata", vec!["a", "o"])?, "noto");
271///     assert_eq!(replace("hi there fellow", vec![" ", "-"])?, "hi-there-fellow");
272/// # Ok(())
273/// # }
274pub fn replace(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
275    let func_name = "replace";
276    check_arguments_len(func_name, 2..=2, args.len())?;
277    Ok(val.replace(args[0], args[1]))
278}
279
280/// Split the text with given separator and then take the Nth group
281///
282/// N=0, will give the whole group separated by comma, but it might
283/// give unexpected results if there is already comma in string and
284/// you're splitting with something else
285///
286/// ```rust
287/// # use std::error::Error;
288/// # use string_template_plus::transformers::*;
289/// #
290/// # fn main() -> Result<(), Box<dyn Error>> {
291///     assert_eq!(take("nata", vec!["a", "2"])?, "t");
292///     assert_eq!(take("hi there fellow", vec![" ", "2"])?, "there");
293///     assert_eq!(take("hi there fellow", vec![" ", "2", "2"])?, "there fellow");
294/// # Ok(())
295/// # }
296pub fn take(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
297    let func_name = "take";
298    check_arguments_len(func_name, 2..=3, args.len())?;
299    let n: usize = args[1].parse().map_err(|_| {
300        TransformerError::InvalidArgumentType(func_name, args[1].to_string(), "uint")
301    })?;
302    let spl = if args.len() == 2 {
303        val.split(args[0]).nth(n - 1)
304    } else {
305        val.splitn(
306            args[2].parse().map_err(|_| {
307                TransformerError::InvalidArgumentType(func_name, args[1].to_string(), "int")
308            })?,
309            args[0],
310        )
311        .nth(n - 1)
312    };
313
314    Ok(spl.unwrap_or("").to_string())
315}
316
317/// Trim the given string with given patterns one after another
318///
319///
320/// ```rust
321/// # use std::error::Error;
322/// # use string_template_plus::transformers::*;
323/// #
324/// # fn main() -> Result<(), Box<dyn Error>> {
325///     assert_eq!(trim("nata", vec!["a"])?, "nat");
326///     assert_eq!(trim("  \tnata\t  ", vec![])?, "nata");
327///     assert_eq!(trim("hi there! ", vec![" ", "!"])?, "hi there");
328///     assert_eq!(trim("hi there! ", vec![" !", "ih"])?, " there");
329/// # Ok(())
330/// # }
331pub fn trim(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
332    let func_name = "trim";
333    check_arguments_len(func_name, .., args.len())?;
334    if args.is_empty() {
335        return Ok(val.trim().to_string());
336    }
337    let mut val = val;
338    for arg in args {
339        val = val.trim_matches(|c| arg.contains(c))
340    }
341
342    Ok(val.to_string())
343}
344
345/// Insert commas to the given string in provided positions
346///
347///
348/// ```rust
349/// # use std::error::Error;
350/// # use string_template_plus::transformers::*;
351/// #
352/// # fn main() -> Result<(), Box<dyn Error>> {
353///     assert_eq!(comma("1234", vec!["3"])?, "1,234");
354///     assert_eq!(comma("1234567", vec!["3"])?, "1,234,567");
355///     assert_eq!(comma("1234567", vec!["3", "2"])?, "12,34,567");
356///     assert_eq!(comma("91234567", vec!["3", "2"])?, "9,12,34,567");
357/// # Ok(())
358/// # }
359pub fn comma(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
360    let func_name = "comma";
361    check_arguments_len(func_name, 1.., args.len())?;
362    let mut args: Vec<usize> = args
363        .iter()
364        .map(|s| {
365            s.parse().map_err(|_| {
366                TransformerError::InvalidArgumentType(func_name, s.to_string(), "uint")
367            })
368        })
369        .rev()
370        .collect::<Result<Vec<usize>, TransformerError>>()?;
371    let last = args[0];
372    let mut i = args.pop().unwrap();
373
374    let mut result = vec![];
375    let val: Vec<char> = val.replace(',', "").chars().rev().collect();
376    for c in val {
377        if i == 0 {
378            i = args.pop().unwrap_or(last);
379            result.push(',');
380        }
381        result.push(c);
382        i -= 1;
383    }
384    result.reverse();
385    let result: String = result.into_iter().collect();
386    Ok(result)
387}
388
389/// Insert characters to the given string in provided positions
390///
391///
392/// ```rust
393/// # use std::error::Error;
394/// # use string_template_plus::transformers::*;
395/// #
396/// # fn main() -> Result<(), Box<dyn Error>> {
397///     assert_eq!(group("1234", vec![",", "3"])?, "1,234");
398///     assert_eq!(group("1234567", vec!["_", "3"])?, "1_234_567");
399///     assert_eq!(group("1234567", vec![", ", "3", "2"])?, "12, 34, 567");
400///     assert_eq!(group("91234567", vec!["_", "3", "2"])?, "9_12_34_567");
401/// # Ok(())
402/// # }
403pub fn group(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
404    let func_name = "group";
405    check_arguments_len(func_name, 2.., args.len())?;
406    let sep = args[0];
407    let mut args: Vec<usize> = args[1..]
408        .iter()
409        .map(|s| {
410            s.parse().map_err(|_| {
411                TransformerError::InvalidArgumentType(func_name, s.to_string(), "uint")
412            })
413        })
414        .rev()
415        .collect::<Result<Vec<usize>, TransformerError>>()?;
416    let last = args[0];
417    let mut i = args.pop().unwrap();
418
419    let mut result = vec![];
420    let val: Vec<char> = val.replace(sep, "").chars().rev().collect();
421    for c in val {
422        if i == 0 {
423            i = args.pop().unwrap_or(last);
424            for c in sep.chars().rev() {
425                result.push(c);
426            }
427        }
428        result.push(c);
429        i -= 1;
430    }
431    result.reverse();
432    let result: String = result.into_iter().collect();
433    Ok(result)
434}
435
436/// Quote the text with given strings or `""`
437///
438/// ```rust
439/// # use std::error::Error;
440/// # use string_template_plus::transformers::*;
441/// #
442/// # fn main() -> Result<(), Box<dyn Error>> {
443///     assert_eq!(quote("nata", vec![])?, "\"nata\"");
444///     assert_eq!(quote("nata", vec!["'"])?, "'nata'");
445///     assert_eq!(quote("na\"ta", vec![])?, "\"na\\\"ta\"");
446///     assert_eq!(quote("na'ta", vec!["'"])?, "'na\\'ta'");
447///     assert_eq!(quote("nata", vec!["`", "'"])?, "`nata'");
448/// # Ok(())
449/// # }
450pub fn quote(val: &str, args: Vec<&str>) -> Result<String, TransformerError> {
451    let func_name = "quote";
452    check_arguments_len(func_name, ..=2, args.len())?;
453    Ok(if args.is_empty() {
454        format!("{:?}", val)
455    } else if args.len() == 1 {
456        if args[0].is_empty() {
457            format!("{:?}", val)
458        } else {
459            format!(
460                "{0}{1}{0}",
461                args[0],
462                val.replace(args[0], &format!("\\{}", args[0]))
463            )
464        }
465    } else {
466        format!(
467            "{}{}{}",
468            args[0],
469            val.replace(args[0], &format!("\\{}", args[0]))
470                .replace(args[1], &format!("\\{}", args[1])),
471            args[1]
472        )
473    })
474}