1use std::collections::HashMap;
30
31#[derive(Debug)]
33pub enum Error {
34 MismatchedBraces { open: usize, close: usize },
38
39 ExpectedClosing { head: usize },
43
44 ExpectedOpening { tail: usize },
48
49 NestedTemplate { pos: usize },
53
54 DuplicateKeys,
56
57 EmptyTemplate,
59
60 OptionalKeys,
62}
63
64impl std::fmt::Display for Error {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 use Error::*;
67 match self {
68 MismatchedBraces { open, close } => write!(
69 f,
70 "found {} open braces, and {} closed braces. a mistmatch",
71 open, close
72 ),
73 ExpectedClosing { head } => write!(f, "expected closing bracket from offset {}", head),
74 ExpectedOpening { tail } => write!(f, "expected opening bracket from offset {}", tail),
75 NestedTemplate { pos } => write!(f, "nested template starting at offset: {}", pos),
76 DuplicateKeys => f.write_str("duplicate keys were found"),
77 EmptyTemplate => f.write_str("empty template was found"),
78 OptionalKeys => f.write_str("optional keys were found"),
79 }
80 }
81}
82impl std::error::Error for Error {}
83
84#[derive(Debug, Clone)]
85struct State<'a> {
86 keys: Vec<&'a str>,
87}
88
89impl<'a> State<'a> {
90 fn new(keys: Vec<&'a str>) -> Self {
91 Self { keys }
92 }
93
94 fn has_keys(&self) -> bool {
95 !self.keys.is_empty()
96 }
97
98 fn remove(&mut self, key: &str) -> Option<(&'a str, usize)> {
99 if self.keys.is_empty() {
100 return None;
101 }
102
103 let mut out = None;
104 let mut i = 0;
105 while i != self.keys.len() {
106 if self.keys[i] == key {
107 let s = self.keys.remove(i);
108 let (_, count) = out.get_or_insert_with(|| (s, 0));
109 *count += 1;
110 } else {
111 i += 1;
112 }
113 }
114 out
115 }
116
117 fn has_duplicates(&self) -> bool {
118 let mut set = std::collections::HashSet::new();
119 self.keys.iter().any(|key| !set.insert(key))
120 }
121}
122
123#[derive(Clone, Debug)]
149pub struct Template<'a> {
150 data: String, state: State<'a>,
152 opts: Opts,
153}
154
155impl<'a> Template<'a> {
156 pub fn parse(input: &'a str, opts: Opts) -> Result<Self, Error> {
162 let state = State::new(Self::find_keys(input)?);
163 opts.validate(&state)?;
164 Ok(Self {
165 data: input.to_string(),
166 state,
167 opts,
168 })
169 }
170
171 pub fn is_empty(&self) -> bool {
173 self.opts.empty_template
174 }
175
176 pub fn apply<'k>(mut self, args: &Args<'k>) -> Result<String, Error> {
180 for (key, val) in &args.mapping {
181 let matches = self.state.remove(key);
182 match matches {
183 Some((match_, _)) => {
184 let s = self.data.replace(&format!("${{{}}}", match_), &val);
185 std::mem::replace(&mut self.data, s);
186 }
187 None if self.opts.optional_keys || self.is_empty() => continue,
188 _ => return Err(Error::OptionalKeys),
189 }
190 }
191
192 self.data.shrink_to_fit();
193 Ok(self.data)
194 }
195
196 pub fn find_keys(input: &str) -> Result<Vec<&str>, Error> {
210 let mut heads = vec![];
211 let mut tails = vec![];
212
213 let mut last = None;
214 let mut iter = input.char_indices().peekable();
215 while let Some((pos, ch)) = iter.next() {
216 if ch == '$' && iter.peek().map(|&(_, d)| d == '{').unwrap_or_default() {
217 last.replace(pos);
218 heads.push(pos);
219 iter.next();
220 }
221 if ch == '{' && last.is_some() {
222 return Err(Error::NestedTemplate { pos });
223 }
224
225 if ch == '}' && last.is_some() {
226 tails.push(pos);
227 last.take();
228 }
229 }
230
231 if heads.len() != tails.len() {
232 return Err(Error::MismatchedBraces {
233 open: heads.len(),
234 close: tails.len(),
235 });
236 }
237
238 tails.reverse();
239
240 let mut keys = Vec::with_capacity(heads.len());
241 for head in heads {
242 let tail = tails.pop().ok_or_else(|| Error::ExpectedClosing { head })?;
243 if tail > head {
244 keys.push(&input[head + 2..tail]);
245 } else {
246 return Err(Error::ExpectedOpening { tail });
247 }
248 }
249
250 if !tails.is_empty() {
251 return Err(Error::MismatchedBraces {
252 open: 0,
253 close: tails.len(),
254 });
255 }
256
257 Ok(keys)
258 }
259}
260
261#[derive(Default, Copy, Clone, Debug, PartialEq)]
287pub struct Opts {
288 optional_keys: bool,
289 duplicate_keys: bool,
290 empty_template: bool,
291}
292
293impl Opts {
294 pub fn optional_keys(&mut self) -> &mut Self {
298 self.optional_keys = !self.optional_keys;
299 self
300 }
301
302 pub fn duplicate_keys(&mut self) -> &mut Self {
306 self.duplicate_keys = !self.duplicate_keys;
307 self
308 }
309
310 pub fn empty_template(&mut self) -> &mut Self {
314 self.empty_template = !self.empty_template;
315 self
316 }
317
318 pub fn build(self) -> Self {
320 self
321 }
322
323 fn validate(self, keys: &State<'_>) -> Result<(), Error> {
324 if !self.empty_template && !keys.has_keys() {
325 return Err(Error::EmptyTemplate);
326 }
327 if !self.duplicate_keys && keys.has_duplicates() {
328 return Err(Error::DuplicateKeys);
329 }
330 Ok(())
331 }
332}
333
334#[derive(Default, Clone)]
349pub struct Args<'k> {
350 mapping: HashMap<std::borrow::Cow<'k, str>, String>,
351}
352
353impl<'k> Args<'k> {
354 pub fn new() -> Self {
356 Self {
357 mapping: HashMap::new(),
358 }
359 }
360
361 pub fn len(&self) -> usize {
363 self.mapping.len()
364 }
365
366 pub fn is_empty(&self) -> bool {
368 self.mapping.is_empty()
369 }
370
371 pub fn with(
373 mut self,
374 key: impl Into<std::borrow::Cow<'k, str>>,
375 val: impl std::fmt::Display,
376 ) -> Self {
377 self.mapping.insert(key.into(), val.to_string().into());
378 self
379 }
380
381 pub fn iter(&self) -> impl Iterator<Item = (&'_ std::borrow::Cow<'k, str>, &'_ String)> + '_ {
382 self.mapping.iter()
383 }
384}
385
386pub type ArgsIntoIter<'k> = std::collections::hash_map::IntoIter<std::borrow::Cow<'k, str>, String>;
387
388impl<'k> IntoIterator for Args<'k> {
389 type Item = (std::borrow::Cow<'k, str>, String);
390 type IntoIter = ArgsIntoIter<'k>;
391 fn into_iter(self) -> Self::IntoIter {
392 self.mapping.into_iter()
393 }
394}
395
396impl<'k, K, V> std::iter::FromIterator<(K, V)> for Args<'k>
397where
398 K: Into<std::borrow::Cow<'k, str>>,
399 V: std::fmt::Display,
400{
401 fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
402 Self {
403 mapping: iter
404 .into_iter()
405 .map(|(k, v)| (k.into(), v.to_string()))
406 .collect(),
407 }
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn duplicate_key() {
417 let args = Args::new()
418 .with("a", &true)
419 .with("a", &false)
420 .with("a", &true);
421
422 let v = args
423 .into_iter()
424 .map(|(k, v)| (k, v.to_string()))
425 .collect::<Vec<_>>();
426 assert_eq!(v, vec![("a".into(), "true".to_string())]);
427 }
428
429 #[test]
430 fn duplicates() {
431 let state = State::new(vec!["a", "b", "c"]);
432 assert!(!state.has_duplicates());
433
434 let state = State::new(vec!["a", "b", "a", "c"]);
435 assert!(state.has_duplicates());
436 }
437
438 #[test]
439 fn basic() {
440 let p = Template::parse("${a} ${b}${c}", Default::default()).unwrap();
441 let a = Args::new().with("a", &0).with("b", &1).with("c", &2);
442 let t = p.apply(&a).unwrap();
443 assert_eq!(t, "0 12");
444 }
445
446 #[test]
447 fn apply_iter() {
448 let mut base = (b'a'..=b'z')
449 .map(|c| format!("${{{}}}", c as char))
450 .collect::<Vec<_>>()
451 .join(" ");
452
453 for c in b'a'..=b'z' {
454 let t = Template::parse(&base, Default::default()).unwrap();
455 let a = Args::new().with(format!("{}", c as char), format!("{} = {}", c as char, c));
456 base = t.apply(&a).unwrap();
457 }
458
459 let expected = "a = 97 b = 98 c = 99 d = 100 e = 101 \
460 f = 102 g = 103 h = 104 i = 105 j = 106 \
461 k = 107 l = 108 m = 109 n = 110 o = 111 \
462 p = 112 q = 113 r = 114 s = 115 t = 116 \
463 u = 117 v = 118 w = 119 x = 120 y = 121 \
464 z = 122";
465
466 assert_eq!(base, expected);
467 }
468
469 #[test]
470 fn owned_key() {
471 let args: Args<'static> = Args::new().with("foo".to_string(), 42);
472 assert_eq!(args.len(), 1);
473 }
474
475 #[test]
476 fn with_args() {
477 let template = "you've reached a max of ${max} credits, \
478 out of ${total} total credits with ${success} \
479 successes and ${failure} failures. and I've \
480 'collected' ${overall_total} credits from all of \
481 the failures.";
482
483 let t = Template::parse(&template, Default::default()).unwrap();
484 let parts = Args::new()
485 .with("max", &"218,731")
486 .with("total", &"706,917")
487 .with("success", &"169")
488 .with("failure", &"174")
489 .with("overall_total", &"1,629,011");
490
491 let expected = "you've reached a max of 218,731 credits, \
492 out of 706,917 total credits with 169 \
493 successes and 174 failures. and I've \
494 'collected' 1,629,011 credits from all of \
495 the failures.";
496
497 assert_eq!(t.apply(&parts).unwrap(), expected);
498 }
499
500 #[test]
501 fn empty_template() {
502 let input = "";
503 Template::parse(&input, Default::default()).unwrap_err(); let template = Template::parse(&input, Opts::default().empty_template().build()).unwrap();
506 assert!(template.is_empty());
507 assert_eq!(input, template.apply(&Args::new()).unwrap());
508
509 let input = "foobar baz quux {{something}}";
510 Template::parse(&input, Default::default()).unwrap_err(); let template = Template::parse(&input, Opts::default().empty_template().build()).unwrap();
513 assert!(template.is_empty());
514 assert_eq!(input, template.apply(&Args::new()).unwrap());
515 }
516
517 #[test]
518 fn duplicate_keys() {
519 let input = "${one} and ${two} and ${one}";
520 Template::parse(&input, Default::default()).unwrap_err(); let input = "${one} and ${two} and ${one}";
523 let template = Template::parse(&input, Opts::default().duplicate_keys().build()).unwrap();
524 let parts = Args::new().with("one", &1).with("two", &2);
525 assert_eq!("1 and 2 and 1", template.apply(&parts).unwrap());
526 }
527
528 #[test]
529 fn optional_keys() {
530 let input = "${foo} ${bar} ${baz}";
531
532 let parts = Args::new().with("foo", &false).with("unknown", &true);
533
534 let template = Template::parse(&input, Default::default()).unwrap();
535 template.apply(&parts).unwrap_err(); let template = Template::parse(&input, Opts::default().optional_keys().build()).unwrap();
538 assert_eq!("false ${bar} ${baz}", template.apply(&parts).unwrap());
539 }
540
541 #[test]
542 fn empty_template_replace() {
543 let template =
544 Template::parse("${short_name}", Opts::default().empty_template().build()).unwrap();
545 let parts = Args::new().with("short_name", &1);
546 assert_eq!("1", template.apply(&parts).unwrap());
547 }
548
549 #[test]
550 fn args_owned() {
551 let args = Args::new().with("foo", 42).with("bar", false);
552 let template = Template::parse("${foo} ${bar}", Default::default()).unwrap();
553 let s = template.apply(&args).unwrap();
554 assert_eq!(s, "42 false");
555
556 let key = "foo".to_string();
557 let args: Args = Args::new().with(&key, &42).with("bar", false);
558 let template = Template::parse("${foo} ${bar}", Default::default()).unwrap();
559 let s = template.apply(&args).unwrap();
560 assert_eq!(s, "42 false");
561 }
562}