struct_string_template/
formatter.rs

1use 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}