1use lazy_static::lazy_static;
41use regex::Regex;
42use std::collections::HashMap;
43
44lazy_static! {
45 static ref MATCH_START: Regex = Regex::new(r"^[{](\w+)[}]").unwrap();
47 static ref MATCH_OTHER: Regex = Regex::new(r"[^{][{](\w+)[}]").unwrap();
48}
49
50pub fn render(template: &str, values: &HashMap<String, String>) -> Result<String, String> {
102 let mut output = match MATCH_START.captures(template) {
106 None => template.to_string(),
107 Some(capture) => match capture.get(1) {
108 None => panic!("at the disco"),
109 Some(key) => match values.get(key.as_str()) {
110 None => return Err(key.as_str().to_string()),
111 Some(value) => Regex::new(format!("^[{{]{}[}}]", key.as_str()).as_str())
112 .unwrap()
113 .replace(template, value)
114 .to_string(),
115 },
116 },
117 };
118
119 loop {
120 match MATCH_OTHER.captures(&output) {
121 None => break,
122 Some(capture) => match capture.get(1) {
123 None => panic!("at the disco"),
124 Some(key) => match values.get(key.as_str()) {
125 None => return Err(key.as_str().to_string()),
126 Some(value) => {
127 output =
128 Regex::new(format!("([^{{])[{{]{}[}}]", key.as_str()).as_str())
129 .unwrap()
130 .replace(&output, format!("${{1}}{}", value))
131 .to_string()
132 }
133 },
134 },
135 }
136 }
137
138 Ok(output)
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn empty_string() {
147 let before = String::from("");
148 let after = String::from("");
149 let values = HashMap::new();
150
151 assert!(render(&before, &values) == Ok(after));
152 }
153
154 #[test]
155 fn no_substitution() {
156 let before = String::from("Hello world");
157 let after = String::from("Hello world");
158 let values = HashMap::new();
159
160 assert!(render(&before, &values) == Ok(after));
161 }
162
163 #[test]
164 fn ignore_escaped() {
165 let before = String::from("Hello {{middle} w{{orld");
166 let after = String::from("Hello {{middle} w{{orld");
167 let values = HashMap::new();
168
169 assert!(render(&before, &values) == Ok(after));
170 }
171
172 #[test]
173 fn missing_start_value() {
174 let before = String::from("{start} world");
175 let values = HashMap::new();
176
177 assert!(render(&before, &values) == Err(String::from("start")));
178 }
179
180 #[test]
181 fn missing_middle_value() {
182 let before = String::from("Hello {middle} world");
183 let values = HashMap::new();
184
185 assert!(render(&before, &values) == Err(String::from("middle")));
186 }
187 #[test]
188 fn missing_end_value() {
189 let before = String::from("Hello {end}");
190 let values = HashMap::new();
191
192 assert!(render(&before, &values) == Err(String::from("end")));
193 }
194
195 #[test]
196 fn missing_one_value() {
197 let before = String::from("{start} {middle} world");
198
199 let mut values = HashMap::new();
200 values.insert(String::from("start"), String::from("Hello"));
201
202 assert!(render(&before, &values) == Err(String::from("middle")));
203 }
204
205 #[test]
206 fn missing_one_value_again() {
207 let before = String::from("{start} {middle} world");
208
209 let mut values = HashMap::new();
210 values.insert(String::from("middle"), String::from("beautiful"));
211
212 assert!(render(&before, &values) == Err(String::from("start")));
213 }
214
215 #[test]
216 fn start() {
217 let before = String::from("{start} world");
218 let after = String::from("Hello world");
219
220 let mut values = HashMap::new();
221 values.insert(String::from("start"), String::from("Hello"));
222
223 assert!(render(&before, &values) == Ok(after));
224 }
225
226 #[test]
227 fn end() {
228 let before = String::from("Hello {end}");
229 let after = String::from("Hello world");
230
231 let mut values = HashMap::new();
232 values.insert(String::from("end"), String::from("world"));
233
234 assert!(render(&before, &values) == Ok(after));
235 }
236
237 #[test]
238 fn middle() {
239 let before = String::from("Hello {middle} world");
240 let after = String::from("Hello beautiful world");
241
242 let mut values = HashMap::new();
243 values.insert(String::from("middle"), String::from("beautiful"));
244
245 assert!(render(&before, &values) == Ok(after));
246 }
247
248 #[test]
249 fn hello_beautiful_world() {
250 let before = String::from("{start} {middle} {end}");
251 let after = String::from("Hello beautiful world");
252
253 let mut values = HashMap::new();
254 values.insert(String::from("start"), String::from("Hello"));
255 values.insert(String::from("middle"), String::from("beautiful"));
256 values.insert(String::from("end"), String::from("world"));
257
258 assert!(render(&before, &values) == Ok(after));
259 }
260
261 #[test]
262 fn multi_line_hello() {
263 let before = String::from("{start} is a\n{middle} test to see\nif the regex {end}");
264 let after = String::from("This is a\nmulti-line test to see\nif the regex works");
265
266 let mut values = HashMap::new();
267 values.insert(String::from("start"), String::from("This"));
268 values.insert(String::from("middle"), String::from("multi-line"));
269 values.insert(String::from("end"), String::from("works"));
270
271 assert!(render(&before, &values) == Ok(after));
272 }
273
274 #[test]
275 fn a_longer_test() {
276 let before =
277 String::from(format!("{}\n{}\n{}\n{}\n{}",
278 "No society can surely {fourth}e flourishing {first} happy, {third} which {second} far greater part {third} {second}",
279 "mem{fourth}ers are poor {first} misera{fourth}le. It is but equity, besides, that they who feed,",
280 "clothe, {first} lodge {second} whole body {third} {second} people, should have such a share {third} {second}",
281 "produce {third} their own la{fourth}our as to be themselves tolera{fourth}ly well fed, clothed, {first}",
282 "lodged."));
283
284 let after =
285 String::from(format!("{}\n{}\n{}\n{}\n{}",
286 "No society can surely be flourishing and happy, of which the far greater part of the",
287 "members are poor and miserable. It is but equity, besides, that they who feed,",
288 "clothe, and lodge the whole body of the people, should have such a share of the",
289 "produce of their own labour as to be themselves tolerably well fed, clothed, and",
290 "lodged."));
291
292 let mut values = HashMap::new();
293 values.insert(String::from("first"), String::from("and"));
294 values.insert(String::from("second"), String::from("the"));
295 values.insert(String::from("third"), String::from("of"));
296 values.insert(String::from("fourth"), String::from("b"));
297 values.insert(String::from("fifth"), String::from("these"));
298 values.insert(String::from("sixth"), String::from("last"));
299 values.insert(String::from("seventh"), String::from("ones"));
300 values.insert(String::from("eighth"), String::from("do"));
301 values.insert(String::from("ninth"), String::from("not"));
302 values.insert(String::from("tenth"), String::from("exist"));
303
304 assert!(render(&before, &values) == Ok(after));
305 }
306}