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