handlebars/helpers/
mod.rs

1use crate::context::Context;
2use crate::error::{RenderError, RenderErrorReason};
3use crate::json::value::ScopedJson;
4use crate::output::Output;
5use crate::registry::Registry;
6use crate::render::{do_escape, Helper, RenderContext};
7
8pub use self::helper_each::EACH_HELPER;
9pub use self::helper_if::{IF_HELPER, UNLESS_HELPER};
10pub use self::helper_log::LOG_HELPER;
11pub use self::helper_lookup::LOOKUP_HELPER;
12pub use self::helper_raw::RAW_HELPER;
13pub use self::helper_with::WITH_HELPER;
14
15/// A type alias for `Result<(), RenderError>`
16pub type HelperResult = Result<(), RenderError>;
17
18/// Helper Definition
19///
20/// Implement `HelperDef` to create custom helpers. You can retrieve useful information from these arguments.
21///
22/// * `&Helper`: current helper template information, contains name, params, hashes and nested template
23/// * `&Registry`: the global registry, you can find templates by name from registry
24/// * `&Context`: the whole data to render, in most case you can use data from `Helper`
25/// * `&mut RenderContext`: you can access data or modify variables (starts with @)/partials in render context, for example, @index of #each. See its document for detail.
26/// * `&mut dyn Output`: where you write output to
27///
28/// By default, you can use a bare function as a helper definition because we have supported unboxed_closure. If you have stateful or configurable helper, you can create a struct to implement `HelperDef`.
29///
30/// ## Define an inline helper
31///
32/// ```
33/// use handlebars::*;
34///
35/// fn upper(h: &Helper< '_>, _: &Handlebars<'_>, _: &Context, rc:
36/// &mut RenderContext<'_, '_>, out: &mut dyn Output)
37///     -> HelperResult {
38///    // get parameter from helper or throw an error
39///    let param = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
40///    out.write(param.to_uppercase().as_ref())?;
41///    Ok(())
42/// }
43/// ```
44///
45/// ## Define block helper
46///
47/// Block helper is like `#if` or `#each` which has a inner template and an optional *inverse* template (the template in else branch). You can access the inner template by `helper.template()` and `helper.inverse()`. In most cases you will just call `render` on it.
48///
49/// ```
50/// use handlebars::*;
51///
52/// fn dummy_block<'reg, 'rc>(
53///     h: &Helper<'rc>,
54///     r: &'reg Handlebars<'reg>,
55///     ctx: &'rc Context,
56///     rc: &mut RenderContext<'reg, 'rc>,
57///     out: &mut dyn Output,
58/// ) -> HelperResult {
59///     h.template()
60///         .map(|t| t.render(r, ctx, rc, out))
61///         .unwrap_or(Ok(()))
62/// }
63/// ```
64///
65/// ## Define helper function using macro
66///
67/// In most cases you just need some simple function to call from templates. We have a `handlebars_helper!` macro to simplify the job.
68///
69/// ```
70/// use handlebars::*;
71///
72/// handlebars_helper!(plus: |x: i64, y: i64| x + y);
73///
74/// let mut hbs = Handlebars::new();
75/// hbs.register_helper("plus", Box::new(plus));
76/// ```
77///
78pub trait HelperDef {
79    /// A simplified api to define helper
80    ///
81    /// To implement your own `call_inner`, you will return a new `ScopedJson`
82    /// which has a JSON value computed from current context.
83    ///
84    /// ### Calling from subexpression
85    ///
86    /// When calling the helper as a subexpression, the value and its type can
87    /// be received by upper level helpers.
88    ///
89    /// Note that the value can be `json!(null)` which is treated as `false` in
90    /// helpers like `if` and rendered as empty string.
91    fn call_inner<'reg: 'rc, 'rc>(
92        &self,
93        _: &Helper<'rc>,
94        _: &'reg Registry<'reg>,
95        _: &'rc Context,
96        _: &mut RenderContext<'reg, 'rc>,
97    ) -> Result<ScopedJson<'rc>, RenderError> {
98        Err(RenderErrorReason::Unimplemented.into())
99    }
100
101    /// A complex version of helper interface.
102    ///
103    /// This function offers `Output`, which you can write custom string into
104    /// and render child template. Helpers like `if` and `each` are implemented
105    /// with this. Because the data written into `Output` are typically without
106    /// type information. So helpers defined by this function are not composable.
107    ///
108    /// ### Calling from subexpression
109    ///
110    /// Although helpers defined by this are not composable, when called from
111    /// subexpression, handlebars tries to parse the string output as JSON to
112    /// re-build its type. This can be buggy with numrical and other literal values.
113    /// So it is not recommended to use these helpers in subexpression.
114    fn call<'reg: 'rc, 'rc>(
115        &self,
116        h: &Helper<'rc>,
117        r: &'reg Registry<'reg>,
118        ctx: &'rc Context,
119        rc: &mut RenderContext<'reg, 'rc>,
120        out: &mut dyn Output,
121    ) -> HelperResult {
122        match self.call_inner(h, r, ctx, rc) {
123            Ok(result) => {
124                if r.strict_mode() && result.is_missing() {
125                    Err(RenderError::strict_error(None))
126                } else {
127                    // auto escape according to settings
128                    let output = do_escape(r, rc, result.render());
129                    out.write(output.as_ref())?;
130                    Ok(())
131                }
132            }
133            Err(e) => {
134                if e.is_unimplemented() {
135                    // default implementation, do nothing
136                    Ok(())
137                } else {
138                    Err(e)
139                }
140            }
141        }
142    }
143}
144
145/// implement HelperDef for bare function so we can use function as helper
146impl<
147        F: for<'reg, 'rc> Fn(
148            &Helper<'rc>,
149            &'reg Registry<'reg>,
150            &'rc Context,
151            &mut RenderContext<'reg, 'rc>,
152            &mut dyn Output,
153        ) -> HelperResult,
154    > HelperDef for F
155{
156    fn call<'reg: 'rc, 'rc>(
157        &self,
158        h: &Helper<'rc>,
159        r: &'reg Registry<'reg>,
160        ctx: &'rc Context,
161        rc: &mut RenderContext<'reg, 'rc>,
162        out: &mut dyn Output,
163    ) -> HelperResult {
164        (*self)(h, r, ctx, rc, out)
165    }
166}
167
168mod block_util;
169mod helper_each;
170pub(crate) mod helper_extras;
171mod helper_if;
172mod helper_log;
173mod helper_lookup;
174mod helper_raw;
175mod helper_with;
176#[cfg(feature = "script_helper")]
177pub(crate) mod scripting;
178
179#[cfg(feature = "string_helpers")]
180pub(crate) mod string_helpers;
181
182// pub type HelperDef = for <'a, 'b, 'c> Fn<(&'a Context, &'b Helper, &'b Registry, &'c mut RenderContext), Result<String, RenderError>>;
183//
184// pub fn helper_dummy (ctx: &Context, h: &Helper, r: &Registry, rc: &mut RenderContext) -> Result<String, RenderError> {
185// h.template().unwrap().render(ctx, r, rc).unwrap()
186// }
187//
188#[cfg(test)]
189mod test {
190    use std::collections::BTreeMap;
191
192    use crate::context::Context;
193    use crate::error::RenderError;
194    use crate::helpers::HelperDef;
195    use crate::json::value::JsonRender;
196    use crate::output::Output;
197    use crate::registry::Registry;
198    use crate::render::{Helper, RenderContext, Renderable};
199
200    #[derive(Clone, Copy)]
201    struct MetaHelper;
202
203    impl HelperDef for MetaHelper {
204        fn call<'reg: 'rc, 'rc>(
205            &self,
206            h: &Helper<'rc>,
207            r: &'reg Registry<'reg>,
208            ctx: &'rc Context,
209            rc: &mut RenderContext<'reg, 'rc>,
210            out: &mut dyn Output,
211        ) -> Result<(), RenderError> {
212            let v = h.param(0).unwrap();
213
214            write!(out, "{}:{}", h.name(), v.value().render())?;
215            if h.is_block() {
216                out.write("->")?;
217                h.template().unwrap().render(r, ctx, rc, out)?;
218            }
219            Ok(())
220        }
221    }
222
223    #[test]
224    fn test_meta_helper() {
225        let mut handlebars = Registry::new();
226        assert!(handlebars
227            .register_template_string("t0", "{{foo this}}")
228            .is_ok());
229        assert!(handlebars
230            .register_template_string("t1", "{{#bar this}}nice{{/bar}}")
231            .is_ok());
232
233        let meta_helper = MetaHelper;
234        handlebars.register_helper("helperMissing", Box::new(meta_helper));
235        handlebars.register_helper("blockHelperMissing", Box::new(meta_helper));
236
237        let r0 = handlebars.render("t0", &true);
238        assert_eq!(r0.ok().unwrap(), "foo:true".to_string());
239
240        let r1 = handlebars.render("t1", &true);
241        assert_eq!(r1.ok().unwrap(), "bar:true->nice".to_string());
242    }
243
244    #[test]
245    fn test_helper_for_subexpression() {
246        let mut handlebars = Registry::new();
247        assert!(handlebars
248            .register_template_string("t2", "{{foo value=(bar 0)}}")
249            .is_ok());
250
251        handlebars.register_helper(
252            "helperMissing",
253            Box::new(
254                |h: &Helper<'_>,
255                 _: &Registry<'_>,
256                 _: &Context,
257                 _: &mut RenderContext<'_, '_>,
258                 out: &mut dyn Output|
259                 -> Result<(), RenderError> {
260                    write!(out, "{}{}", h.name(), h.param(0).unwrap().value())?;
261                    Ok(())
262                },
263            ),
264        );
265        handlebars.register_helper(
266            "foo",
267            Box::new(
268                |h: &Helper<'_>,
269                 _: &Registry<'_>,
270                 _: &Context,
271                 _: &mut RenderContext<'_, '_>,
272                 out: &mut dyn Output|
273                 -> Result<(), RenderError> {
274                    write!(out, "{}", h.hash_get("value").unwrap().value().render())?;
275                    Ok(())
276                },
277            ),
278        );
279
280        let mut data = BTreeMap::new();
281        // handlebars should never try to lookup this value because
282        // subexpressions are now resolved as string literal
283        data.insert("bar0".to_string(), true);
284
285        let r2 = handlebars.render("t2", &data);
286
287        assert_eq!(r2.ok().unwrap(), "bar0".to_string());
288    }
289}