1 use std
::collections
::HashMap
;
2 use std
::io
::prelude
::*;
5 use std
::fmt
::{self, Debug, Formatter}
;
9 use regex
::{Captures, Regex}
;
11 use template
::Template
;
12 use render
::{RenderContext, Renderable}
;
14 use helpers
::{self, HelperDef}
;
15 use directives
::{self, DirectiveDef}
;
16 use support
::str::StringWriter
;
17 use error
::{RenderError, TemplateError, TemplateFileError, TemplateRenderError}
;
20 static ref DEFAULT_REPLACE
: Regex
= Regex
::new(">|<|\"|&").unwrap();
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.
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
>;
30 /// The default *escape fn* replaces the characters `&"<>`
31 /// with the equivalent html / xml entities.
32 pub fn html_escape(data
: &str) -> String
{
34 .replace_all(data
, |cap
: &Captures
| {
35 match cap
.get(0).map(|m
| m
.as_str()) {
38 Some("\"") => """,
46 /// `EscapeFn` that do not change any thing. Useful when using in a non-html
48 pub fn no_escape(data
: &str) -> String
{
52 /// The single entry point of your Handlebars templates
54 /// It maintains compiled templates and registered helpers.
56 templates
: HashMap
<String
, Template
>,
57 helpers
: HashMap
<String
, Box
<HelperDef
+ '
static>>,
58 directives
: HashMap
<String
, Box
<DirectiveDef
+ '
static>>,
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
)
76 pub fn new() -> Registry
{
78 templates
: HashMap
::new(),
79 helpers
: HashMap
::new(),
80 directives
: HashMap
::new(),
81 escape_fn
: Box
::new(html_escape
),
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
));
98 self.register_decorator("inline", Box
::new(directives
::INLINE_DIRECTIVE
));
102 /// Enable handlebars template source map
104 /// Source map provides line/col reporting on error. It uses slightly
105 /// more memory to maintain the data.
108 pub fn source_map_enabled(&mut self, enable
: bool
) {
109 self.source_map
= enable
;
112 /// Enable handlebars strict mode
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
;
123 /// Return strict mode state, default is false.
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
{
134 /// Register a template string
136 /// Returns `TemplateError` if there is syntax error on parsing template.
137 pub fn register_template_string
<S
>(
141 ) -> Result
<(), TemplateError
>
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
)))
152 /// Register a partial string
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
>
160 self.register_template_string(name
, partial_str
)
163 /// Register a template from a path
164 pub fn register_template_file
<P
>(
168 ) -> Result
<(), TemplateFileError
>
173 try
!(File
::open(tpl_path
).map_err(|e
| TemplateFileError
::IOError(e
, name
.to_owned())));
174 self.register_template_source(name
, &mut file
)
177 /// Register a template from `std::io::Read` source
178 pub fn register_template_source(
181 tpl_source
: &mut Read
,
182 ) -> Result
<(), TemplateFileError
> {
183 let mut buf
= String
::new();
186 .read_to_string(&mut buf
)
187 .map_err(|e
| TemplateFileError
::IOError(e
, name
.to_owned()))
189 try
!(self.register_template_string(name
, buf
));
193 /// remove a template from the registry
194 pub fn unregister_template(&mut self, name
: &str) {
195 self.templates
.remove(name
);
198 /// register a helper
199 pub fn register_helper(
202 def
: Box
<HelperDef
+ '
static>,
203 ) -> Option
<Box
<HelperDef
+ '
static>> {
204 self.helpers
.insert(name
.to_string(), def
)
207 /// register a decorator
208 pub fn register_decorator(
211 def
: Box
<DirectiveDef
+ '
static>,
212 ) -> Option
<Box
<DirectiveDef
+ '
static>> {
213 self.directives
.insert(name
.to_string(), def
)
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
>(
221 self.escape_fn
= Box
::new(escape_fn
);
224 /// Restore the default *escape fn*.
225 pub fn unregister_escape_fn(&mut self) {
226 self.escape_fn
= Box
::new(html_escape
);
229 /// Get a reference to the current *escape fn*.
230 pub fn get_escape_fn(&self) -> &Fn(&str) -> String
{
234 /// Return a registered template,
235 pub fn get_template(&self, name
: &str) -> Option
<&Template
> {
236 self.templates
.get(name
)
239 /// Return a registered helper
240 pub fn get_helper(&self, name
: &str) -> Option
<&Box
<HelperDef
+ '
static>> {
241 self.helpers
.get(name
)
244 /// Return a registered directive, aka decorator
245 pub fn get_decorator(&self, name
: &str) -> Option
<&Box
<DirectiveDef
+ '
static>> {
246 self.directives
.get(name
)
249 /// Return all templates registered
250 pub fn get_templates(&self) -> &HashMap
<String
, Template
> {
254 /// Unregister all templates
255 pub fn clear_templates(&mut self) {
256 self.templates
.clear();
259 /// Render a registered template with some data into a string
261 /// * `name` is the template name you registred previously
262 /// * `ctx` is the data that implements `serde::Serialize`
264 /// Returns rendered string or an struct with error information
265 pub fn render
<T
>(&self, name
: &str, data
: &T
) -> Result
<String
, RenderError
>
269 let mut writer
= StringWriter
::new();
271 try
!(self.render_to_write(name
, data
, &mut writer
));
273 Ok(writer
.to_string())
276 /// Render a registered template and write some data to the `std::io::Write`
277 pub fn render_to_write
<T
>(
282 ) -> Result
<(), RenderError
>
286 self.get_template(&name
.to_string())
287 .ok_or(RenderError
::new(format
!("Template not found: {}", name
)))
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
)
297 /// render a template string using current registry without register it
298 pub fn render_template
<T
>(
300 template_string
: &str,
302 ) -> Result
<String
, TemplateRenderError
>
306 let mut writer
= StringWriter
::new();
308 try
!(self.render_template_to_write(template_string
, data
, &mut writer
));
310 Ok(writer
.to_string())
313 /// render a template string using current registry without register it
314 pub fn render_template_to_write
<T
>(
316 template_string
: &str,
319 ) -> Result
<(), TemplateRenderError
>
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
)
331 /// render a template source using current registry without register it
332 pub fn render_template_source_to_write
<T
>(
334 template_source
: &mut Read
,
337 ) -> Result
<(), TemplateRenderError
>
341 let mut tpl_str
= String
::new();
344 .read_to_string(&mut tpl_str
)
345 .map_err(|e
| TemplateRenderError
::IOError(e
, "Unamed template source".to_owned()))
347 self.render_template_to_write(&tpl_str
, data
, writer
)
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
>
355 self.render_to_write(name
, data
, writer
)
358 #[deprecated(since = "0.30.0", note = "Please use render_template instead.")]
359 pub fn template_render
<T
>(
361 template_string
: &str,
363 ) -> Result
<String
, TemplateRenderError
>
367 self.render_template(template_string
, data
)
370 #[deprecated(since = "0.30.0", note = "Please use render_template_to_write instead.")]
371 pub fn template_renderw
<T
>(
373 template_string
: &str,
376 ) -> Result
<(), TemplateRenderError
>
380 self.render_template_to_write(template_string
, data
, writer
)
383 #[deprecated(since = "0.30.0", note = "Please use render_template_source_to_write instead.")]
384 pub fn template_renderw2
<T
>(
386 template_source
: &mut Read
,
389 ) -> Result
<(), TemplateRenderError
>
393 self.render_template_source_to_write(template_source
, data
, writer
)
399 use registry
::Registry
;
400 use render
::{Helper, RenderContext, Renderable}
;
401 use helpers
::HelperDef
;
402 use support
::str::StringWriter
;
403 use error
::RenderError
;
405 #[derive(Clone, Copy)]
408 impl HelperDef
for DummyHelper
{
413 rc
: &mut RenderContext
,
414 ) -> Result
<(), RenderError
> {
415 try
!(h
.template().unwrap().render(r
, rc
));
420 static DUMMY_HELPER
: DummyHelper
= DummyHelper
;
423 fn test_registry_operations() {
424 let mut r
= Registry
::new();
426 assert
!(r
.register_template_string("index", "<h1></h1>").is_ok());
427 assert
!(r
.register_template_string("index2", "<h2></h2>").is_ok());
429 assert_eq
!(r
.templates
.len(), 2);
431 r
.unregister_template("index");
432 assert_eq
!(r
.templates
.len(), 1);
435 assert_eq
!(r
.templates
.len(), 0);
437 r
.register_helper("dummy", Box
::new(DUMMY_HELPER
));
439 // built-in helpers plus 1
440 assert_eq
!(r
.helpers
.len(), 7 + 1);
444 fn test_render_to_write() {
445 let mut r
= Registry
::new();
447 assert
!(r
.register_template_string("index", "<h1></h1>").is_ok());
449 let mut sw
= StringWriter
::new();
451 r
.render_to_write("index", &(), &mut sw
).ok().unwrap();
454 assert_eq
!("<h1></h1>".to_string(), sw
.to_string());
458 fn test_escape_fn() {
459 let mut r
= Registry
::new();
461 let input
= String
::from("\"<>&");
463 r
.register_template_string("test", String
::from("{{this}}"))
466 assert_eq
!(""<>&", r
.render("test", &input
).unwrap());
468 r
.register_escape_fn(|s
| s
.into());
470 assert_eq
!("\"<>&", r
.render("test", &input
).unwrap());
472 r
.unregister_escape_fn();
474 assert_eq
!(""<>&", r
.render("test", &input
).unwrap());
479 let r
= Registry
::new();
486 r
.render_template(r
"\{{hello}}", &data
).unwrap()
491 r
.render_template(r
" \{{hello}}", &data
).unwrap()
496 r
.render_template(r
"\\{{hello}}", &data
).unwrap()
501 fn test_strict_mode() {
502 let mut r
= Registry
::new();
503 assert
!(!r
.strict_mode());
505 r
.set_strict_mode(true);
506 assert
!(r
.strict_mode());
509 "the_only_key": "the_only_value"
513 r
.render_template("accessing the_only_key {{the_only_key}}", &data
)
517 r
.render_template("accessing non-exists key {{the_key_never_exists}}", &data
)
522 r
.render_template("accessing non-exists key {{the_key_never_exists}}", &data
)
525 render_error
.as_render_error().unwrap().column_no
.unwrap(),
529 let data2
= json
!([1, 2, 3]);
531 r
.render_template("accessing valid array index {{this.[2]}}", &data2
)
535 r
.render_template("accessing invalid array index {{this.[3]}}", &data2
)
538 let render_error2
= r
.render_template("accessing invalid array index {{this.[3]}}", &data2
)
541 render_error2
.as_render_error().unwrap().column_no
.unwrap(),