spin_expressions/
template.rs1use std::fmt::Display;
2
3use crate::{Error, Result};
4
5#[derive(Clone, Debug, PartialEq)]
8pub struct Template {
9 parts: Vec<Part>,
10}
11
12impl Template {
13 pub fn new(template: impl Into<Box<str>>) -> Result<Self> {
14 let mut parts = vec![];
15 let mut remainder: Box<str> = template.into();
16 while !remainder.is_empty() {
17 let (part, rest) = if let Some(expr_rest) = remainder.strip_prefix("{{") {
18 if let Some((expr, rest)) = expr_rest.split_once("}}") {
20 (Part::expr(expr.trim()), rest)
22 } else {
23 return Err(Error::InvalidTemplate(
25 "unmatched '{{' in template".to_string(),
26 ));
27 }
28 } else {
29 if let Some(idx) = remainder.find("{{") {
31 let (lit, rest) = remainder.split_at(idx);
33 (Part::lit(lit), rest)
34 } else {
35 (Part::lit(remainder), "")
37 }
38 };
39 parts.push(part);
40 remainder = rest.into();
41 }
42 Ok(Template { parts })
43 }
44
45 pub fn is_literal(&self) -> bool {
46 self.parts.iter().all(|p| matches!(p, Part::Lit(_)))
47 }
48
49 pub(crate) fn parts(&self) -> std::slice::Iter<Part> {
50 self.parts.iter()
51 }
52}
53
54impl Display for Template {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 self.parts().try_for_each(|part| match part {
57 Part::Lit(lit) => f.write_str(lit),
58 Part::Expr(expr) => write!(f, "{{ {} }}", expr),
59 })
60 }
61}
62
63#[derive(Clone, Debug, PartialEq)]
64pub(crate) enum Part {
65 Lit(Box<str>),
66 Expr(Box<str>),
67}
68
69impl Part {
70 pub fn lit(lit: impl Into<Box<str>>) -> Self {
71 Self::Lit(lit.into())
72 }
73
74 pub fn expr(expr: impl Into<Box<str>>) -> Self {
75 Self::Expr(expr.into())
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn template_parts() {
85 for (tmpl, expected) in [
86 ("", vec![]),
87 ("a", vec![Part::lit("a")]),
88 (
89 "a-{{ expr }}-b",
90 vec![Part::lit("a-"), Part::expr("expr"), Part::lit("-b")],
91 ),
92 (
93 "{{ expr1 }}{{ expr2 }}",
94 vec![Part::expr("expr1"), Part::expr("expr2")],
95 ),
96 ] {
97 let template = Template::new(tmpl).unwrap();
98 assert!(
99 template.parts().eq(&expected),
100 "{:?} -> {:?} != {:?}",
101 tmpl,
102 template,
103 expected,
104 );
105 }
106 }
107
108 #[test]
109 fn template_parts_bad() {
110 Template::new("{{ matched }} {{ unmatched").unwrap_err();
111 }
112}