string_template/
lib.rs

1extern crate regex;
2
3use regex::Regex;
4use std::collections::HashMap;
5
6pub struct Template {
7    src: String,
8    matches: Vec<(usize, usize)>,
9}
10
11impl Template {
12    pub fn new(template: &str) -> Self {
13        let regex = Regex::new(r"\{\{([^}]*)\}\}").unwrap();
14
15        Template {
16            src: template.to_owned(),
17            matches: regex
18                .find_iter(template)
19                .map(|m| (m.start(), m.end()))
20                .collect(),
21        }
22    }
23
24    /// ```
25    /// # Examples
26    ///
27    /// let template = Template::new("Hi, my name is {{name}} and I'm a {{lang}} developer.");
28    ///
29    /// let mut args = HashMap::new();
30    /// args.insert("name", "Michael");
31    /// args.insert("lang", "Rust");
32    /// let s = template.render(&args);
33    ///
34    /// assert_eq!(s, "Hi, my name is Michael and I'm a Rust developer.");
35    ///
36    /// let mut args1 = HashMap::new();
37    /// args1.insert("name", "Vader");
38    /// args1.insert("lang", "Dart");
39    /// let s2 = template.render(&args1);
40    ///
41    /// assert_eq!(s2, "Hi, my name is Vader and I'm a Dart developer.");
42    /// ```
43    pub fn render(&self, vals: &HashMap<&str, &str>) -> String {
44        self.render_named(vals)
45    }
46
47    ///
48    /// See render() for examples.
49    ///
50    pub fn render_named(&self, vals: &HashMap<&str, &str>) -> String {
51        let mut parts: Vec<&str> = vec![];
52        let template_str = &self.src;
53
54        // get index of first arg match or return a copy of the template if no args matched
55        let first = match self.matches.first() {
56            Some((start, _)) => *start,
57            _ => return template_str.clone(),
58        };
59
60        // copy from template start to first arg
61        if first > 0 {
62            parts.push(&template_str[0..first])
63        }
64
65        // keeps the index of the previous argument end
66        let mut prev_end: Option<usize> = None;
67
68        for (start, end) in self.matches.iter() {
69            // copy from previous argument end till current argument start
70            if let Some(last_end) = prev_end {
71                parts.push(&template_str[last_end..*start])
72            }
73
74            // argument name with braces
75            let arg = &template_str[*start..*end];
76            // just the argument name
77            let arg_name = &arg[2..arg.len() - 2];
78
79            // if value passed for argument then append it, otherwise append original argument
80            // name with braces
81            match vals.get(arg_name) {
82                Some(s) => parts.push(s),
83                _ => parts.push(arg),
84            }
85
86            prev_end = Some(*end);
87        }
88
89        let template_len = template_str.len();
90        // if last arg end index isn't the end of the string then copy
91        // from last arg end till end of template string
92        if let Some(last_pos) = prev_end {
93            if last_pos < template_len {
94                parts.push(&template_str[last_pos..template_len])
95            }
96        }
97
98        parts.join("")
99    }
100
101    /// ```
102    /// let template = Template::new("Hi, my name is {{}} and I'm a {{}} developer.");
103    ///
104    /// let args = vec!["Michael", "Rust"];
105    /// let s = template.render_positional(&args);
106    /// assert_eq!(s, "Hi, my name is Michael and I'm a Rust developer.");
107    ///
108    /// let args1 = vec!["Vader", "Dart"];
109    /// let s2 = template.render_positional(&args1);
110    /// assert_eq!(s2, "Hi, my name is Vader and I'm a Dart developer.");
111    /// ```
112    pub fn render_positional(&self, vals: &[&str]) -> String {
113        let mut parts: Vec<&str> = vec![];
114        let template_str = &self.src;
115
116        // get index of first arg match or return a copy of the template if no args matched
117        let first = match self.matches.first() {
118            Some((start, _)) => *start,
119            _ => return template_str.clone(),
120        };
121
122        // copy from template start to first arg
123        if first > 0 {
124            parts.push(&template_str[0..first])
125        }
126
127        // keeps the index of the previous argument end
128        let mut prev_end: Option<usize> = None;
129
130        for ((start, end), val) in self.matches.iter().zip(vals.iter()) {
131            // copy from previous argument end till current argument start
132            if let Some(last_end) = prev_end {
133                parts.push(&template_str[last_end..*start])
134            }
135
136            parts.push(val);
137
138            prev_end = Some(*end);
139        }
140
141        let template_len = template_str.len();
142        // if last arg end index isn't the end of the string then copy
143        // from last arg end till end of template string
144        if let Some(last_pos) = prev_end {
145            if last_pos < template_len {
146                parts.push(&template_str[last_pos..template_len])
147            }
148        }
149
150        parts.join("")
151    }
152}