1extern crate regex;
2
3use self::regex::Regex;
4use self::Token::*;
5
6#[derive(Debug, PartialEq, Eq, Clone)]
14pub enum Token<'a> {
15 Text(&'a str), Variable(&'a str, &'a str), OTag(&'a str, bool, &'a str), CTag(&'a str, &'a str), Raw(&'a str, &'a str), Partial(&'a str, &'a str), Comment,
22}
23
24pub fn create_tokens<'a>(contents: &'a str) -> Vec<Token<'a>> {
27 let mut tokens: Vec<Token> = Vec::new();
28
29 let mut close_pos = 0;
32 let len = contents.len();
33
34 let re =
36 Regex::new(r"(?s)(.*?)([ \t\r\n]*)(\{\{(\{?\S?\s*?[\w\.\s]*.*?\s*?\}?)\}\})([ \t\r\n]*)")
37 .unwrap();
38
39 for cap in re.captures_iter(contents) {
41 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 let (_, c) = cap.pos(0).unwrap();
50
51 if !preceding_text.is_empty() {
53 tokens.push(Text(preceding_text));
54 }
55
56 if !preceding_whitespace.is_empty() {
58 tokens.push(Text(preceding_whitespace));
59 }
60
61 close_pos = c;
63 add_token(inner, outer, &mut tokens);
64
65 if !trailing_whitespace.is_empty() {
67 tokens.push(Text(&trailing_whitespace));
68 }
69 }
70
71 if close_pos < len {
73 tokens.push(Text(&contents[close_pos..]));
74 }
75
76 tokens
78}
79
80fn 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}