]> git.proxmox.com Git - rustc.git/blob - src/vendor/handlebars/src/registry.rs
New upstream version 1.29.0+dfsg1
[rustc.git] / src / vendor / handlebars / src / registry.rs
1 use std::collections::HashMap;
2 use std::io::prelude::*;
3 use std::fs::File;
4 use std::path::Path;
5 use std::fmt::{self, Debug, Formatter};
6
7 use serde::Serialize;
8
9 use regex::{Captures, Regex};
10
11 use template::Template;
12 use render::{RenderContext, Renderable};
13 use context::Context;
14 use helpers::{self, HelperDef};
15 use directives::{self, DirectiveDef};
16 use support::str::StringWriter;
17 use error::{RenderError, TemplateError, TemplateFileError, TemplateRenderError};
18
19 lazy_static!{
20 static ref DEFAULT_REPLACE: Regex = Regex::new(">|<|\"|&").unwrap();
21 }
22
23 /// This type represents an *escape fn*, that is a function who's purpose it is
24 /// to escape potentially problematic characters in a string.
25 ///
26 /// An *escape fn* is represented as a `Box` to avoid unnecessary type
27 /// parameters (and because traits cannot be aliased using `type`).
28 pub type EscapeFn = Box<Fn(&str) -> String + Send + Sync>;
29
30 /// The default *escape fn* replaces the characters `&"<>`
31 /// with the equivalent html / xml entities.
32 pub fn html_escape(data: &str) -> String {
33 DEFAULT_REPLACE
34 .replace_all(data, |cap: &Captures| {
35 match cap.get(0).map(|m| m.as_str()) {
36 Some("<") => "&lt;",
37 Some(">") => "&gt;",
38 Some("\"") => "&quot;",
39 Some("&") => "&amp;",
40 _ => unreachable!(),
41 }.to_owned()
42 })
43 .into_owned()
44 }
45
46 /// `EscapeFn` that do not change any thing. Useful when using in a non-html
47 /// environment.
48 pub fn no_escape(data: &str) -> String {
49 data.to_owned()
50 }
51
52 /// The single entry point of your Handlebars templates
53 ///
54 /// It maintains compiled templates and registered helpers.
55 pub struct Registry {
56 templates: HashMap<String, Template>,
57 helpers: HashMap<String, Box<HelperDef + 'static>>,
58 directives: HashMap<String, Box<DirectiveDef + 'static>>,
59 escape_fn: EscapeFn,
60 source_map: bool,
61 strict_mode: bool,
62 }
63
64 impl Debug for Registry {
65 fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
66 f.debug_struct("Handlebars")
67 .field("templates", &self.templates)
68 .field("helpers", &self.helpers.keys())
69 .field("directives", &self.directives.keys())
70 .field("source_map", &self.source_map)
71 .finish()
72 }
73 }
74
75 impl Registry {
76 pub fn new() -> Registry {
77 let r = Registry {
78 templates: HashMap::new(),
79 helpers: HashMap::new(),
80 directives: HashMap::new(),
81 escape_fn: Box::new(html_escape),
82 source_map: true,
83 strict_mode: false,
84 };
85
86 r.setup_builtins()
87 }
88
89 fn setup_builtins(mut self) -> Registry {
90 self.register_helper("if", Box::new(helpers::IF_HELPER));
91 self.register_helper("unless", Box::new(helpers::UNLESS_HELPER));
92 self.register_helper("each", Box::new(helpers::EACH_HELPER));
93 self.register_helper("with", Box::new(helpers::WITH_HELPER));
94 self.register_helper("lookup", Box::new(helpers::LOOKUP_HELPER));
95 self.register_helper("raw", Box::new(helpers::RAW_HELPER));
96 self.register_helper("log", Box::new(helpers::LOG_HELPER));
97
98 self.register_decorator("inline", Box::new(directives::INLINE_DIRECTIVE));
99 self
100 }
101
102 /// Enable handlebars template source map
103 ///
104 /// Source map provides line/col reporting on error. It uses slightly
105 /// more memory to maintain the data.
106 ///
107 /// Default is true.
108 pub fn source_map_enabled(&mut self, enable: bool) {
109 self.source_map = enable;
110 }
111
112 /// Enable handlebars strict mode
113 ///
114 /// By default, handlebars renders empty string for value that
115 /// undefined or never exists. Since rust is a static type
116 /// language, we offer strict mode in handlebars-rust. In strict
117 /// mode, if you were access a value that doesn't exist, a
118 /// `RenderError` will be raised.
119 pub fn set_strict_mode(&mut self, enable: bool) {
120 self.strict_mode = enable;
121 }
122
123 /// Return strict mode state, default is false.
124 ///
125 /// By default, handlebars renders empty string for value that
126 /// undefined or never exists. Since rust is a static type
127 /// language, we offer strict mode in handlebars-rust. In strict
128 /// mode, if you were access a value that doesn't exist, a
129 /// `RenderError` will be raised.
130 pub fn strict_mode(&self) -> bool {
131 self.strict_mode
132 }
133
134 /// Register a template string
135 ///
136 /// Returns `TemplateError` if there is syntax error on parsing template.
137 pub fn register_template_string<S>(
138 &mut self,
139 name: &str,
140 tpl_str: S,
141 ) -> Result<(), TemplateError>
142 where
143 S: AsRef<str>,
144 {
145 try!(
146 Template::compile_with_name(tpl_str, name.to_owned(), self.source_map)
147 .and_then(|t| Ok(self.templates.insert(name.to_string(), t)))
148 );
149 Ok(())
150 }
151
152 /// Register a partial string
153 ///
154 /// A named partial will be added to the registry. It will overwrite template with
155 /// same name. Currently registered partial is just identical to template.
156 pub fn register_partial<S>(&mut self, name: &str, partial_str: S) -> Result<(), TemplateError>
157 where
158 S: AsRef<str>,
159 {
160 self.register_template_string(name, partial_str)
161 }
162
163 /// Register a template from a path
164 pub fn register_template_file<P>(
165 &mut self,
166 name: &str,
167 tpl_path: P,
168 ) -> Result<(), TemplateFileError>
169 where
170 P: AsRef<Path>,
171 {
172 let mut file =
173 try!(File::open(tpl_path).map_err(|e| TemplateFileError::IOError(e, name.to_owned())));
174 self.register_template_source(name, &mut file)
175 }
176
177 /// Register a template from `std::io::Read` source
178 pub fn register_template_source(
179 &mut self,
180 name: &str,
181 tpl_source: &mut Read,
182 ) -> Result<(), TemplateFileError> {
183 let mut buf = String::new();
184 try!(
185 tpl_source
186 .read_to_string(&mut buf)
187 .map_err(|e| TemplateFileError::IOError(e, name.to_owned()))
188 );
189 try!(self.register_template_string(name, buf));
190 Ok(())
191 }
192
193 /// remove a template from the registry
194 pub fn unregister_template(&mut self, name: &str) {
195 self.templates.remove(name);
196 }
197
198 /// register a helper
199 pub fn register_helper(
200 &mut self,
201 name: &str,
202 def: Box<HelperDef + 'static>,
203 ) -> Option<Box<HelperDef + 'static>> {
204 self.helpers.insert(name.to_string(), def)
205 }
206
207 /// register a decorator
208 pub fn register_decorator(
209 &mut self,
210 name: &str,
211 def: Box<DirectiveDef + 'static>,
212 ) -> Option<Box<DirectiveDef + 'static>> {
213 self.directives.insert(name.to_string(), def)
214 }
215
216 /// Register a new *escape fn* to be used from now on by this registry.
217 pub fn register_escape_fn<F: 'static + Fn(&str) -> String + Send + Sync>(
218 &mut self,
219 escape_fn: F,
220 ) {
221 self.escape_fn = Box::new(escape_fn);
222 }
223
224 /// Restore the default *escape fn*.
225 pub fn unregister_escape_fn(&mut self) {
226 self.escape_fn = Box::new(html_escape);
227 }
228
229 /// Get a reference to the current *escape fn*.
230 pub fn get_escape_fn(&self) -> &Fn(&str) -> String {
231 &*self.escape_fn
232 }
233
234 /// Return a registered template,
235 pub fn get_template(&self, name: &str) -> Option<&Template> {
236 self.templates.get(name)
237 }
238
239 /// Return a registered helper
240 pub fn get_helper(&self, name: &str) -> Option<&Box<HelperDef + 'static>> {
241 self.helpers.get(name)
242 }
243
244 /// Return a registered directive, aka decorator
245 pub fn get_decorator(&self, name: &str) -> Option<&Box<DirectiveDef + 'static>> {
246 self.directives.get(name)
247 }
248
249 /// Return all templates registered
250 pub fn get_templates(&self) -> &HashMap<String, Template> {
251 &self.templates
252 }
253
254 /// Unregister all templates
255 pub fn clear_templates(&mut self) {
256 self.templates.clear();
257 }
258
259 /// Render a registered template with some data into a string
260 ///
261 /// * `name` is the template name you registred previously
262 /// * `ctx` is the data that implements `serde::Serialize`
263 ///
264 /// Returns rendered string or an struct with error information
265 pub fn render<T>(&self, name: &str, data: &T) -> Result<String, RenderError>
266 where
267 T: Serialize,
268 {
269 let mut writer = StringWriter::new();
270 {
271 try!(self.render_to_write(name, data, &mut writer));
272 }
273 Ok(writer.to_string())
274 }
275
276 /// Render a registered template and write some data to the `std::io::Write`
277 pub fn render_to_write<T>(
278 &self,
279 name: &str,
280 data: &T,
281 writer: &mut Write,
282 ) -> Result<(), RenderError>
283 where
284 T: Serialize,
285 {
286 self.get_template(&name.to_string())
287 .ok_or(RenderError::new(format!("Template not found: {}", name)))
288 .and_then(|t| {
289 let ctx = try!(Context::wraps(data));
290 let mut local_helpers = HashMap::new();
291 let mut render_context = RenderContext::new(ctx, &mut local_helpers, writer);
292 render_context.root_template = t.name.clone();
293 t.render(self, &mut render_context)
294 })
295 }
296
297 /// render a template string using current registry without register it
298 pub fn render_template<T>(
299 &self,
300 template_string: &str,
301 data: &T,
302 ) -> Result<String, TemplateRenderError>
303 where
304 T: Serialize,
305 {
306 let mut writer = StringWriter::new();
307 {
308 try!(self.render_template_to_write(template_string, data, &mut writer));
309 }
310 Ok(writer.to_string())
311 }
312
313 /// render a template string using current registry without register it
314 pub fn render_template_to_write<T>(
315 &self,
316 template_string: &str,
317 data: &T,
318 writer: &mut Write,
319 ) -> Result<(), TemplateRenderError>
320 where
321 T: Serialize,
322 {
323 let tpl = try!(Template::compile2(template_string, self.source_map));
324 let ctx = try!(Context::wraps(data));
325 let mut local_helpers = HashMap::new();
326 let mut render_context = RenderContext::new(ctx, &mut local_helpers, writer);
327 tpl.render(self, &mut render_context)
328 .map_err(TemplateRenderError::from)
329 }
330
331 /// render a template source using current registry without register it
332 pub fn render_template_source_to_write<T>(
333 &self,
334 template_source: &mut Read,
335 data: &T,
336 writer: &mut Write,
337 ) -> Result<(), TemplateRenderError>
338 where
339 T: Serialize,
340 {
341 let mut tpl_str = String::new();
342 try!(
343 template_source
344 .read_to_string(&mut tpl_str)
345 .map_err(|e| TemplateRenderError::IOError(e, "Unamed template source".to_owned()))
346 );
347 self.render_template_to_write(&tpl_str, data, writer)
348 }
349
350 #[deprecated(since = "0.30.0", note = "Please use render_to_write instead.")]
351 pub fn renderw<T>(&self, name: &str, data: &T, writer: &mut Write) -> Result<(), RenderError>
352 where
353 T: Serialize,
354 {
355 self.render_to_write(name, data, writer)
356 }
357
358 #[deprecated(since = "0.30.0", note = "Please use render_template instead.")]
359 pub fn template_render<T>(
360 &self,
361 template_string: &str,
362 data: &T,
363 ) -> Result<String, TemplateRenderError>
364 where
365 T: Serialize,
366 {
367 self.render_template(template_string, data)
368 }
369
370 #[deprecated(since = "0.30.0", note = "Please use render_template_to_write instead.")]
371 pub fn template_renderw<T>(
372 &self,
373 template_string: &str,
374 data: &T,
375 writer: &mut Write,
376 ) -> Result<(), TemplateRenderError>
377 where
378 T: Serialize,
379 {
380 self.render_template_to_write(template_string, data, writer)
381 }
382
383 #[deprecated(since = "0.30.0", note = "Please use render_template_source_to_write instead.")]
384 pub fn template_renderw2<T>(
385 &self,
386 template_source: &mut Read,
387 data: &T,
388 writer: &mut Write,
389 ) -> Result<(), TemplateRenderError>
390 where
391 T: Serialize,
392 {
393 self.render_template_source_to_write(template_source, data, writer)
394 }
395 }
396
397 #[cfg(test)]
398 mod test {
399 use registry::Registry;
400 use render::{Helper, RenderContext, Renderable};
401 use helpers::HelperDef;
402 use support::str::StringWriter;
403 use error::RenderError;
404
405 #[derive(Clone, Copy)]
406 struct DummyHelper;
407
408 impl HelperDef for DummyHelper {
409 fn call(
410 &self,
411 h: &Helper,
412 r: &Registry,
413 rc: &mut RenderContext,
414 ) -> Result<(), RenderError> {
415 try!(h.template().unwrap().render(r, rc));
416 Ok(())
417 }
418 }
419
420 static DUMMY_HELPER: DummyHelper = DummyHelper;
421
422 #[test]
423 fn test_registry_operations() {
424 let mut r = Registry::new();
425
426 assert!(r.register_template_string("index", "<h1></h1>").is_ok());
427 assert!(r.register_template_string("index2", "<h2></h2>").is_ok());
428
429 assert_eq!(r.templates.len(), 2);
430
431 r.unregister_template("index");
432 assert_eq!(r.templates.len(), 1);
433
434 r.clear_templates();
435 assert_eq!(r.templates.len(), 0);
436
437 r.register_helper("dummy", Box::new(DUMMY_HELPER));
438
439 // built-in helpers plus 1
440 assert_eq!(r.helpers.len(), 7 + 1);
441 }
442
443 #[test]
444 fn test_render_to_write() {
445 let mut r = Registry::new();
446
447 assert!(r.register_template_string("index", "<h1></h1>").is_ok());
448
449 let mut sw = StringWriter::new();
450 {
451 r.render_to_write("index", &(), &mut sw).ok().unwrap();
452 }
453
454 assert_eq!("<h1></h1>".to_string(), sw.to_string());
455 }
456
457 #[test]
458 fn test_escape_fn() {
459 let mut r = Registry::new();
460
461 let input = String::from("\"<>&");
462
463 r.register_template_string("test", String::from("{{this}}"))
464 .unwrap();
465
466 assert_eq!("&quot;&lt;&gt;&amp;", r.render("test", &input).unwrap());
467
468 r.register_escape_fn(|s| s.into());
469
470 assert_eq!("\"<>&", r.render("test", &input).unwrap());
471
472 r.unregister_escape_fn();
473
474 assert_eq!("&quot;&lt;&gt;&amp;", r.render("test", &input).unwrap());
475 }
476
477 #[test]
478 fn test_escape() {
479 let r = Registry::new();
480 let data = json!({
481 "hello": "world"
482 });
483
484 assert_eq!(
485 "{{hello}}",
486 r.render_template(r"\{{hello}}", &data).unwrap()
487 );
488
489 assert_eq!(
490 " {{hello}}",
491 r.render_template(r" \{{hello}}", &data).unwrap()
492 );
493
494 assert_eq!(
495 r"\world",
496 r.render_template(r"\\{{hello}}", &data).unwrap()
497 );
498 }
499
500 #[test]
501 fn test_strict_mode() {
502 let mut r = Registry::new();
503 assert!(!r.strict_mode());
504
505 r.set_strict_mode(true);
506 assert!(r.strict_mode());
507
508 let data = json!({
509 "the_only_key": "the_only_value"
510 });
511
512 assert!(
513 r.render_template("accessing the_only_key {{the_only_key}}", &data)
514 .is_ok()
515 );
516 assert!(
517 r.render_template("accessing non-exists key {{the_key_never_exists}}", &data)
518 .is_err()
519 );
520
521 let render_error =
522 r.render_template("accessing non-exists key {{the_key_never_exists}}", &data)
523 .unwrap_err();
524 assert_eq!(
525 render_error.as_render_error().unwrap().column_no.unwrap(),
526 26
527 );
528
529 let data2 = json!([1, 2, 3]);
530 assert!(
531 r.render_template("accessing valid array index {{this.[2]}}", &data2)
532 .is_ok()
533 );
534 assert!(
535 r.render_template("accessing invalid array index {{this.[3]}}", &data2)
536 .is_err()
537 );
538 let render_error2 = r.render_template("accessing invalid array index {{this.[3]}}", &data2)
539 .unwrap_err();
540 assert_eq!(
541 render_error2.as_render_error().unwrap().column_no.unwrap(),
542 31
543 );
544 }
545 }