rustache/
compiler.rs

1extern crate regex;
2
3use self::regex::Regex;
4use self::Token::*;
5
6// The compiler takes in a stringified template file or a string and
7// splits into a list of tokens to be processed by the parser.
8
9// Token represents the basic data source for different sections of
10// text provided within the template.  Raw tag values are stored
11// for use in lambdas.
12
13#[derive(Debug, PartialEq, Eq, Clone)]
14pub enum Token<'a> {
15    Text(&'a str), // (text)
16    Variable(&'a str, &'a str), // (name, tag)
17    OTag(&'a str, bool, &'a str), // (name, inverted, tag, whitespace)
18    CTag(&'a str, &'a str), // (name, tag, whitespace)
19    Raw(&'a str, &'a str), // (name, tag)
20    Partial(&'a str, &'a str), // (name, tag)
21    Comment,
22}
23
24// Entry point to the template compiler. It compiles a token list of
25// all applicable tags within a template to send to the parser.
26pub fn create_tokens<'a>(contents: &'a str) -> Vec<Token<'a>> {
27    let mut tokens: Vec<Token> = Vec::new();
28
29    // Close position and length are used to catch trailing characters afer last
30    // tag capture, or if no tags are present in the template.
31    let mut close_pos = 0;
32    let len = contents.len();
33
34    // (text)(whitespace)( (tag) )(whitespace)
35    let re =
36        Regex::new(r"(?s)(.*?)([ \t\r\n]*)(\{\{(\{?\S?\s*?[\w\.\s]*.*?\s*?\}?)\}\})([ \t\r\n]*)")
37            .unwrap();
38
39    // Grab all captures and process
40    for cap in re.captures_iter(contents) {
41        // Establish groups for tag capture, preventing lookup for each call
42        let preceding_text = cap.at(1).unwrap_or("");
43        let preceding_whitespace = cap.at(2).unwrap_or("");
44        let outer = cap.at(3).unwrap_or("");
45        let inner = cap.at(4).unwrap_or("");
46        let trailing_whitespace = cap.at(5).unwrap_or("");
47
48        // Grab closing index
49        let (_, c) = cap.pos(0).unwrap();
50
51        // Catch preceding text
52        if !preceding_text.is_empty() {
53            tokens.push(Text(preceding_text));
54        }
55
56        // Catch preceding whitespace
57        if !preceding_whitespace.is_empty() {
58            tokens.push(Text(preceding_whitespace));
59        }
60
61        // Advance last closing position and add captured token
62        close_pos = c;
63        add_token(inner, outer, &mut tokens);
64
65        // Catch trailing whitespace
66        if !trailing_whitespace.is_empty() {
67            tokens.push(Text(&trailing_whitespace));
68        }
69    }
70
71    // Catch trailing text
72    if close_pos < len {
73        tokens.push(Text(&contents[close_pos..]));
74    }
75
76    // Return
77    tokens
78}
79
80// Simple method for categorizing and adding appropriate token
81fn add_token<'a>(inner: &'a str, outer: &'a str, tokens: &mut Vec<Token<'a>>) {
82    match &inner[0..1] {
83        "!" => tokens.push(Comment),
84        "#" => tokens.push(OTag(inner[1..].trim(), false, outer)),
85        "/" => tokens.push(CTag(inner[1..].trim(), outer)),
86        "^" => tokens.push(OTag(inner[1..].trim(), true, outer)),
87        ">" => tokens.push(Partial(inner[1..].trim(), outer)),
88        "&" => tokens.push(Raw(inner[1..].trim(), outer)),
89        "{" => tokens.push(Raw(inner[1..inner.len() - 1].trim(), outer)),
90        _ => tokens.push(Variable(inner.trim(), outer)),
91    }
92}
93
94#[cfg(test)]
95mod compiler_tests {
96    use compiler;
97    use compiler::Token::{Text, Variable, OTag, CTag, Raw, Partial, Comment};
98
99    #[test]
100    fn test_one_char() {
101        let contents = "c";
102        let tokens = compiler::create_tokens(contents);
103        let expected = vec![Text("c")];
104
105        assert_eq!(expected, tokens);
106    }
107
108    #[test]
109    fn test_extended_dot_notation() {
110        let contents = "{{ test.test.test.test }}";
111        let tokens = compiler::create_tokens(contents);
112        let expected = vec![Variable("test.test.test.test", "{{ test.test.test.test }}")];
113
114        assert_eq!(expected, tokens);
115    }
116
117    #[test]
118    fn basic_compiler_test() {
119        let contents = "<div> <h1> {{ token }} {{{ unescaped }}} {{> partial }} </h1> </div>";
120        let tokens = compiler::create_tokens(contents);
121        let expected = vec![Text("<div> <h1>"),
122                            Text(" "),
123                            Variable("token", "{{ token }}"),
124                            Text(" "),
125                            Raw("unescaped", "{{{ unescaped }}}"),
126                            Text(" "),
127                            Partial("partial", "{{> partial }}"),
128                            Text(" "),
129                            Text("</h1> </div>")];
130
131        assert_eq!(expected, tokens);
132    }
133
134    #[test]
135    fn test_all_directives() {
136        let contents = "{{!comment}}{{#section}}{{/section}}{{^isection}}{{/isection}}{{>partial}}{{&unescaped}}{{value}}other \
137                        crap";
138        let tokens = compiler::create_tokens(contents);
139        let expected = vec![Comment,
140                            OTag("section", false, "{{#section}}"),
141                            CTag("section", "{{/section}}"),
142                            OTag("isection", true, "{{^isection}}"),
143                            CTag("isection", "{{/isection}}"),
144                            Partial("partial", "{{>partial}}"),
145                            Raw("unescaped", "{{&unescaped}}"),
146                            Variable("value", "{{value}}"),
147                            Text("other crap")];
148        assert_eq!(expected, tokens);
149    }
150
151    #[test]
152    fn test_missing_close_on_comment() {
153        let contents = "{{!comment";
154        let tokens = compiler::create_tokens(contents);
155        let expected = vec![Text("{{!comment")];
156        assert_eq!(expected, tokens);
157    }
158
159    #[test]
160    fn test_working_comment() {
161        let contents = "{{!comment}}";
162        let tokens = compiler::create_tokens(contents);
163        let expected = vec![Comment];
164        assert_eq!(expected, tokens);
165    }
166
167    #[test]
168    fn test_embedded_comment() {
169        let contents = "text {{!comment}} text";
170        let tokens = compiler::create_tokens(contents);
171        let expected = vec![Text("text"),
172                            Text(" "),
173                            Comment,
174                            Text(" "),
175                            Text("text"),
176                            ];
177        assert_eq!(expected, tokens);
178    }
179
180    #[test]
181    fn test_missing_close_on_section_close() {
182        let contents = "{{#section}}{{/section";
183        let tokens = compiler::create_tokens(contents);
184        let expected = vec![OTag("section", false, "{{#section}}"), Text("{{/section")];
185        assert_eq!(expected, tokens);
186    }
187
188    #[test]
189    fn test_working_section() {
190        let contents = "{{#section}}{{/section}}";
191        let tokens = compiler::create_tokens(contents);
192        let expected = vec![OTag("section", false, "{{#section}}"),
193                            CTag("section", "{{/section}}")];
194        assert_eq!(expected, tokens);
195    }
196
197    #[test]
198    fn test_missing_close_on_inverted_section_close() {
199        let contents = "{{^isection}}{{/isection";
200        let tokens = compiler::create_tokens(contents);
201        let expected = vec![OTag("isection", true, "{{^isection}}"), Text("{{/isection")];
202        assert_eq!(expected, tokens);
203    }
204
205    #[test]
206    fn test_missing_close_on_partial() {
207        let contents = "{{>partial";
208        let tokens = compiler::create_tokens(contents);
209        let expected = vec![Text("{{>partial")];
210        assert_eq!(expected, tokens);
211    }
212
213    #[test]
214    fn test_working_partial() {
215        let contents = "{{>partial}}";
216        let tokens = compiler::create_tokens(contents);
217        let expected = vec![Partial("partial", "{{>partial}}")];
218        assert_eq!(expected, tokens);
219    }
220
221    #[test]
222    fn test_missing_close_on_unescaped() {
223        let contents = "{{&unescaped";
224        let tokens = compiler::create_tokens(contents);
225        let expected = vec![Text("{{&unescaped")];
226        assert_eq!(expected, tokens);
227    }
228
229    #[test]
230    fn test_working_unescape() {
231        let contents = "{{&unescaped}}";
232        let tokens = compiler::create_tokens(contents);
233        let expected = vec![Raw("unescaped", "{{&unescaped}}")];
234        assert_eq!(expected, tokens);
235    }
236
237    #[test]
238    fn test_missing_close_on_partial_plus_unescaped() {
239        let contents = "{{>partial}}{{&unescaped";
240        let tokens = compiler::create_tokens(contents);
241        let expected = vec![Partial("partial", "{{>partial}}"), Text("{{&unescaped")];
242        assert_eq!(expected, tokens);
243    }
244
245    #[test]
246    fn test_missing_close_on_value() {
247        let contents = "{{value other crap";
248        let tokens = compiler::create_tokens(contents);
249        let expected = vec![Text("{{value other crap")];
250        assert_eq!(expected, tokens);
251    }
252
253    #[test]
254    fn test_bad_opens() {
255        let contents = "value}} other crap";
256        let tokens = compiler::create_tokens(contents);
257        let expected = vec![Text("value}} other crap")];
258        assert_eq!(expected, tokens);
259    }
260
261    #[test]
262    fn test_single_brace_open() {
263        let contents = "{value other crap";
264        let tokens = compiler::create_tokens(contents);
265        let expected = vec![Text("{value other crap")];
266        assert_eq!(expected, tokens);
267    }
268
269    #[test]
270    fn test_single_brace_close() {
271        let contents = "value} other crap";
272        let tokens = compiler::create_tokens(contents);
273        let expected = vec![Text("value} other crap")];
274        assert_eq!(expected, tokens);
275    }
276
277    #[test]
278    fn test_extending_text_across_newlines() {
279        let contents = "bar = \"{{ foo }}\"\nbaz = \"{{ quux }}\"";
280        let tokens = compiler::create_tokens(contents);
281        let expected = vec![Text("bar = \""),
282                            Variable("foo", "{{ foo }}"),
283                            Text("\"\nbaz = \""),
284                            Variable("quux", "{{ quux }}"),
285                            Text("\"")];
286
287        assert_eq!(expected, tokens);
288    }
289}