struct_string_template/
formatter.rs1use unicode_segmentation::UnicodeSegmentation;
2
3use crate::err::TemplateError;
4
5
6#[derive(Debug, PartialEq, Eq, Clone)]
7pub enum TemplateElement {
8 Part {
9 literal: String
10 },
11 Replace {
12 selector: String
13 },
14}
15
16pub struct Formatter {
17 pub(crate) elements: Vec<TemplateElement>
18}
19
20impl IntoIterator for Formatter {
21 type Item = TemplateElement;
22 type IntoIter = std::vec::IntoIter<Self::Item>;
23
24 fn into_iter(self) -> Self::IntoIter {
25 self.elements.into_iter()
26 }
27}
28
29impl Formatter {
30 pub fn build<S>(fmts: S) -> Result<Self, TemplateError>
31 where S: Into<String>
32 {
33 let as_str = fmts.into();
34 let mut chars = as_str.graphemes(true);
35 let mut result = Vec::new();
36 let mut tmp = String::new();
37
38 while let Some(c) = chars.next() {
39 if c != "%" {
40 tmp.push_str(c);
41 continue;
42 }
43
44 match chars.next() {
45 Some("%") => {
46 tmp.push_str("%")
47 },
48 Some("(") => {
49 if !tmp.is_empty() {
50 result.push(TemplateElement::Part { literal: tmp.clone() });
51 }
52 tmp.clear();
53 let mut selector = String::new();
54 loop {
55 match chars.next() {
56 Some(")") => break,
57 Some(c) => selector.push_str(c),
58 None => return Err(TemplateError::UnexpectedEnd { formats: as_str }),
59 }
60 }
61 result.push(TemplateElement::Replace { selector });
62 },
63 Some(c) => return Err(TemplateError::UnexpectedCharacter {
64 character: c.to_string(),
65 formats: as_str,
66 }),
67 None => return Err(TemplateError::UnexpectedEnd { formats: as_str }),
68 };
69 }
70
71 if !tmp.is_empty() {
72 result.push(TemplateElement::Part { literal: tmp.clone() });
73 }
74
75 Ok(Self { elements: result })
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use crate::formatter::TemplateElement;
82 use crate::err::TemplateError;
83 use super::Formatter;
84
85 fn repl(s: &str) -> TemplateElement {
86 TemplateElement::Replace { selector: s.to_owned() }
87 }
88
89 fn part(s: &str) -> TemplateElement {
90 TemplateElement::Part { literal: s.to_owned() }
91 }
92
93 #[test]
94 fn test_valid() {
95 let fmts = "%(title) %(id) %%(select) ";
96 let result = Formatter::build(fmts);
97 assert!(result.is_ok());
98 let formatter = result.ok().unwrap();
99 assert_eq!(formatter.elements, vec![
100 repl("title"),
101 part(" "),
102 repl("id"),
103 part(" %(select) "),
104 ])
105 }
106
107 #[test]
108 fn test_valid_empty() {
109 let fmts = "";
110 let result = Formatter::build(fmts);
111 assert!(result.is_ok());
112 let formatter = result.ok().unwrap();
113 assert_eq!(formatter.elements, vec![])
114 }
115
116 #[test]
117 fn test_valid_whitespace() {
118 let fmts = "\n\t ";
119 let result = Formatter::build(fmts);
120 assert!(result.is_ok());
121 let formatter = result.ok().unwrap();
122 assert_eq!(formatter.elements, vec![
123 part("\n\t "),
124 ])
125 }
126
127 #[test]
128 fn test_valid_nonalnum_in_selector() {
129 let fmts = "%(a.b.c.d)%(p// q)%((?;:'))";
130 let result = Formatter::build(fmts);
131 assert!(result.is_ok());
132 let formatter = result.ok().unwrap();
133 assert_eq!(formatter.elements, vec![
134 repl("a.b.c.d"),
135 repl("p// q"),
136 repl("(?;:'"),
137 part(")"),
138 ])
139 }
140
141 #[test]
142 fn test_valid_marker_literal_simple() {
143 let fmts = "%%%%";
144 let result = Formatter::build(fmts);
145 assert!(result.is_ok());
146 let formatter = result.ok().unwrap();
147 assert_eq!(formatter.elements, vec![
148 part("%%"),
149 ])
150 }
151
152 #[test]
153 fn test_valid_marker_literal_complex() {
154 let fmts = "%%%%%%%(a)P%%%%";
155 let result = Formatter::build(fmts);
156 assert!(result.is_ok());
157 let formatter = result.ok().unwrap();
158 assert_eq!(formatter.elements, vec![
159 part("%%%"),
160 repl("a"),
161 part("P%%"),
162 ])
163 }
164
165 #[test]
166 fn test_invalid_marker_not_followed_by_open_paren() {
167 let fmts = "%%()%l";
168 let result = Formatter::build(fmts);
169 assert!(result.is_err());
170 assert!(matches!(result, Err(TemplateError::UnexpectedCharacter { .. })))
171 }
172
173 #[test]
174 fn test_invalid_unterminated_selector() {
175 let fmts = "%%%(abcd pl";
176 let result = Formatter::build(fmts);
177 assert!(result.is_err());
178 assert!(matches!(result, Err(TemplateError::UnexpectedEnd { .. })))
179 }
180
181 #[test]
182 fn test_invalid_end_reached_after_marker() {
183 let fmts = "%%%%%";
184 let result = Formatter::build(fmts);
185 assert!(result.is_err());
186 assert!(matches!(result, Err(TemplateError::UnexpectedEnd { .. })))
187 }
188}