Skip to main content

handlebars/
lib.rs

1#![doc(html_root_url = "https://docs.rs/handlebars/6.4.4")]
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//! ### Preserving JSON object key order
103//!
104//! Internally handlebars uses `serde_json::Map` to represent JSON objects.
105//! By default `serde_json` sorts object keys alphabetically, which means
106//! iterating over an object with `{{#each}}` would yield keys in sorted
107//! order rather than the order they appear in your data.
108//!
109//! The `preserve_json_order` feature (enabled by default) turns on
110//! `serde_json`'s `preserve_order` feature, so that object keys keep their
111//! insertion order. This is particularly useful when iterating over objects
112//! with `{{#each}}`, where you usually expect keys to follow the order
113//! defined in the input data.
114//!
115//! If you prefer the alphabetical ordering behaviour, disable this feature
116//! by turning off default features:
117//!
118//! ```toml
119//! [dependencies]
120//! handlebars = { version = "6.4.2", default-features = false }
121//! ```
122//!
123//! ## Limitations
124//!
125//! ### Compatibility with original JavaScript version
126//!
127//! This implementation is **not fully compatible** with the original JavaScript version.
128//!
129//! First of all, mustache blocks are not supported. I suggest you to use `#if` and `#each` for
130//! the same functionality.
131//!
132//! Feel free to file an issue on [github](https://github.com/sunng87/handlebars-rust/issues) if
133//! you find missing features.
134//!
135//! ### Types
136//!
137//! As a static typed language, it's a little verbose to use handlebars.
138//! Handlebars templating language is designed against JSON data type. In rust,
139//! we will convert user's structs, vectors or maps into Serde-Json's `Value` type
140//! in order to use in templates. You have to make sure your data implements the
141//! `Serialize` trait from the [Serde](https://serde.rs) project.
142//!
143//! ## Usage
144//!
145//! ### Template Creation and Registration
146//!
147//! Templates are created from `String`s and registered to `Handlebars` with a name.
148//!
149//! ```
150//! use handlebars::Handlebars;
151//!
152//! # fn main() {
153//! let mut handlebars = Handlebars::new();
154//! let source = "hello {{world}}";
155//!
156//! assert!(handlebars.register_template_string("t1", source).is_ok())
157//! # }
158//! ```
159//!
160//! On registration, the template is parsed, compiled and cached in the registry. So further
161//! usage will benefit from the one-time work. Also features like include, inheritance
162//! that involves template reference requires you to register those template first with
163//! a name so the registry can find it.
164//!
165//! If you template is small or just to experiment, you can use `render_template` API
166//! without registration.
167//!
168//! ```
169//! use handlebars::Handlebars;
170//! use std::collections::BTreeMap;
171//!
172//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
173//! let mut handlebars = Handlebars::new();
174//! let source = "hello {{world}}";
175//!
176//! let mut data = BTreeMap::new();
177//! data.insert("world".to_string(), "世界!".to_string());
178//! assert_eq!(handlebars.render_template(source, &data)?, "hello 世界!".to_owned());
179//! # Ok(())
180//! # }
181//! ```
182//!
183//! #### Additional features for loading template from
184//!
185//! * Feature `dir_source` enables template loading
186//!   `register_templates_directory` from given directory.
187//! * Feature `rust-embed` enables template loading
188//!   `register_embed_templates` from embedded resources in rust struct
189//!   generated with `RustEmbed`.
190//!
191//! ### Rendering Something
192//!
193//! 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.
194//!
195//! 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.
196//!
197//! 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`.
198//!
199//! ```
200//! use handlebars::Handlebars;
201//!
202//! #[derive(serde::Serialize)]
203//! struct Person {
204//!   name: String,
205//!   age: i16,
206//! }
207//!
208//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
209//! let source = "Hello, {{name}}";
210//!
211//! let mut handlebars = Handlebars::new();
212//! assert!(handlebars.register_template_string("hello", source).is_ok());
213//!
214//! let data = Person {
215//!     name: "Ning Sun".to_string(),
216//!     age: 27
217//! };
218//! assert_eq!(handlebars.render("hello", &data)?, "Hello, Ning Sun".to_owned());
219//! # Ok(())
220//! # }
221//! ```
222//!
223//! Or if you don't need the template to be cached or referenced by other ones, you can
224//! simply render it without registering.
225//!
226//! ```
227//! use handlebars::Handlebars;
228//! # #[derive(serde::Serialize)]
229//! # struct Person {
230//! #  name: String,
231//! #  age: i16,
232//! # }
233//!
234//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
235//! let source = "Hello, {{name}}";
236//!
237//! let mut handlebars = Handlebars::new();
238//!
239//! let data = Person {
240//!     name: "Ning Sun".to_string(),
241//!     age: 27
242//! };
243//! assert_eq!(
244//!   handlebars.render_template("Hello, {{name}}", &data)?,
245//!   "Hello, Ning Sun".to_owned()
246//! );
247//! # Ok(())
248//! # }
249//! ```
250//!
251//! #### Escaping
252//!
253//! 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.
254//!
255//! ### Custom Helper
256//!
257//! 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.
258//!
259//! ```
260//! use std::io::Write;
261//! use handlebars::*;
262//!
263//! // implement by a structure impls HelperDef
264//! #[derive(Clone, Copy)]
265//! struct SimpleHelper;
266//!
267//! impl HelperDef for SimpleHelper {
268//!   fn call<'reg: 'rc, 'rc>(&self, h: &Helper, _: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
269//!     let param = h.param(0).unwrap();
270//!
271//!     out.write("1st helper: ")?;
272//!     out.write(param.value().render().as_ref())?;
273//!     Ok(())
274//!   }
275//! }
276//!
277//! // implement via bare function
278//! fn another_simple_helper (h: &Helper, _: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
279//!     let param = h.param(0).unwrap();
280//!
281//!     out.write("2nd helper: ")?;
282//!     out.write(param.value().render().as_ref())?;
283//!     Ok(())
284//! }
285//!
286//!
287//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
288//! let mut handlebars = Handlebars::new();
289//! handlebars.register_helper("simple-helper", Box::new(SimpleHelper));
290//! handlebars.register_helper("another-simple-helper", Box::new(another_simple_helper));
291//! // via closure
292//! handlebars.register_helper("closure-helper",
293//!     Box::new(|h: &Helper, r: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output| -> HelperResult {
294//!         let param =
295//!         h.param(0).ok_or(RenderErrorReason::ParamNotFoundForIndex("closure-helper", 0))?;
296//!
297//!         out.write("3rd helper: ")?;
298//!         out.write(param.value().render().as_ref())?;
299//!         Ok(())
300//!     }));
301//!
302//! let tpl = "{{simple-helper 1}}\n{{another-simple-helper 2}}\n{{closure-helper 3}}";
303//! assert_eq!(
304//!   handlebars.render_template(tpl, &())?,
305//!   "1st helper: 1\n2nd helper: 2\n3rd helper: 3".to_owned()
306//! );
307//! # Ok(())
308//! # }
309//! ```
310//!
311//! Data available to helper can be found in [Helper](struct.Helper.html). And there are more
312//! examples in [`HelperDef`](trait.HelperDef.html) page.
313//!
314//! You can learn more about helpers by looking into source code of built-in helpers.
315//!
316//!
317//! ### Script Helper
318//!
319//! Like our JavaScript counterparts, handlebars allows user to define simple helpers with
320//! a scripting language, [rhai](https://docs.rs/crate/rhai/). This can be enabled by
321//! turning on `script_helper` feature flag.
322//!
323//! A sample script:
324//!
325//! ```handlebars
326//! {{percent 0.34 label="%"}}
327//! ```
328//!
329//! ```rhai
330//! // percent.rhai
331//! // get first parameter from `params` array
332//! let value = params[0];
333//! // get key  value pair `label` from `hash` map
334//! let label = hash["label"];
335//!
336//! // compute the final string presentation
337//! (value * 100).to_string() + label
338//! ```
339//!
340//! A runnable [example](https://github.com/sunng87/handlebars-rust/blob/master/examples/script.rs) can be find in the repo.
341//!
342//! #### Built-in Helpers
343//!
344//! * `{{{{raw}}}} ... {{{{/raw}}}}` escape handlebars expression within the block
345//! * `{{#if ...}} ... {{else}} ... {{/if}}` if-else block
346//!   (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#if) on how to use this helper.)
347//! * `{{#unless ...}} ... {{else}} .. {{/unless}}` if-not-else block
348//!   (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#unless) on how to use this helper.)
349//! * `{{#each ...}} ... {{/each}}` iterates over an array or object. Handlebars-rust doesn't support mustache iteration syntax so use `each` instead.
350//!   (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#each) on how to use this helper.)
351//! * `{{#with ...}} ... {{/with}}` change current context. Similar to `{{#each}}`, used for replace corresponding mustache syntax.
352//!   (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#with) on how to use this helper.)
353//! * `{{lookup ... ...}}` get value from array by `@index` or `@key`
354//!   (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#lookup) on how to use this helper.)
355//! * `{{> ...}}` include template by its name
356//! * `{{log ...}}` log value with rust logger, default level: INFO. Currently you cannot change the level.
357//! * Boolean helpers that can be used in `if` as subexpression, for example `{{#if (gt 2 1)}} ...`:
358//!   * `eq`
359//!   * `ne`
360//!   * `gt`
361//!   * `gte`
362//!   * `lt`
363//!   * `lte`
364//!   * `and`
365//!   * `or`
366//!   * `not`
367//! * `{{len ...}}` returns length of array/object/string
368//!
369//! ### Template inheritance
370//!
371//! Handlebars.js' partial system is fully supported in this implementation.
372//! Check [example](https://github.com/sunng87/handlebars-rust/blob/master/examples/partials.rs#L49) for details.
373//!
374//! ### String (or Case) Helpers
375//!
376//! [Handlebars] supports helpers for converting string cases for example converting a value to
377//! 'camelCase or 'kebab-case' etc. This can be useful during generating code using Handlebars.
378//! This can be enabled by selecting the feature-flag `string_helpers`.  Currently the case
379//! conversions from the [`heck`](https://docs.rs/heck/latest/heck) crate are supported.
380//!
381//! ```
382//! # #[cfg(feature = "string_helpers")] {
383//! use handlebars::Handlebars;
384//!
385//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
386//! let mut handlebars = Handlebars::new();
387//!
388//! let data = serde_json::json!({"value": "lower camel case"});
389//! assert_eq!(
390//!   handlebars.render_template("This is {{lowerCamelCase value}}", &data)?,
391//!   "This is lowerCamelCase".to_owned()
392//! );
393//! # Ok(())
394//! # }
395//! # }
396//! ```
397//!
398
399#![allow(dead_code, clippy::upper_case_acronyms)]
400#![warn(rust_2018_idioms)]
401#![recursion_limit = "200"]
402
403#[cfg(not(feature = "no_logging"))]
404#[macro_use]
405extern crate log;
406
407#[macro_use]
408extern crate pest_derive;
409#[cfg(test)]
410#[macro_use]
411extern crate serde_derive;
412
413#[allow(unused_imports)]
414#[macro_use]
415extern crate serde_json;
416
417pub use self::block::{BlockContext, BlockParamHolder, BlockParams};
418pub use self::context::Context;
419pub use self::decorators::DecoratorDef;
420pub use self::error::{RenderError, RenderErrorReason, TemplateError, TemplateErrorReason};
421pub use self::helpers::{HelperDef, HelperResult};
422pub use self::json::path::{Path, PathSeg};
423pub use self::json::value::{JsonRender, JsonTruthy, PathAndJson, ScopedJson, to_json};
424pub use self::local_vars::LocalVars;
425pub use self::output::{Output, StringOutput, WriteOutput};
426#[cfg(feature = "dir_source")]
427pub use self::registry::{DirectorySourceOptions, DirectorySourceOptionsBuilder};
428pub use self::registry::{EscapeFn, Registry as Handlebars, html_escape, no_escape};
429pub use self::render::{Decorator, Evaluable, Helper, RenderContext, Renderable};
430pub use self::template::Template;
431
432#[doc(hidden)]
433pub use self::serde_json::Value as JsonValue;
434
435#[macro_use]
436mod macros;
437mod block;
438mod context;
439mod decorators;
440mod error;
441mod grammar;
442mod helpers;
443mod json;
444mod local_vars;
445mod output;
446mod partial;
447mod registry;
448mod render;
449mod sources;
450mod support;
451pub mod template;
452mod util;
453
454/// Test helpers shared by this crate's tests and, behind the `testing` cargo
455/// feature, by downstream users. See `src/testing.rs`.
456#[cfg(any(test, feature = "testing"))]
457#[doc(hidden)]
458pub mod testing;