jens/parser/
template.rs

1use crate::parser::{get_ident, parse_phase2, segment::Segment, Rule};
2use pest::iterators::Pair;
3
4#[derive(Debug, Default, PartialEq)]
5pub struct Template {
6    pub name: String,
7    pub indent_ignored: usize,
8    pub lines: Vec<TemplateLine>,
9}
10
11impl<'a> From<Pair<'a, Rule>> for Template {
12    fn from(pair: Pair<'a, Rule>) -> Template {
13        let mut template = Template::default();
14
15        for item in pair.into_inner() {
16            match item.as_rule() {
17                Rule::template_content => template.lines.push(item.as_str().into()),
18                Rule::template_decl => template.name = get_ident(item),
19                Rule::template_line => template.lines.push(item.into()),
20                Rule::template_terminator => {
21                    template.indent_ignored = item.as_str().matches("-").count()
22                }
23                Rule::template_empty_line => template.lines.push(TemplateLine::default()),
24                Rule::EOI => {}
25                _ => unreachable!(),
26            }
27        }
28        template
29    }
30}
31
32impl Template {
33    pub fn placeholder_names(&self) -> Vec<String> {
34        let mut names = Vec::new();
35        for line in &self.lines {
36            for segment in &line.segments {
37                match segment {
38                    Segment::Placeholder(ref s) => {
39                        if !names.contains(s) {
40                            names.push(s.clone());
41                        }
42                    }
43                    _ => {}
44                }
45            }
46        }
47        names
48    }
49}
50
51#[derive(Debug, Default, PartialEq)]
52pub struct TemplateLine {
53    pub indentation: String,
54    pub segments: Vec<Segment>,
55}
56
57impl From<&str> for TemplateLine {
58    fn from(s: &str) -> TemplateLine {
59        TemplateLine {
60            indentation: "".into(),
61            segments: parse_phase2(s).unwrap(),
62        }
63    }
64}
65
66impl<'a> From<Pair<'a, Rule>> for TemplateLine {
67    fn from(pair: Pair<'a, Rule>) -> TemplateLine {
68        let mut indentation = String::new();
69        let mut segments = vec![];
70        for item in pair.into_inner() {
71            match item.as_rule() {
72                Rule::significant_whitespace => indentation = item.as_str().into(),
73                Rule::template_content => segments = parse_phase2(item.as_str()).unwrap(),
74                _ => unreachable!(),
75            }
76        }
77        TemplateLine {
78            indentation,
79            segments,
80        }
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use crate::parser::parse;
87
88    #[test]
89    fn empty_template() {
90        use insta::assert_debug_snapshot_matches;
91        let templates = parse("main =\n----").unwrap();
92
93        assert_debug_snapshot_matches!("template.empty_template", templates);
94    }
95
96    #[test]
97    fn space_indented_content() {
98        use insta::assert_debug_snapshot_matches;
99        let templates = parse("main =\n    indent4\n     indent5\n----").unwrap();
100
101        assert_debug_snapshot_matches!("template.indented_content", templates);
102    }
103
104    #[test]
105    fn tab_indented() {
106        use insta::assert_debug_snapshot_matches;
107        let templates = parse("main =\n\tindent1\n\t\tindent2\n-").unwrap();
108
109        assert_debug_snapshot_matches!("template.tab_indented", templates);
110    }
111
112    #[test]
113    fn one_liner() {
114        use insta::assert_debug_snapshot_matches;
115        let templates =
116            parse("main =      this is a one-liner and white space at the beginning is ignored")
117                .unwrap();
118
119        assert_debug_snapshot_matches!("template.one_liner", templates);
120    }
121
122    #[test]
123    fn mixed_indentation() {
124        use insta::assert_debug_snapshot_matches;
125        let templates = parse("main =\n    \tindent\n----").unwrap();
126
127        assert_debug_snapshot_matches!("template.mixed_indentation", templates);
128    }
129
130    #[test]
131    fn ignore_more_indentation() {
132        use insta::assert_debug_snapshot_matches;
133        let templates = parse("main =\n  content\n----").unwrap();
134
135        assert_debug_snapshot_matches!("template.ignore_more_indentation", templates);
136    }
137
138    #[test]
139    fn ignore_inner_template() {
140        use insta::assert_debug_snapshot_matches;
141        let templates = parse("main =\n    main =\n        x\n    ----\n----").unwrap();
142
143        assert_debug_snapshot_matches!("template.ignore_inner_template", templates);
144    }
145
146    #[test]
147    fn invalid_start() {
148        assert!(parse("main\n  content\n--").is_err())
149    }
150
151    #[test]
152    fn invalid_end() {
153        assert!(parse("main =\n content\n").is_err())
154    }
155}