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}