]>
Commit | Line | Data |
---|---|---|
3dfed10e | 1 | #![doc(html_root_url = "https://docs.rs/handlebars/3.4.0")] |
8bb4bdeb XL |
2 | //! # Handlebars |
3 | //! | |
4 | //! [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). | |
5 | //! | |
6 | //! And this is handlebars Rust implementation, designed for general purpose text generation. | |
7 | //! | |
8 | //! ## Quick Start | |
9 | //! | |
10 | //! ``` | |
11 | //! use std::collections::BTreeMap; | |
12 | //! use handlebars::Handlebars; | |
13 | //! | |
14 | //! fn main() { | |
15 | //! // create the handlebars registry | |
16 | //! let mut handlebars = Handlebars::new(); | |
17 | //! | |
18 | //! // register the template. The template string will be verified and compiled. | |
19 | //! let source = "hello {{world}}"; | |
20 | //! assert!(handlebars.register_template_string("t1", source).is_ok()); | |
21 | //! | |
22 | //! // Prepare some data. | |
23 | //! // | |
041b39d2 | 24 | //! // The data type should implements `serde::Serialize` |
8bb4bdeb XL |
25 | //! let mut data = BTreeMap::new(); |
26 | //! data.insert("world".to_string(), "世界!".to_string()); | |
27 | //! assert_eq!(handlebars.render("t1", &data).unwrap(), "hello 世界!"); | |
28 | //! } | |
29 | //! ``` | |
30 | //! | |
31 | //! In this example, we created a template registry and registered a template named `t1`. | |
32 | //! Then we rendered a `BTreeMap` with an entry of key `world`, the result is just what | |
33 | //! we expected. | |
34 | //! | |
35 | //! I recommend you to walk through handlebars.js' [intro page](http://handlebarsjs.com) | |
36 | //! if you are not quite familiar with the template language itself. | |
37 | //! | |
f9f354fc | 38 | //! ## Features |
8bb4bdeb XL |
39 | //! |
40 | //! Handlebars is a real-world templating system that you can use to build | |
41 | //! your application without pain. | |
42 | //! | |
f9f354fc | 43 | //! ### Isolation of Rust and HTML |
8bb4bdeb XL |
44 | //! |
45 | //! This library doesn't attempt to use some macro magic to allow you to | |
f9f354fc | 46 | //! write your template within your rust code. I admit that it's fun to do |
3dfed10e | 47 | //! that but it doesn't fit real-world use cases. |
8bb4bdeb | 48 | //! |
3dfed10e | 49 | //! ### Limited but essential control structures built-in |
8bb4bdeb | 50 | //! |
3dfed10e XL |
51 | //! Only essential control directives `if` and `each` are built-in. This |
52 | //! prevents you from putting too much application logic into your template. | |
8bb4bdeb | 53 | //! |
f9f354fc | 54 | //! ### Extensible helper system |
8bb4bdeb XL |
55 | //! |
56 | //! You can write your own helper with Rust! It can be a block helper or | |
3dfed10e | 57 | //! inline helper. Put your logic into the helper and don't repeat |
8bb4bdeb XL |
58 | //! yourself. |
59 | //! | |
f9f354fc XL |
60 | //! The built-in helpers like `if` and `each` were written with these |
61 | //! helper APIs and the APIs are fully available to developers. | |
62 | //! | |
63 | //! ### Template inheritance | |
8bb4bdeb XL |
64 | //! |
65 | //! Every time I look into a templating system, I will investigate its | |
83c7162d XL |
66 | //! support for [template inheritance][t]. |
67 | //! | |
68 | //! [t]: https://docs.djangoproject.com/en/1.9/ref/templates/language/#template-inheritance | |
8bb4bdeb | 69 | //! |
3dfed10e XL |
70 | //! Template include is not sufficient for template reuse. In most cases |
71 | //! you will need a skeleton of page as parent (header, footer, etc.), and | |
72 | //! embed your page into this parent. | |
8bb4bdeb | 73 | //! |
3dfed10e XL |
74 | //! You can find a real example of template inheritance in |
75 | //! `examples/partials.rs` and templates used by this file. | |
8bb4bdeb | 76 | //! |
f9f354fc | 77 | //! ### Strict mode |
83c7162d XL |
78 | //! |
79 | //! Handlebars, the language designed to work with JavaScript, has no | |
3dfed10e XL |
80 | //! strict restriction on accessing nonexistent fields or indexes. It |
81 | //! generates empty strings for such cases. However, in Rust we want to be | |
82 | //! a little stricter sometimes. | |
83c7162d | 83 | //! |
9fa01778 | 84 | //! By enabling `strict_mode` on handlebars: |
83c7162d XL |
85 | //! |
86 | //! ``` | |
87 | //! # use handlebars::Handlebars; | |
88 | //! # let mut handlebars = Handlebars::new(); | |
89 | //! handlebars.set_strict_mode(true); | |
90 | //! ``` | |
91 | //! | |
3dfed10e | 92 | //! You will get a `RenderError` when accessing fields that do not exist. |
83c7162d | 93 | //! |
f9f354fc | 94 | //! ## Limitations |
8bb4bdeb | 95 | //! |
f9f354fc | 96 | //! ### Compatibility with original JavaScript version |
8bb4bdeb | 97 | //! |
3dfed10e | 98 | //! This implementation is **not fully compatible** with the original JavaScript version. |
8bb4bdeb | 99 | //! |
3dfed10e XL |
100 | //! First of all, mustache blocks are not supported. I suggest you to use `#if` and `#each` for |
101 | //! the same functionality. | |
8bb4bdeb XL |
102 | //! |
103 | //! There are some other minor features missing: | |
104 | //! | |
105 | //! * Chained else [#12](https://github.com/sunng87/handlebars-rust/issues/12) | |
106 | //! | |
3dfed10e | 107 | //! Feel free to file an issue on [github](https://github.com/sunng87/handlebars-rust/issues) if |
8bb4bdeb XL |
108 | //! you find missing features. |
109 | //! | |
f9f354fc | 110 | //! ### Types |
8bb4bdeb XL |
111 | //! |
112 | //! As a static typed language, it's a little verbose to use handlebars. | |
041b39d2 | 113 | //! Handlebars templating language is designed against JSON data type. In rust, |
3dfed10e XL |
114 | //! we will convert user's structs, vectors or maps into Serde-Json's `Value` type |
115 | //! in order to use in templates. You have to make sure your data implements the | |
f9f354fc | 116 | //! `Serialize` trait from the [Serde](https://serde.rs) project. |
8bb4bdeb XL |
117 | //! |
118 | //! ## Usage | |
119 | //! | |
120 | //! ### Template Creation and Registration | |
121 | //! | |
3dfed10e | 122 | //! Templates are created from `String`s and registered to `Handlebars` with a name. |
8bb4bdeb XL |
123 | //! |
124 | //! ``` | |
9fa01778 | 125 | //! # extern crate handlebars; |
8bb4bdeb XL |
126 | //! |
127 | //! use handlebars::Handlebars; | |
128 | //! | |
9fa01778 | 129 | //! # fn main() { |
8bb4bdeb XL |
130 | //! let mut handlebars = Handlebars::new(); |
131 | //! let source = "hello {{world}}"; | |
132 | //! | |
133 | //! assert!(handlebars.register_template_string("t1", source).is_ok()) | |
9fa01778 | 134 | //! # } |
8bb4bdeb XL |
135 | //! ``` |
136 | //! | |
9fa01778 XL |
137 | //! On registration, the template is parsed, compiled and cached in the registry. So further |
138 | //! usage will benefit from the one-time work. Also features like include, inheritance | |
8bb4bdeb XL |
139 | //! that involves template reference requires you to register those template first with |
140 | //! a name so the registry can find it. | |
141 | //! | |
9fa01778 | 142 | //! If you template is small or just to experiment, you can use `render_template` API |
8bb4bdeb XL |
143 | //! without registration. |
144 | //! | |
145 | //! ``` | |
9fa01778 | 146 | //! # use std::error::Error; |
8bb4bdeb XL |
147 | //! use handlebars::Handlebars; |
148 | //! use std::collections::BTreeMap; | |
149 | //! | |
9fa01778 | 150 | //! # fn main() -> Result<(), Box<Error>> { |
8bb4bdeb XL |
151 | //! let mut handlebars = Handlebars::new(); |
152 | //! let source = "hello {{world}}"; | |
153 | //! | |
154 | //! let mut data = BTreeMap::new(); | |
155 | //! data.insert("world".to_string(), "世界!".to_string()); | |
9fa01778 XL |
156 | //! assert_eq!(handlebars.render_template(source, &data)?, "hello 世界!".to_owned()); |
157 | //! # Ok(()) | |
158 | //! # } | |
8bb4bdeb XL |
159 | //! ``` |
160 | //! | |
161 | //! ### Rendering Something | |
162 | //! | |
041b39d2 | 163 | //! 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. |
8bb4bdeb | 164 | //! |
041b39d2 | 165 | //! 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. |
8bb4bdeb | 166 | //! |
3dfed10e | 167 | //! 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`. |
8bb4bdeb | 168 | //! |
9fa01778 XL |
169 | //! ``` |
170 | //! # use std::error::Error; | |
171 | //! # #[macro_use] | |
172 | //! # extern crate serde_derive; | |
173 | //! # extern crate handlebars; | |
8bb4bdeb XL |
174 | //! |
175 | //! use handlebars::Handlebars; | |
176 | //! | |
041b39d2 | 177 | //! #[derive(Serialize)] |
8bb4bdeb XL |
178 | //! struct Person { |
179 | //! name: String, | |
180 | //! age: i16, | |
181 | //! } | |
182 | //! | |
9fa01778 | 183 | //! # fn main() -> Result<(), Box<Error>> { |
8bb4bdeb XL |
184 | //! let source = "Hello, {{name}}"; |
185 | //! | |
186 | //! let mut handlebars = Handlebars::new(); | |
187 | //! assert!(handlebars.register_template_string("hello", source).is_ok()); | |
188 | //! | |
189 | //! | |
190 | //! let data = Person { | |
191 | //! name: "Ning Sun".to_string(), | |
192 | //! age: 27 | |
193 | //! }; | |
9fa01778 XL |
194 | //! assert_eq!(handlebars.render("hello", &data)?, "Hello, Ning Sun".to_owned()); |
195 | //! # Ok(()) | |
196 | //! # } | |
197 | //! # | |
8bb4bdeb XL |
198 | //! ``` |
199 | //! | |
200 | //! Or if you don't need the template to be cached or referenced by other ones, you can | |
201 | //! simply render it without registering. | |
202 | //! | |
9fa01778 XL |
203 | //! ``` |
204 | //! # use std::error::Error; | |
205 | //! # #[macro_use] | |
206 | //! # extern crate serde_derive; | |
207 | //! # extern crate handlebars; | |
208 | //! use handlebars::Handlebars; | |
209 | //! # #[derive(Serialize)] | |
210 | //! # struct Person { | |
211 | //! # name: String, | |
212 | //! # age: i16, | |
213 | //! # } | |
214 | //! | |
416331ca | 215 | //! # fn main() -> Result<(), Box<dyn Error>> { |
8bb4bdeb XL |
216 | //! let source = "Hello, {{name}}"; |
217 | //! | |
218 | //! let mut handlebars = Handlebars::new(); | |
219 | //! | |
220 | //! let data = Person { | |
221 | //! name: "Ning Sun".to_string(), | |
222 | //! age: 27 | |
223 | //! }; | |
9fa01778 | 224 | //! assert_eq!(handlebars.render_template("Hello, {{name}}", &data)?, |
8bb4bdeb | 225 | //! "Hello, Ning Sun".to_owned()); |
9fa01778 XL |
226 | //! # Ok(()) |
227 | //! # } | |
8bb4bdeb XL |
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. | |
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; | |
9fa01778 XL |
240 | //! # use std::error::Error; |
241 | //! use handlebars::{Handlebars, HelperDef, RenderContext, Helper, Context, JsonRender, HelperResult, Output, RenderError}; | |
8bb4bdeb XL |
242 | //! |
243 | //! // implement by a structure impls HelperDef | |
244 | //! #[derive(Clone, Copy)] | |
245 | //! struct SimpleHelper; | |
246 | //! | |
247 | //! impl HelperDef for SimpleHelper { | |
416331ca | 248 | //! fn call<'reg: 'rc, 'rc>(&self, h: &Helper, _: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output) -> HelperResult { |
8bb4bdeb XL |
249 | //! let param = h.param(0).unwrap(); |
250 | //! | |
9fa01778 XL |
251 | //! out.write("1st helper: ")?; |
252 | //! out.write(param.value().render().as_ref())?; | |
8bb4bdeb XL |
253 | //! Ok(()) |
254 | //! } | |
255 | //! } | |
256 | //! | |
257 | //! // implement via bare function | |
416331ca | 258 | //! fn another_simple_helper (h: &Helper, _: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output) -> HelperResult { |
8bb4bdeb XL |
259 | //! let param = h.param(0).unwrap(); |
260 | //! | |
9fa01778 XL |
261 | //! out.write("2nd helper: ")?; |
262 | //! out.write(param.value().render().as_ref())?; | |
8bb4bdeb XL |
263 | //! Ok(()) |
264 | //! } | |
265 | //! | |
266 | //! | |
416331ca | 267 | //! # fn main() -> Result<(), Box<dyn Error>> { |
8bb4bdeb XL |
268 | //! let mut handlebars = Handlebars::new(); |
269 | //! handlebars.register_helper("simple-helper", Box::new(SimpleHelper)); | |
270 | //! handlebars.register_helper("another-simple-helper", Box::new(another_simple_helper)); | |
271 | //! // via closure | |
272 | //! handlebars.register_helper("closure-helper", | |
416331ca | 273 | //! Box::new(|h: &Helper, r: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output| -> HelperResult { |
9fa01778 | 274 | //! let param = h.param(0).ok_or(RenderError::new("param not found"))?; |
8bb4bdeb | 275 | //! |
9fa01778 XL |
276 | //! out.write("3rd helper: ")?; |
277 | //! out.write(param.value().render().as_ref())?; | |
8bb4bdeb XL |
278 | //! Ok(()) |
279 | //! })); | |
280 | //! | |
281 | //! let tpl = "{{simple-helper 1}}\n{{another-simple-helper 2}}\n{{closure-helper 3}}"; | |
9fa01778 | 282 | //! assert_eq!(handlebars.render_template(tpl, &())?, |
8bb4bdeb | 283 | //! "1st helper: 1\n2nd helper: 2\n3rd helper: 3".to_owned()); |
9fa01778 XL |
284 | //! # Ok(()) |
285 | //! # } | |
286 | //! | |
8bb4bdeb | 287 | //! ``` |
3dfed10e | 288 | //! |
8bb4bdeb XL |
289 | //! Data available to helper can be found in [Helper](struct.Helper.html). And there are more |
290 | //! examples in [HelperDef](trait.HelperDef.html) page. | |
291 | //! | |
292 | //! You can learn more about helpers by looking into source code of built-in helpers. | |
293 | //! | |
3dfed10e XL |
294 | //! |
295 | //! ### Script Helper | |
296 | //! | |
297 | //! Like our JavaScript counterparts, handlebars allows user to define simple helpers with | |
298 | //! a scripting language, [rhai](https://docs.rs/crate/rhai/). This can be enabled by | |
299 | //! turning on `script_helper` feature flag. | |
300 | //! | |
301 | //! A sample script: | |
302 | //! | |
303 | //! ```handlebars | |
304 | //! {{percent 0.34 label="%"}} | |
305 | //! ``` | |
306 | //! | |
307 | //! ```rhai | |
308 | //! // percent.rhai | |
309 | //! // get first parameter from `params` array | |
310 | //! let value = params[0]; | |
311 | //! // get key value pair `label` from `hash` map | |
312 | //! let label = hash["label"]; | |
313 | //! | |
314 | //! // compute the final string presentation | |
315 | //! (value * 100).to_string() + label | |
316 | //! ``` | |
317 | //! | |
318 | //! A runnable [example](https://github.com/sunng87/handlebars-rust/blob/master/examples/script.rs) can be find in the repo. | |
319 | //! | |
8bb4bdeb XL |
320 | //! #### Built-in Helpers |
321 | //! | |
416331ca | 322 | //! * `{{{{raw}}}} ... {{{{/raw}}}}` escape handlebars expression within the block |
8bb4bdeb XL |
323 | //! * `{{#if ...}} ... {{else}} ... {{/if}}` if-else block |
324 | //! * `{{#unless ...}} ... {{else}} .. {{/unless}}` if-not-else block | |
3dfed10e XL |
325 | //! * `{{#each ...}} ... {{/each}}` iterates over an array or object. Handlebars-rust doesn't support mustache iteration syntax so use this instead. |
326 | //! * `{{#with ...}} ... {{/with}}` change current context. Similar to `{{#each}}`, used for replace corresponding mustache syntax. | |
8bb4bdeb XL |
327 | //! * `{{lookup ... ...}}` get value from array by `@index` or `@key` |
328 | //! * `{{> ...}}` include template with name | |
329 | //! * `{{log ...}}` log value with rust logger, default level: INFO. Currently you cannot change the level. | |
9fa01778 XL |
330 | //! * Boolean helpers that can be used in `if` as subexpression, for example `{{#if (gt 2 1)}} ...`: |
331 | //! * `eq` | |
332 | //! * `ne` | |
333 | //! * `gt` | |
334 | //! * `gte` | |
335 | //! * `lt` | |
336 | //! * `lte` | |
337 | //! * `and` | |
338 | //! * `or` | |
339 | //! * `not` | |
8bb4bdeb XL |
340 | //! |
341 | //! ### Template inheritance | |
342 | //! | |
9fa01778 XL |
343 | //! Handlebars.js' partial system is fully supported in this implementation. |
344 | //! Check [example](https://github.com/sunng87/handlebars-rust/blob/master/examples/partials.rs#L49) for details. | |
8bb4bdeb XL |
345 | //! |
346 | //! | |
347 | ||
348 | #![allow(dead_code)] | |
3dfed10e | 349 | #![warn(rust_2018_idioms)] |
8bb4bdeb XL |
350 | #![recursion_limit = "200"] |
351 | ||
9fa01778 | 352 | #[cfg(not(feature = "no_logging"))] |
8bb4bdeb | 353 | #[macro_use] |
83c7162d | 354 | extern crate log; |
9fa01778 | 355 | |
83c7162d | 356 | #[cfg(test)] |
8bb4bdeb | 357 | #[macro_use] |
83c7162d | 358 | extern crate maplit; |
8bb4bdeb | 359 | #[macro_use] |
83c7162d | 360 | extern crate pest_derive; |
8bb4bdeb | 361 | #[macro_use] |
83c7162d | 362 | extern crate quick_error; |
041b39d2 XL |
363 | #[cfg(test)] |
364 | #[macro_use] | |
365 | extern crate serde_derive; | |
8bb4bdeb | 366 | |
041b39d2 XL |
367 | #[allow(unused_imports)] |
368 | #[macro_use] | |
8bb4bdeb XL |
369 | extern crate serde_json; |
370 | ||
f9f354fc XL |
371 | pub use self::block::{BlockContext, BlockParams}; |
372 | pub use self::context::Context; | |
373 | pub use self::decorators::DecoratorDef; | |
ea8adc8c | 374 | pub use self::error::{RenderError, TemplateError, TemplateFileError, TemplateRenderError}; |
83c7162d | 375 | pub use self::helpers::{HelperDef, HelperResult}; |
f9f354fc XL |
376 | pub use self::json::path::Path; |
377 | pub use self::json::value::{to_json, JsonRender, PathAndJson, ScopedJson}; | |
9fa01778 XL |
378 | pub use self::output::Output; |
379 | pub use self::registry::{html_escape, no_escape, EscapeFn, Registry as Handlebars}; | |
f9f354fc | 380 | pub use self::render::{Decorator, Evaluable, Helper, RenderContext, Renderable}; |
9fa01778 | 381 | pub use self::template::Template; |
8bb4bdeb | 382 | |
9fa01778 XL |
383 | #[doc(hidden)] |
384 | pub use self::serde_json::Value as JsonValue; | |
385 | ||
386 | #[macro_use] | |
387 | mod macros; | |
f9f354fc | 388 | mod block; |
9fa01778 | 389 | mod context; |
f9f354fc | 390 | mod decorators; |
8bb4bdeb | 391 | mod error; |
9fa01778 XL |
392 | mod grammar; |
393 | mod helpers; | |
f9f354fc | 394 | mod json; |
9fa01778 XL |
395 | mod output; |
396 | mod partial; | |
8bb4bdeb XL |
397 | mod registry; |
398 | mod render; | |
8bb4bdeb | 399 | mod support; |
9fa01778 | 400 | pub mod template; |
f9f354fc | 401 | mod util; |