handlebars/lib.rs
1#![doc(html_root_url = "https://docs.rs/handlebars/5.0.0")]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3//! # Handlebars
4//!
5//! [Handlebars](http://handlebarsjs.com/) is a modern and extensible templating solution originally created in the JavaScript world. It's used by many popular frameworks like [Ember.js](http://emberjs.com) and Chaplin. It's also ported to some other platforms such as [Java](https://github.com/jknack/handlebars.java).
6//!
7//! And this is handlebars Rust implementation, designed for general purpose text generation.
8//!
9//! ## Quick Start
10//!
11//! ```
12//! use std::collections::BTreeMap;
13//! use handlebars::Handlebars;
14//!
15//! # fn main() {
16//! // create the handlebars registry
17//! let mut handlebars = Handlebars::new();
18//!
19//! // register the template. The template string will be verified and compiled.
20//! let source = "hello {{world}}";
21//! assert!(handlebars.register_template_string("t1", source).is_ok());
22//!
23//! // Prepare some data.
24//! //
25//! // The data type should implements `serde::Serialize`
26//! let mut data = BTreeMap::new();
27//! data.insert("world".to_string(), "世界!".to_string());
28//! assert_eq!(handlebars.render("t1", &data).unwrap(), "hello 世界!");
29//! # }
30//! ```
31//!
32//! In this example, we created a template registry and registered a template named `t1`.
33//! Then we rendered a `BTreeMap` with an entry of key `world`, the result is just what
34//! we expected.
35//!
36//! I recommend you to walk through handlebars.js' [intro page](http://handlebarsjs.com)
37//! if you are not quite familiar with the template language itself.
38//!
39//! ## Features
40//!
41//! Handlebars is a real-world templating system that you can use to build
42//! your application without pain.
43//!
44//! ### Isolation of Rust and HTML
45//!
46//! This library doesn't attempt to use some macro magic to allow you to
47//! write your template within your rust code. I admit that it's fun to do
48//! that but it doesn't fit real-world use cases.
49//!
50//! ### Limited but essential control structures built-in
51//!
52//! Only essential control directives `if` and `each` are built-in. This
53//! prevents you from putting too much application logic into your template.
54//!
55//! ### Extensible helper system
56//!
57//! Helper is the control system of handlebars language. In the original JavaScript
58//! version, you can implement your own helper with JavaScript.
59//!
60//! Handlebars-rust offers similar mechanism that custom helper can be defined with
61//! rust function, or [rhai](https://github.com/jonathandturner/rhai) script.
62//!
63//! The built-in helpers like `if` and `each` were written with these
64//! helper APIs and the APIs are fully available to developers.
65//!
66//! ### Auto-reload in dev mode
67//!
68//! By turning on `dev_mode`, handlebars auto reloads any template and scripts that
69//! loaded from files or directory. This can be handy for template development.
70//!
71//! ### Template inheritance
72//!
73//! Every time I look into a templating system, I will investigate its
74//! support for [template inheritance][t].
75//!
76//! [t]: https://docs.djangoproject.com/en/3.2/ref/templates/language/#template-inheritance
77//!
78//! Template include is not sufficient for template reuse. In most cases
79//! you will need a skeleton of page as parent (header, footer, etc.), and
80//! embed your page into this parent.
81//!
82//! You can find a real example of template inheritance in
83//! `examples/partials.rs` and templates used by this file.
84//!
85//! ### Strict mode
86//!
87//! Handlebars, the language designed to work with JavaScript, has no
88//! strict restriction on accessing nonexistent fields or indexes. It
89//! generates empty strings for such cases. However, in Rust we want to be
90//! a little stricter sometimes.
91//!
92//! By enabling `strict_mode` on handlebars:
93//!
94//! ```
95//! # use handlebars::Handlebars;
96//! # let mut handlebars = Handlebars::new();
97//! handlebars.set_strict_mode(true);
98//! ```
99//!
100//! You will get a `RenderError` when accessing fields that do not exist.
101//!
102//! ## Limitations
103//!
104//! ### Compatibility with original JavaScript version
105//!
106//! This implementation is **not fully compatible** with the original JavaScript version.
107//!
108//! First of all, mustache blocks are not supported. I suggest you to use `#if` and `#each` for
109//! the same functionality.
110//!
111//! There are some other minor features missing:
112//!
113//! * Chained else [#12](https://github.com/sunng87/handlebars-rust/issues/12)
114//!
115//! Feel free to file an issue on [github](https://github.com/sunng87/handlebars-rust/issues) if
116//! you find missing features.
117//!
118//! ### Types
119//!
120//! As a static typed language, it's a little verbose to use handlebars.
121//! Handlebars templating language is designed against JSON data type. In rust,
122//! we will convert user's structs, vectors or maps into Serde-Json's `Value` type
123//! in order to use in templates. You have to make sure your data implements the
124//! `Serialize` trait from the [Serde](https://serde.rs) project.
125//!
126//! ## Usage
127//!
128//! ### Template Creation and Registration
129//!
130//! Templates are created from `String`s and registered to `Handlebars` with a name.
131//!
132//! ```
133//! # extern crate handlebars;
134//!
135//! use handlebars::Handlebars;
136//!
137//! # fn main() {
138//! let mut handlebars = Handlebars::new();
139//! let source = "hello {{world}}";
140//!
141//! assert!(handlebars.register_template_string("t1", source).is_ok())
142//! # }
143//! ```
144//!
145//! On registration, the template is parsed, compiled and cached in the registry. So further
146//! usage will benefit from the one-time work. Also features like include, inheritance
147//! that involves template reference requires you to register those template first with
148//! a name so the registry can find it.
149//!
150//! If you template is small or just to experiment, you can use `render_template` API
151//! without registration.
152//!
153//! ```
154//! # use std::error::Error;
155//! use handlebars::Handlebars;
156//! use std::collections::BTreeMap;
157//!
158//! # fn main() -> Result<(), Box<dyn Error>> {
159//! let mut handlebars = Handlebars::new();
160//! let source = "hello {{world}}";
161//!
162//! let mut data = BTreeMap::new();
163//! data.insert("world".to_string(), "世界!".to_string());
164//! assert_eq!(handlebars.render_template(source, &data)?, "hello 世界!".to_owned());
165//! # Ok(())
166//! # }
167//! ```
168//!
169//! #### Additional features for loading template from
170//!
171//! * Feature `dir_source` enables template loading
172//! `register_templates_directory` from given directory.
173//! * Feature `rust-embed` enables template loading
174//! `register_embed_templates` from embedded resources in rust struct
175//! generated with `RustEmbed`.
176//!
177//! ### Rendering Something
178//!
179//! Since handlebars is originally based on JavaScript type system. It supports dynamic features like duck-typing, truthy/falsey values. But for a static language like Rust, this is a little difficult. As a solution, we are using the `serde_json::value::Value` internally for data rendering.
180//!
181//! That means, if you want to render something, you have to ensure the data type implements the `serde::Serialize` trait. Most rust internal types already have that trait. Use `#derive[Serialize]` for your types to generate default implementation.
182//!
183//! You can use default `render` function to render a template into `String`. From 0.9, there's `render_to_write` to render text into anything of `std::io::Write`.
184//!
185//! ```
186//! # use std::error::Error;
187//! # #[macro_use]
188//! # extern crate serde_derive;
189//! # extern crate handlebars;
190//!
191//! use handlebars::Handlebars;
192//!
193//! #[derive(Serialize)]
194//! struct Person {
195//! name: String,
196//! age: i16,
197//! }
198//!
199//! # fn main() -> Result<(), Box<dyn Error>> {
200//! let source = "Hello, {{name}}";
201//!
202//! let mut handlebars = Handlebars::new();
203//! assert!(handlebars.register_template_string("hello", source).is_ok());
204//!
205//!
206//! let data = Person {
207//! name: "Ning Sun".to_string(),
208//! age: 27
209//! };
210//! assert_eq!(handlebars.render("hello", &data)?, "Hello, Ning Sun".to_owned());
211//! # Ok(())
212//! # }
213//! #
214//! ```
215//!
216//! Or if you don't need the template to be cached or referenced by other ones, you can
217//! simply render it without registering.
218//!
219//! ```
220//! # use std::error::Error;
221//! # #[macro_use]
222//! # extern crate serde_derive;
223//! # extern crate handlebars;
224//! use handlebars::Handlebars;
225//! # #[derive(Serialize)]
226//! # struct Person {
227//! # name: String,
228//! # age: i16,
229//! # }
230//!
231//! # fn main() -> Result<(), Box<dyn Error>> {
232//! let source = "Hello, {{name}}";
233//!
234//! let mut handlebars = Handlebars::new();
235//!
236//! let data = Person {
237//! name: "Ning Sun".to_string(),
238//! age: 27
239//! };
240//! assert_eq!(handlebars.render_template("Hello, {{name}}", &data)?,
241//! "Hello, Ning Sun".to_owned());
242//! # Ok(())
243//! # }
244//! ```
245//!
246//! #### Escaping
247//!
248//! As per the handlebars spec, output using `{{expression}}` is escaped by default (to be precise, the characters `&"<>` are replaced by their respective html / xml entities). However, since the use cases of a rust template engine are probably a bit more diverse than those of a JavaScript one, this implementation allows the user to supply a custom escape function to be used instead. For more information see the `EscapeFn` type and `Handlebars::register_escape_fn()` method. In particular, `no_escape()` can be used as the escape function if no escaping at all should be performed.
249//!
250//! ### Custom Helper
251//!
252//! Handlebars is nothing without helpers. You can also create your own helpers with rust. Helpers in handlebars-rust are custom struct implements the `HelperDef` trait, concretely, the `call` function. For your convenience, most of stateless helpers can be implemented as bare functions.
253//!
254//! ```
255//! use std::io::Write;
256//! # use std::error::Error;
257//! use handlebars::*;
258//!
259//! // implement by a structure impls HelperDef
260//! #[derive(Clone, Copy)]
261//! struct SimpleHelper;
262//!
263//! impl HelperDef for SimpleHelper {
264//! fn call<'reg: 'rc, 'rc>(&self, h: &Helper, _: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
265//! let param = h.param(0).unwrap();
266//!
267//! out.write("1st helper: ")?;
268//! out.write(param.value().render().as_ref())?;
269//! Ok(())
270//! }
271//! }
272//!
273//! // implement via bare function
274//! fn another_simple_helper (h: &Helper, _: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
275//! let param = h.param(0).unwrap();
276//!
277//! out.write("2nd helper: ")?;
278//! out.write(param.value().render().as_ref())?;
279//! Ok(())
280//! }
281//!
282//!
283//! # fn main() -> Result<(), Box<dyn Error>> {
284//! let mut handlebars = Handlebars::new();
285//! handlebars.register_helper("simple-helper", Box::new(SimpleHelper));
286//! handlebars.register_helper("another-simple-helper", Box::new(another_simple_helper));
287//! // via closure
288//! handlebars.register_helper("closure-helper",
289//! Box::new(|h: &Helper, r: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output| -> HelperResult {
290//! let param =
291//! h.param(0).ok_or(RenderErrorReason::ParamNotFoundForIndex("closure-helper", 0))?;
292//!
293//! out.write("3rd helper: ")?;
294//! out.write(param.value().render().as_ref())?;
295//! Ok(())
296//! }));
297//!
298//! let tpl = "{{simple-helper 1}}\n{{another-simple-helper 2}}\n{{closure-helper 3}}";
299//! assert_eq!(handlebars.render_template(tpl, &())?,
300//! "1st helper: 1\n2nd helper: 2\n3rd helper: 3".to_owned());
301//! # Ok(())
302//! # }
303//!
304//! ```
305//!
306//! Data available to helper can be found in [Helper](struct.Helper.html). And there are more
307//! examples in [HelperDef](trait.HelperDef.html) page.
308//!
309//! You can learn more about helpers by looking into source code of built-in helpers.
310//!
311//!
312//! ### Script Helper
313//!
314//! Like our JavaScript counterparts, handlebars allows user to define simple helpers with
315//! a scripting language, [rhai](https://docs.rs/crate/rhai/). This can be enabled by
316//! turning on `script_helper` feature flag.
317//!
318//! A sample script:
319//!
320//! ```handlebars
321//! {{percent 0.34 label="%"}}
322//! ```
323//!
324//! ```rhai
325//! // percent.rhai
326//! // get first parameter from `params` array
327//! let value = params[0];
328//! // get key value pair `label` from `hash` map
329//! let label = hash["label"];
330//!
331//! // compute the final string presentation
332//! (value * 100).to_string() + label
333//! ```
334//!
335//! A runnable [example](https://github.com/sunng87/handlebars-rust/blob/master/examples/script.rs) can be find in the repo.
336//!
337//! #### Built-in Helpers
338//!
339//! * `{{{{raw}}}} ... {{{{/raw}}}}` escape handlebars expression within the block
340//! * `{{#if ...}} ... {{else}} ... {{/if}}` if-else block
341//! (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#if) on how to use this helper.)
342//! * `{{#unless ...}} ... {{else}} .. {{/unless}}` if-not-else block
343//! (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#unless) on how to use this helper.)
344//! * `{{#each ...}} ... {{/each}}` iterates over an array or object. Handlebars-rust doesn't support mustache iteration syntax so use `each` instead.
345//! (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#each) on how to use this helper.)
346//! * `{{#with ...}} ... {{/with}}` change current context. Similar to `{{#each}}`, used for replace corresponding mustache syntax.
347//! (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#with) on how to use this helper.)
348//! * `{{lookup ... ...}}` get value from array by `@index` or `@key`
349//! (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#lookup) on how to use this helper.)
350//! * `{{> ...}}` include template by its name
351//! * `{{log ...}}` log value with rust logger, default level: INFO. Currently you cannot change the level.
352//! * Boolean helpers that can be used in `if` as subexpression, for example `{{#if (gt 2 1)}} ...`:
353//! * `eq`
354//! * `ne`
355//! * `gt`
356//! * `gte`
357//! * `lt`
358//! * `lte`
359//! * `and`
360//! * `or`
361//! * `not`
362//! * `{{len ...}}` returns length of array/object/string
363//!
364//! ### Template inheritance
365//!
366//! Handlebars.js' partial system is fully supported in this implementation.
367//! Check [example](https://github.com/sunng87/handlebars-rust/blob/master/examples/partials.rs#L49) for details.
368//!
369//! ### String (or Case) Helpers
370//!
371//! [Handlebars] supports helpers for converting string cases for example converting a value to
372//! 'camelCase or 'kebab-case' etc. This can be useful during generating code using Handlebars.
373//! This can be enabled by selecting the feature-flag `string_helpers`. Currently the case
374//! conversions from the [`heck`](https://docs.rs/heck/latest/heck) crate are supported.
375//!
376//! ```
377//! # #[cfg(feature = "string_helpers")] {
378//! # use std::error::Error;
379//! # extern crate handlebars;
380//! use handlebars::Handlebars;
381//!
382//! # fn main() -> Result<(), Box<dyn Error>> {
383//!
384//! let mut handlebars = Handlebars::new();
385//!
386//! let data = serde_json::json!({"value": "lower camel case"});
387//! assert_eq!(handlebars.render_template("This is {{lowerCamelCase value}}", &data)?,
388//! "This is lowerCamelCase".to_owned());
389//! # Ok(())
390//! # }
391//! # }
392//! ```
393//!
394
395#![allow(dead_code, clippy::upper_case_acronyms)]
396#![warn(rust_2018_idioms)]
397#![recursion_limit = "200"]
398
399#[cfg(not(feature = "no_logging"))]
400#[macro_use]
401extern crate log;
402
403#[macro_use]
404extern crate pest_derive;
405#[cfg(test)]
406#[macro_use]
407extern crate serde_derive;
408
409#[allow(unused_imports)]
410#[macro_use]
411extern crate serde_json;
412
413pub use self::block::{BlockContext, BlockParams};
414pub use self::context::Context;
415pub use self::decorators::DecoratorDef;
416pub use self::error::{RenderError, RenderErrorReason, TemplateError, TemplateErrorReason};
417pub use self::helpers::{HelperDef, HelperResult};
418pub use self::json::path::Path;
419pub use self::json::value::{to_json, JsonRender, JsonTruthy, PathAndJson, ScopedJson};
420pub use self::local_vars::LocalVars;
421pub use self::output::{Output, StringOutput};
422#[cfg(feature = "dir_source")]
423pub use self::registry::DirectorySourceOptions;
424pub use self::registry::{html_escape, no_escape, EscapeFn, Registry as Handlebars};
425pub use self::render::{Decorator, Evaluable, Helper, RenderContext, Renderable};
426pub use self::template::Template;
427
428#[doc(hidden)]
429pub use self::serde_json::Value as JsonValue;
430
431#[macro_use]
432mod macros;
433mod block;
434mod context;
435mod decorators;
436mod error;
437mod grammar;
438mod helpers;
439mod json;
440mod local_vars;
441mod output;
442mod partial;
443mod registry;
444mod render;
445mod sources;
446mod support;
447pub mod template;
448mod util;