]>
Commit | Line | Data |
---|---|---|
8bb4bdeb XL |
1 | use std::collections::{HashMap, BTreeMap, VecDeque}; |
2 | use std::error; | |
3 | use std::fmt; | |
4 | use std::rc::Rc; | |
5 | use std::io::Write; | |
6 | use std::io::Error as IOError; | |
7 | ||
041b39d2 XL |
8 | use serde::Serialize; |
9 | use serde_json::value::{Value as Json}; | |
8bb4bdeb XL |
10 | |
11 | use template::{Template, TemplateElement, Parameter, HelperTemplate, TemplateMapping, BlockParam, | |
12 | Directive as DirectiveTemplate}; | |
13 | use template::TemplateElement::*; | |
14 | use registry::Registry; | |
15 | use context::{Context, JsonRender}; | |
16 | use helpers::HelperDef; | |
17 | use support::str::StringWriter; | |
18 | #[cfg(not(feature="partial_legacy"))] | |
19 | use partial; | |
20 | ||
21 | /// Error when rendering data on template. | |
22 | #[derive(Debug, Clone)] | |
23 | pub struct RenderError { | |
24 | pub desc: String, | |
25 | pub template_name: Option<String>, | |
26 | pub line_no: Option<usize>, | |
27 | pub column_no: Option<usize>, | |
28 | } | |
29 | ||
30 | impl fmt::Display for RenderError { | |
31 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { | |
32 | match (self.line_no, self.column_no) { | |
33 | (Some(line), Some(col)) => { | |
34 | write!(f, | |
35 | "Error rendering \"{}\" line {}, col {}: {}", | |
36 | self.template_name.as_ref().unwrap_or(&"Unnamed template".to_owned()), | |
37 | line, | |
38 | col, | |
39 | self.desc) | |
40 | } | |
41 | _ => write!(f, "{}", self.desc), | |
42 | } | |
43 | ||
44 | } | |
45 | } | |
46 | ||
47 | impl error::Error for RenderError { | |
48 | fn description(&self) -> &str { | |
49 | &self.desc[..] | |
50 | } | |
51 | } | |
52 | ||
53 | impl From<IOError> for RenderError { | |
54 | fn from(_: IOError) -> RenderError { | |
55 | RenderError::new("IO Error") | |
56 | } | |
57 | } | |
58 | ||
59 | impl RenderError { | |
60 | pub fn new<T: AsRef<str>>(desc: T) -> RenderError { | |
61 | RenderError { | |
62 | desc: desc.as_ref().to_owned(), | |
63 | template_name: None, | |
64 | line_no: None, | |
65 | column_no: None, | |
66 | } | |
67 | } | |
68 | } | |
69 | ||
70 | /// The context of a render call | |
71 | /// | |
72 | /// this context stores information of a render and a writer where generated | |
73 | /// content is written to. | |
74 | /// | |
75 | pub struct RenderContext<'a> { | |
76 | partials: HashMap<String, Template>, | |
77 | path: String, | |
78 | local_path_root: VecDeque<String>, | |
79 | local_variables: HashMap<String, Json>, | |
80 | local_helpers: &'a mut HashMap<String, Rc<Box<HelperDef + 'static>>>, | |
81 | default_var: Json, | |
82 | block_context: VecDeque<Context>, | |
83 | /// the context | |
84 | context: &'a mut Context, | |
85 | /// the `Write` where page is generated | |
86 | pub writer: &'a mut Write, | |
87 | /// current template name | |
88 | pub current_template: Option<String>, | |
89 | /// root template name | |
90 | pub root_template: Option<String>, | |
91 | pub disable_escape: bool, | |
92 | } | |
93 | ||
94 | impl<'a> RenderContext<'a> { | |
95 | /// Create a render context from a `Write` | |
96 | pub fn new(ctx: &'a mut Context, | |
97 | local_helpers: &'a mut HashMap<String, Rc<Box<HelperDef + 'static>>>, | |
98 | w: &'a mut Write) | |
99 | -> RenderContext<'a> { | |
100 | RenderContext { | |
101 | partials: HashMap::new(), | |
102 | path: ".".to_string(), | |
103 | local_path_root: VecDeque::new(), | |
104 | local_variables: HashMap::new(), | |
105 | local_helpers: local_helpers, | |
106 | default_var: Json::Null, | |
107 | block_context: VecDeque::new(), | |
108 | context: ctx, | |
109 | writer: w, | |
110 | current_template: None, | |
111 | root_template: None, | |
112 | disable_escape: false, | |
113 | } | |
114 | } | |
115 | ||
116 | pub fn derive(&mut self) -> RenderContext { | |
117 | RenderContext { | |
118 | partials: self.partials.clone(), | |
119 | path: self.path.clone(), | |
120 | local_path_root: self.local_path_root.clone(), | |
121 | local_variables: self.local_variables.clone(), | |
122 | current_template: self.current_template.clone(), | |
123 | root_template: self.root_template.clone(), | |
124 | default_var: self.default_var.clone(), | |
125 | block_context: self.block_context.clone(), | |
126 | ||
127 | disable_escape: self.disable_escape, | |
128 | local_helpers: self.local_helpers, | |
129 | context: self.context, | |
130 | writer: self.writer, | |
131 | } | |
132 | } | |
133 | ||
134 | pub fn get_partial(&self, name: &str) -> Option<Template> { | |
135 | self.partials.get(name).map(|t| t.clone()) | |
136 | } | |
137 | ||
138 | pub fn set_partial(&mut self, name: String, result: Template) { | |
139 | self.partials.insert(name, result); | |
140 | } | |
141 | ||
142 | pub fn get_path(&self) -> &String { | |
143 | &self.path | |
144 | } | |
145 | ||
146 | pub fn set_path(&mut self, path: String) { | |
147 | self.path = path; | |
148 | } | |
149 | ||
150 | pub fn get_local_path_root(&self) -> &VecDeque<String> { | |
151 | &self.local_path_root | |
152 | } | |
153 | ||
154 | pub fn push_local_path_root(&mut self, path: String) { | |
155 | self.local_path_root.push_front(path) | |
156 | } | |
157 | ||
158 | pub fn pop_local_path_root(&mut self) { | |
159 | self.local_path_root.pop_front(); | |
160 | } | |
161 | ||
162 | pub fn set_local_var(&mut self, name: String, value: Json) { | |
163 | self.local_variables.insert(name, value); | |
164 | } | |
165 | ||
166 | pub fn clear_local_vars(&mut self) { | |
167 | self.local_variables.clear(); | |
168 | } | |
169 | ||
170 | pub fn promote_local_vars(&mut self) { | |
171 | let mut new_map: HashMap<String, Json> = HashMap::new(); | |
172 | for key in self.local_variables.keys() { | |
173 | let mut new_key = String::new(); | |
174 | new_key.push_str("@../"); | |
175 | new_key.push_str(&key[1..]); | |
176 | ||
7cac9316 XL |
177 | let v = self.local_variables |
178 | .get(key) | |
179 | .unwrap() | |
180 | .clone(); | |
8bb4bdeb XL |
181 | new_map.insert(new_key, v); |
182 | } | |
183 | self.local_variables = new_map; | |
184 | } | |
185 | ||
186 | pub fn demote_local_vars(&mut self) { | |
187 | let mut new_map: HashMap<String, Json> = HashMap::new(); | |
188 | for key in self.local_variables.keys() { | |
189 | if key.starts_with("@../") { | |
190 | let mut new_key = String::new(); | |
191 | new_key.push('@'); | |
192 | new_key.push_str(&key[4..]); | |
193 | ||
7cac9316 XL |
194 | let v = self.local_variables |
195 | .get(key) | |
196 | .unwrap() | |
197 | .clone(); | |
8bb4bdeb XL |
198 | new_map.insert(new_key, v); |
199 | } | |
200 | } | |
201 | self.local_variables = new_map; | |
202 | } | |
203 | ||
204 | pub fn get_local_var(&self, name: &String) -> Option<&Json> { | |
205 | self.local_variables.get(name) | |
206 | } | |
207 | ||
208 | pub fn writer(&mut self) -> &mut Write { | |
209 | self.writer | |
210 | } | |
211 | ||
212 | pub fn push_block_context<T>(&mut self, ctx: &T) | |
041b39d2 | 213 | where T: Serialize |
8bb4bdeb XL |
214 | { |
215 | self.block_context.push_front(Context::wraps(ctx)); | |
216 | } | |
217 | ||
218 | pub fn pop_block_context(&mut self) { | |
219 | self.block_context.pop_front(); | |
220 | } | |
221 | ||
222 | pub fn evaluate_in_block_context(&self, local_path: &str) -> Option<&Json> { | |
223 | for bc in self.block_context.iter() { | |
224 | let v = bc.navigate(".", &self.local_path_root, local_path); | |
225 | if !v.is_null() { | |
226 | return Some(v); | |
227 | } | |
228 | } | |
229 | ||
230 | None | |
231 | } | |
232 | ||
233 | pub fn is_current_template(&self, p: &str) -> bool { | |
7cac9316 XL |
234 | self.current_template |
235 | .as_ref() | |
236 | .map(|s| s == p) | |
237 | .unwrap_or(false) | |
8bb4bdeb XL |
238 | } |
239 | ||
240 | pub fn context(&self) -> &Context { | |
241 | self.context | |
242 | } | |
243 | ||
244 | pub fn context_mut(&mut self) -> &mut Context { | |
245 | self.context | |
246 | } | |
247 | ||
248 | pub fn register_local_helper(&mut self, | |
249 | name: &str, | |
250 | def: Box<HelperDef + 'static>) | |
251 | -> Option<Rc<Box<HelperDef + 'static>>> { | |
252 | self.local_helpers.insert(name.to_string(), Rc::new(def)) | |
253 | } | |
254 | ||
255 | pub fn unregister_local_helper(&mut self, name: &str) { | |
256 | self.local_helpers.remove(name); | |
257 | } | |
258 | ||
259 | pub fn get_local_helper(&self, name: &str) -> Option<Rc<Box<HelperDef + 'static>>> { | |
260 | self.local_helpers.get(name).map(|r| r.clone()) | |
261 | } | |
262 | } | |
263 | ||
264 | impl<'a> fmt::Debug for RenderContext<'a> { | |
265 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { | |
266 | write!(f, | |
267 | "partials: {:?}, path: {:?}, local_variables: {:?}, current_template: {:?}, \ | |
268 | root_template: {:?}, disable_escape: {:?}, local_path_root: {:?}", | |
269 | self.partials, | |
270 | self.path, | |
271 | self.local_variables, | |
272 | self.current_template, | |
273 | self.root_template, | |
274 | self.disable_escape, | |
275 | self.local_path_root) | |
276 | } | |
277 | } | |
278 | ||
279 | /// Json wrapper that holds the Json value and reference path information | |
280 | /// | |
281 | #[derive(Debug)] | |
282 | pub struct ContextJson { | |
283 | path: Option<String>, | |
284 | value: Json, | |
285 | } | |
286 | ||
287 | impl ContextJson { | |
288 | /// Returns relative path when the value is referenced | |
289 | /// If the value is from a literal, the path is `None` | |
290 | pub fn path(&self) -> Option<&String> { | |
291 | self.path.as_ref() | |
292 | } | |
293 | ||
294 | /// Return root level of this path if any | |
295 | pub fn path_root(&self) -> Option<&str> { | |
296 | self.path.as_ref().and_then(|p| p.split(|c| c == '.' || c == '/').nth(0)) | |
297 | } | |
298 | ||
299 | /// Returns the value | |
300 | pub fn value(&self) -> &Json { | |
301 | &self.value | |
302 | } | |
303 | } | |
304 | ||
305 | /// Render-time Helper data when using in a helper definition | |
306 | pub struct Helper<'a> { | |
307 | name: &'a str, | |
308 | params: Vec<ContextJson>, | |
309 | hash: BTreeMap<String, ContextJson>, | |
310 | block_param: &'a Option<BlockParam>, | |
311 | template: &'a Option<Template>, | |
312 | inverse: &'a Option<Template>, | |
313 | block: bool, | |
314 | } | |
315 | ||
316 | impl<'a, 'b> Helper<'a> { | |
317 | fn from_template(ht: &'a HelperTemplate, | |
318 | registry: &Registry, | |
319 | rc: &'b mut RenderContext) | |
320 | -> Result<Helper<'a>, RenderError> { | |
321 | let mut evaluated_params = Vec::new(); | |
322 | for p in ht.params.iter() { | |
323 | let r = try!(p.expand(registry, rc)); | |
324 | evaluated_params.push(r); | |
325 | } | |
326 | ||
327 | let mut evaluated_hash = BTreeMap::new(); | |
328 | for (k, p) in ht.hash.iter() { | |
329 | let r = try!(p.expand(registry, rc)); | |
330 | evaluated_hash.insert(k.clone(), r); | |
331 | } | |
332 | ||
333 | Ok(Helper { | |
7cac9316 XL |
334 | name: &ht.name, |
335 | params: evaluated_params, | |
336 | hash: evaluated_hash, | |
337 | block_param: &ht.block_param, | |
338 | template: &ht.template, | |
339 | inverse: &ht.inverse, | |
340 | block: ht.block, | |
341 | }) | |
8bb4bdeb XL |
342 | } |
343 | ||
344 | /// Returns helper name | |
345 | pub fn name(&self) -> &str { | |
346 | &self.name | |
347 | } | |
348 | ||
349 | /// Returns all helper params, resolved within the context | |
350 | pub fn params(&self) -> &Vec<ContextJson> { | |
351 | &self.params | |
352 | } | |
353 | ||
354 | /// Returns nth helper param, resolved within the context. | |
355 | /// | |
356 | /// ## Example | |
357 | /// | |
358 | /// To get the first param in `{{my_helper abc}}` or `{{my_helper 2}}`, | |
359 | /// use `h.param(0)` in helper definition. | |
360 | /// Variable `abc` is auto resolved in current context. | |
361 | /// | |
362 | /// ``` | |
363 | /// use handlebars::*; | |
364 | /// | |
365 | /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> { | |
366 | /// let v = h.param(0).map(|v| v.value()).unwrap(); | |
367 | /// // .. | |
368 | /// Ok(()) | |
369 | /// } | |
370 | /// ``` | |
371 | pub fn param(&self, idx: usize) -> Option<&ContextJson> { | |
372 | self.params.get(idx) | |
373 | } | |
374 | ||
375 | /// Returns hash, resolved within the context | |
376 | pub fn hash(&self) -> &BTreeMap<String, ContextJson> { | |
377 | &self.hash | |
378 | } | |
379 | ||
380 | /// Return hash value of a given key, resolved within the context | |
381 | /// | |
382 | /// ## Example | |
383 | /// | |
384 | /// To get the first param in `{{my_helper v=abc}}` or `{{my_helper v=2}}`, | |
385 | /// use `h.hash_get("v")` in helper definition. | |
386 | /// Variable `abc` is auto resolved in current context. | |
387 | /// | |
388 | /// ``` | |
389 | /// use handlebars::*; | |
390 | /// | |
391 | /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> { | |
392 | /// let v = h.hash_get("v").map(|v| v.value()).unwrap(); | |
393 | /// // .. | |
394 | /// Ok(()) | |
395 | /// } | |
396 | /// ``` | |
397 | pub fn hash_get(&self, key: &str) -> Option<&ContextJson> { | |
398 | self.hash.get(key) | |
399 | } | |
400 | ||
401 | /// Returns the default inner template if the helper is a block helper. | |
402 | /// | |
403 | /// Typically you will render the template via: `template.render(registry, render_context)` | |
404 | /// | |
405 | pub fn template(&self) -> Option<&Template> { | |
406 | (*self.template).as_ref().map(|t| t) | |
407 | } | |
408 | ||
409 | /// Returns the template of `else` branch if any | |
410 | pub fn inverse(&self) -> Option<&Template> { | |
411 | (*self.inverse).as_ref().map(|t| t) | |
412 | } | |
413 | ||
414 | /// Returns if the helper is a block one `{{#helper}}{{/helper}}` or not `{{helper 123}}` | |
415 | pub fn is_block(&self) -> bool { | |
416 | self.block | |
417 | } | |
418 | ||
419 | /// Returns block param if any | |
420 | pub fn block_param(&self) -> Option<&str> { | |
421 | if let Some(BlockParam::Single(Parameter::Name(ref s))) = *self.block_param { | |
422 | Some(s) | |
423 | } else { | |
424 | None | |
425 | } | |
426 | } | |
427 | ||
428 | /// Return block param pair (for example |key, val|) if any | |
429 | pub fn block_param_pair(&self) -> Option<(&str, &str)> { | |
430 | if let Some(BlockParam::Pair((Parameter::Name(ref s1), Parameter::Name(ref s2)))) = | |
7cac9316 | 431 | *self.block_param { |
8bb4bdeb XL |
432 | Some((s1, s2)) |
433 | } else { | |
434 | None | |
435 | } | |
436 | } | |
437 | } | |
438 | ||
439 | /// Render-time Decorator data when using in a decorator definition | |
440 | pub struct Directive<'a> { | |
441 | name: String, | |
442 | params: Vec<ContextJson>, | |
443 | hash: BTreeMap<String, ContextJson>, | |
444 | template: &'a Option<Template>, | |
445 | } | |
446 | ||
447 | impl<'a, 'b> Directive<'a> { | |
448 | fn from_template(dt: &'a DirectiveTemplate, | |
449 | registry: &Registry, | |
450 | rc: &'b mut RenderContext) | |
451 | -> Result<Directive<'a>, RenderError> { | |
452 | let name = try!(dt.name.expand_as_name(registry, rc)); | |
453 | ||
454 | let mut evaluated_params = Vec::new(); | |
455 | for p in dt.params.iter() { | |
456 | let r = try!(p.expand(registry, rc)); | |
457 | evaluated_params.push(r); | |
458 | } | |
459 | ||
460 | let mut evaluated_hash = BTreeMap::new(); | |
461 | for (k, p) in dt.hash.iter() { | |
462 | let r = try!(p.expand(registry, rc)); | |
463 | evaluated_hash.insert(k.clone(), r); | |
464 | } | |
465 | ||
466 | Ok(Directive { | |
7cac9316 XL |
467 | name: name, |
468 | params: evaluated_params, | |
469 | hash: evaluated_hash, | |
470 | template: &dt.template, | |
471 | }) | |
8bb4bdeb XL |
472 | } |
473 | ||
474 | /// Returns helper name | |
475 | pub fn name(&self) -> &str { | |
476 | &self.name | |
477 | } | |
478 | ||
479 | /// Returns all helper params, resolved within the context | |
480 | pub fn params(&self) -> &Vec<ContextJson> { | |
481 | &self.params | |
482 | } | |
483 | ||
484 | /// Returns nth helper param, resolved within the context | |
485 | pub fn param(&self, idx: usize) -> Option<&ContextJson> { | |
486 | self.params.get(idx) | |
487 | } | |
488 | ||
489 | /// Returns hash, resolved within the context | |
490 | pub fn hash(&self) -> &BTreeMap<String, ContextJson> { | |
491 | &self.hash | |
492 | } | |
493 | ||
494 | /// Return hash value of a given key, resolved within the context | |
495 | pub fn hash_get(&self, key: &str) -> Option<&ContextJson> { | |
496 | self.hash.get(key) | |
497 | } | |
498 | ||
499 | /// Returns the default inner template if any | |
500 | pub fn template(&self) -> Option<&Template> { | |
501 | (*self.template).as_ref().map(|t| t) | |
502 | } | |
503 | } | |
504 | ||
505 | /// Render trait | |
506 | pub trait Renderable { | |
507 | /// render into RenderContext's `writer` | |
508 | fn render(&self, registry: &Registry, rc: &mut RenderContext) -> Result<(), RenderError>; | |
509 | ||
510 | /// render into string | |
511 | fn renders(&self, registry: &Registry, rc: &mut RenderContext) -> Result<String, RenderError> { | |
512 | let mut sw = StringWriter::new(); | |
513 | { | |
514 | let mut local_rc = rc.derive(); | |
515 | local_rc.writer = &mut sw; | |
516 | try!(self.render(registry, &mut local_rc)); | |
517 | } | |
518 | ||
519 | let s = sw.to_string(); | |
520 | Ok(s) | |
521 | } | |
522 | } | |
523 | ||
524 | /// Evaluate directive or decorator | |
525 | pub trait Evaluable { | |
526 | fn eval(&self, registry: &Registry, rc: &mut RenderContext) -> Result<(), RenderError>; | |
527 | } | |
528 | ||
529 | ||
530 | impl Parameter { | |
531 | pub fn expand_as_name(&self, | |
532 | registry: &Registry, | |
533 | rc: &mut RenderContext) | |
534 | -> Result<String, RenderError> { | |
535 | match self { | |
536 | &Parameter::Name(ref name) => Ok(name.to_owned()), | |
537 | &Parameter::Subexpression(ref t) => { | |
538 | let mut local_writer = StringWriter::new(); | |
539 | { | |
540 | let mut local_rc = rc.derive(); | |
541 | local_rc.writer = &mut local_writer; | |
542 | // disable html escape for subexpression | |
543 | local_rc.disable_escape = true; | |
544 | ||
545 | try!(t.as_template().render(registry, &mut local_rc)); | |
546 | } | |
547 | ||
548 | Ok(local_writer.to_string()) | |
549 | } | |
550 | &Parameter::Literal(ref j) => Ok(j.render()), | |
551 | } | |
552 | } | |
553 | ||
554 | pub fn expand(&self, | |
555 | registry: &Registry, | |
556 | rc: &mut RenderContext) | |
557 | -> Result<ContextJson, RenderError> { | |
558 | match self { | |
559 | &Parameter::Name(ref name) => { | |
560 | Ok(rc.get_local_var(&name).map_or_else(|| { | |
561 | ContextJson { | |
562 | path: Some(name.to_owned()), | |
563 | value: rc.evaluate_in_block_context(name).map_or_else(|| {rc.context().navigate(rc.get_path(), rc.get_local_path_root(), name).clone()}, |v| v.clone()), | |
564 | } | |
565 | ||
566 | }, | |
567 | |v| { | |
568 | ContextJson { | |
569 | path: None, | |
570 | value: v.clone(), | |
571 | } | |
572 | })) | |
573 | } | |
574 | &Parameter::Literal(ref j) => { | |
575 | Ok(ContextJson { | |
7cac9316 XL |
576 | path: None, |
577 | value: j.clone(), | |
578 | }) | |
8bb4bdeb XL |
579 | } |
580 | &Parameter::Subexpression(_) => { | |
581 | let text_value = try!(self.expand_as_name(registry, rc)); | |
582 | Ok(ContextJson { | |
7cac9316 XL |
583 | path: None, |
584 | value: Json::String(text_value), | |
585 | }) | |
8bb4bdeb XL |
586 | } |
587 | } | |
588 | } | |
589 | } | |
590 | ||
591 | impl Renderable for Template { | |
592 | fn render(&self, registry: &Registry, rc: &mut RenderContext) -> Result<(), RenderError> { | |
593 | rc.current_template = self.name.clone(); | |
594 | let iter = self.elements.iter(); | |
595 | let mut idx = 0; | |
596 | for t in iter { | |
597 | try!(t.render(registry, rc).map_err(|mut e| { | |
598 | // add line/col number if the template has mapping data | |
599 | if e.line_no.is_none() { | |
600 | if let Some(ref mapping) = self.mapping { | |
601 | if let Some(&TemplateMapping(line, col)) = mapping.get(idx) { | |
602 | e.line_no = Some(line); | |
603 | e.column_no = Some(col); | |
604 | ||
605 | } | |
606 | } | |
607 | } | |
608 | ||
609 | if e.template_name.is_none() { | |
610 | e.template_name = self.name.clone(); | |
611 | } | |
612 | ||
613 | e | |
614 | })); | |
615 | idx = idx + 1; | |
616 | } | |
617 | Ok(()) | |
618 | } | |
619 | } | |
620 | ||
621 | impl Evaluable for Template { | |
622 | fn eval(&self, registry: &Registry, rc: &mut RenderContext) -> Result<(), RenderError> { | |
623 | let iter = self.elements.iter(); | |
624 | let mut idx = 0; | |
625 | for t in iter { | |
626 | try!(t.eval(registry, rc).map_err(|mut e| { | |
627 | if e.line_no.is_none() { | |
628 | if let Some(ref mapping) = self.mapping { | |
629 | if let Some(&TemplateMapping(line, col)) = mapping.get(idx) { | |
630 | e.line_no = Some(line); | |
631 | e.column_no = Some(col); | |
632 | ||
633 | } | |
634 | } | |
635 | } | |
636 | ||
637 | e.template_name = self.name.clone(); | |
638 | e | |
639 | })); | |
640 | idx = idx + 1; | |
641 | } | |
642 | Ok(()) | |
643 | } | |
644 | } | |
645 | ||
646 | impl Renderable for TemplateElement { | |
647 | fn render(&self, registry: &Registry, rc: &mut RenderContext) -> Result<(), RenderError> { | |
648 | debug!("rendering {:?}, {:?}", self, rc); | |
649 | match *self { | |
650 | RawString(ref v) => { | |
651 | try!(rc.writer.write(v.clone().into_bytes().as_ref())); | |
652 | Ok(()) | |
653 | } | |
654 | Expression(ref v) => { | |
655 | let context_json = try!(v.expand(registry, rc)); | |
656 | let rendered = context_json.value.render(); | |
657 | ||
658 | let output = if !rc.disable_escape { | |
659 | registry.get_escape_fn()(&rendered) | |
660 | } else { | |
661 | rendered | |
662 | }; | |
663 | try!(rc.writer.write(output.into_bytes().as_ref())); | |
664 | Ok(()) | |
665 | } | |
666 | HTMLExpression(ref v) => { | |
667 | let context_json = try!(v.expand(registry, rc)); | |
668 | let rendered = context_json.value.render(); | |
669 | try!(rc.writer.write(rendered.into_bytes().as_ref())); | |
670 | Ok(()) | |
671 | } | |
7cac9316 XL |
672 | HelperExpression(ref ht) | |
673 | HelperBlock(ref ht) => { | |
8bb4bdeb XL |
674 | let helper = try!(Helper::from_template(ht, registry, rc)); |
675 | if let Some(ref d) = rc.get_local_helper(&ht.name) { | |
676 | d.call(&helper, registry, rc) | |
677 | } else { | |
678 | registry.get_helper(&ht.name) | |
7cac9316 XL |
679 | .or(registry.get_helper(if ht.block { |
680 | "blockHelperMissing" | |
681 | } else { | |
682 | "helperMissing" | |
683 | })) | |
684 | .ok_or(RenderError::new(format!("Helper not defined: {:?}", ht.name))) | |
685 | .and_then(|d| d.call(&helper, registry, rc)) | |
8bb4bdeb XL |
686 | } |
687 | } | |
7cac9316 XL |
688 | DirectiveExpression(_) | |
689 | DirectiveBlock(_) => self.eval(registry, rc), | |
690 | #[cfg(not(feature="partial_legacy"))] | |
691 | PartialExpression(ref dt) | PartialBlock(ref dt) => { | |
692 | Directive::from_template(dt, registry, rc) | |
693 | .and_then(|di| partial::expand_partial(&di, registry, rc)) | |
8bb4bdeb XL |
694 | } |
695 | _ => Ok(()), | |
696 | } | |
697 | } | |
698 | } | |
699 | ||
700 | impl Evaluable for TemplateElement { | |
701 | fn eval(&self, registry: &Registry, rc: &mut RenderContext) -> Result<(), RenderError> { | |
702 | match *self { | |
7cac9316 XL |
703 | DirectiveExpression(ref dt) | |
704 | DirectiveBlock(ref dt) => { | |
8bb4bdeb XL |
705 | Directive::from_template(dt, registry, rc).and_then(|di| { |
706 | match registry.get_decorator(&di.name) { | |
707 | Some(d) => (**d).call(&di, registry, rc), | |
708 | None => { | |
709 | Err(RenderError::new(format!("Directive not defined: {:?}", dt.name))) | |
710 | } | |
711 | } | |
712 | }) | |
713 | } | |
8bb4bdeb XL |
714 | _ => Ok(()), |
715 | } | |
716 | } | |
717 | } | |
718 | ||
719 | #[test] | |
720 | fn test_raw_string() { | |
721 | let r = Registry::new(); | |
722 | let mut sw = StringWriter::new(); | |
723 | let mut ctx = Context::null(); | |
724 | let mut hlps = HashMap::new(); | |
725 | { | |
726 | let mut rc = RenderContext::new(&mut ctx, &mut hlps, &mut sw); | |
727 | let raw_string = RawString("<h1>hello world</h1>".to_string()); | |
728 | ||
729 | raw_string.render(&r, &mut rc).ok().unwrap(); | |
730 | } | |
731 | assert_eq!(sw.to_string(), "<h1>hello world</h1>".to_string()); | |
732 | } | |
733 | ||
734 | #[test] | |
735 | fn test_expression() { | |
736 | let r = Registry::new(); | |
737 | let mut sw = StringWriter::new(); | |
738 | let mut hlps = HashMap::new(); | |
739 | let mut m: HashMap<String, String> = HashMap::new(); | |
740 | let value = "<p></p>".to_string(); | |
741 | m.insert("hello".to_string(), value); | |
742 | let mut ctx = Context::wraps(&m); | |
743 | { | |
744 | ||
745 | let mut rc = RenderContext::new(&mut ctx, &mut hlps, &mut sw); | |
746 | let element = Expression(Parameter::Name("hello".into())); | |
747 | ||
748 | element.render(&r, &mut rc).ok().unwrap(); | |
749 | } | |
750 | ||
751 | assert_eq!(sw.to_string(), "<p></p>".to_string()); | |
752 | } | |
753 | ||
754 | #[test] | |
755 | fn test_html_expression() { | |
756 | let r = Registry::new(); | |
757 | let mut sw = StringWriter::new(); | |
758 | let mut hlps = HashMap::new(); | |
759 | let mut m: HashMap<String, String> = HashMap::new(); | |
760 | let value = "world"; | |
761 | m.insert("hello".to_string(), value.to_string()); | |
762 | let mut ctx = Context::wraps(&m); | |
763 | { | |
764 | ||
765 | let mut rc = RenderContext::new(&mut ctx, &mut hlps, &mut sw); | |
766 | let element = HTMLExpression(Parameter::Name("hello".into())); | |
767 | element.render(&r, &mut rc).ok().unwrap(); | |
768 | } | |
769 | ||
770 | assert_eq!(sw.to_string(), value.to_string()); | |
771 | } | |
772 | ||
773 | #[test] | |
774 | fn test_template() { | |
775 | let r = Registry::new(); | |
776 | let mut sw = StringWriter::new(); | |
777 | let mut hlps = HashMap::new(); | |
778 | let mut m: HashMap<String, String> = HashMap::new(); | |
779 | let value = "world".to_string(); | |
780 | m.insert("hello".to_string(), value); | |
781 | let mut ctx = Context::wraps(&m); | |
782 | ||
783 | { | |
784 | ||
785 | ||
786 | let mut rc = RenderContext::new(&mut ctx, &mut hlps, &mut sw); | |
787 | let mut elements: Vec<TemplateElement> = Vec::new(); | |
788 | ||
789 | let e1 = RawString("<h1>".to_string()); | |
790 | elements.push(e1); | |
791 | ||
792 | let e2 = Expression(Parameter::Name("hello".into())); | |
793 | elements.push(e2); | |
794 | ||
795 | let e3 = RawString("</h1>".to_string()); | |
796 | elements.push(e3); | |
797 | ||
798 | let e4 = Comment("".to_string()); | |
799 | elements.push(e4); | |
800 | ||
801 | let template = Template { | |
802 | elements: elements, | |
803 | name: None, | |
804 | mapping: None, | |
805 | }; | |
806 | template.render(&r, &mut rc).ok().unwrap(); | |
807 | } | |
808 | ||
809 | assert_eq!(sw.to_string(), "<h1>world</h1>".to_string()); | |
810 | } | |
811 | ||
812 | #[test] | |
8bb4bdeb | 813 | fn test_render_context_promotion_and_demotion() { |
041b39d2 | 814 | use context::to_json; |
8bb4bdeb XL |
815 | let mut sw = StringWriter::new(); |
816 | let mut ctx = Context::null(); | |
817 | let mut hlps = HashMap::new(); | |
818 | ||
819 | let mut render_context = RenderContext::new(&mut ctx, &mut hlps, &mut sw); | |
820 | ||
041b39d2 | 821 | render_context.set_local_var("@index".to_string(), to_json(&0)); |
8bb4bdeb XL |
822 | |
823 | render_context.promote_local_vars(); | |
824 | ||
825 | assert_eq!(render_context.get_local_var(&"@../index".to_string()).unwrap(), | |
041b39d2 | 826 | &to_json(&0)); |
8bb4bdeb XL |
827 | |
828 | render_context.demote_local_vars(); | |
829 | ||
830 | assert_eq!(render_context.get_local_var(&"@index".to_string()).unwrap(), | |
041b39d2 | 831 | &to_json(&0)); |
8bb4bdeb XL |
832 | } |
833 | ||
834 | #[test] | |
835 | fn test_render_subexpression() { | |
836 | let r = Registry::new(); | |
837 | let mut sw = StringWriter::new(); | |
838 | ||
839 | let mut m: HashMap<String, String> = HashMap::new(); | |
840 | m.insert("hello".to_string(), "world".to_string()); | |
841 | m.insert("world".to_string(), "nice".to_string()); | |
842 | m.insert("const".to_string(), "truthy".to_string()); | |
843 | ||
844 | { | |
845 | if let Err(e) = r.template_renderw("<h1>{{#if (const)}}{{(hello)}}{{/if}}</h1>", | |
846 | &m, | |
847 | &mut sw) { | |
848 | panic!("{}", e); | |
849 | } | |
850 | } | |
851 | ||
852 | assert_eq!(sw.to_string(), "<h1>world</h1>".to_string()); | |
853 | } | |
854 | ||
855 | #[test] | |
856 | fn test_render_subexpression_issue_115() { | |
857 | let mut r = Registry::new(); | |
858 | r.register_helper("format", | |
859 | Box::new(|h: &Helper, | |
860 | _: &Registry, | |
861 | rc: &mut RenderContext| | |
862 | -> Result<(), RenderError> { | |
7cac9316 XL |
863 | rc.writer |
864 | .write(format!("{}", | |
865 | h.param(0) | |
866 | .unwrap() | |
867 | .value() | |
868 | .render()) | |
869 | .into_bytes() | |
870 | .as_ref()) | |
871 | .map(|_| ()) | |
872 | .map_err(RenderError::from) | |
873 | })); | |
8bb4bdeb XL |
874 | |
875 | let mut sw = StringWriter::new(); | |
876 | let mut m: HashMap<String, String> = HashMap::new(); | |
877 | m.insert("a".to_string(), "123".to_string()); | |
878 | ||
879 | { | |
880 | if let Err(e) = r.template_renderw("{{format (format a)}}", &m, &mut sw) { | |
881 | panic!("{}", e); | |
882 | } | |
883 | } | |
884 | ||
885 | assert_eq!(sw.to_string(), "123".to_string()); | |
886 | } | |
887 | ||
888 | #[test] | |
889 | fn test_render_error_line_no() { | |
890 | let mut r = Registry::new(); | |
891 | let m: HashMap<String, String> = HashMap::new(); | |
892 | ||
893 | let name = "invalid_template"; | |
894 | assert!(r.register_template_string(name, "<h1>\n{{#if true}}\n {{#each}}{{/each}}\n{{/if}}") | |
7cac9316 | 895 | .is_ok()); |
8bb4bdeb XL |
896 | |
897 | if let Err(e) = r.render(name, &m) { | |
898 | assert_eq!(e.line_no.unwrap(), 3); | |
899 | assert_eq!(e.column_no.unwrap(), 3); | |
900 | assert_eq!(e.template_name, Some(name.to_owned())); | |
901 | } else { | |
902 | panic!("Error expected"); | |
903 | } | |
904 | } | |
7cac9316 XL |
905 | |
906 | #[test] | |
907 | #[cfg(not(feature="partial_legacy"))] | |
908 | fn test_partial_failback_render() { | |
909 | let mut r = Registry::new(); | |
910 | ||
911 | assert!(r.register_template_string("parent", "<html>{{> layout}}</html>").is_ok()); | |
912 | assert!(r.register_template_string("child", "{{#*inline \"layout\"}}content{{/inline}}{{#> parent}}{{> seg}}{{/parent}}").is_ok()); | |
913 | assert!(r.register_template_string("seg", "1234").is_ok()); | |
914 | ||
915 | let r = r.render("child", &true).expect("should work"); | |
916 | assert_eq!(r, "<html>content</html>"); | |
917 | } | |
041b39d2 XL |
918 | |
919 | #[test] | |
920 | fn test_key_with_slash() { | |
921 | let mut r = Registry::new(); | |
922 | ||
923 | assert!(r.register_template_string("t", "{{#each .}}{{@key}}: {{this}}\n{{/each}}").is_ok()); | |
924 | ||
925 | let r = r.render("t", &json!({ | |
926 | "/foo": "bar" | |
927 | })).expect("should work"); | |
928 | ||
929 | assert_eq!(r, "/foo: bar\n"); | |
930 | } |