1use crate::context::Context;
2use crate::error::RenderError;
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<'reg, '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<'reg, 'rc>,
94 _: &'reg Registry<'reg>,
95 _: &'rc Context,
96 _: &mut RenderContext<'reg, 'rc>,
97 ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
98 Err(RenderError::unimplemented())
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<'reg, '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<'reg, '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<'reg, '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// pub type HelperDef = for <'a, 'b, 'c> Fn<(&'a Context, &'b Helper, &'b Registry, &'c mut RenderContext), Result<String, RenderError>>;
180//
181// pub fn helper_dummy (ctx: &Context, h: &Helper, r: &Registry, rc: &mut RenderContext) -> Result<String, RenderError> {
182// h.template().unwrap().render(ctx, r, rc).unwrap()
183// }
184//
185#[cfg(test)]
186mod test {
187 use std::collections::BTreeMap;
188
189 use crate::context::Context;
190 use crate::error::RenderError;
191 use crate::helpers::HelperDef;
192 use crate::json::value::JsonRender;
193 use crate::output::Output;
194 use crate::registry::Registry;
195 use crate::render::{Helper, RenderContext, Renderable};
196
197 #[derive(Clone, Copy)]
198 struct MetaHelper;
199
200 impl HelperDef for MetaHelper {
201 fn call<'reg: 'rc, 'rc>(
202 &self,
203 h: &Helper<'reg, 'rc>,
204 r: &'reg Registry<'reg>,
205 ctx: &'rc Context,
206 rc: &mut RenderContext<'reg, 'rc>,
207 out: &mut dyn Output,
208 ) -> Result<(), RenderError> {
209 let v = h.param(0).unwrap();
210
211 write!(out, "{}:{}", h.name(), v.value().render())?;
212 if h.is_block() {
213 out.write("->")?;
214 h.template().unwrap().render(r, ctx, rc, out)?;
215 }
216 Ok(())
217 }
218 }
219
220 #[test]
221 fn test_meta_helper() {
222 let mut handlebars = Registry::new();
223 assert!(handlebars
224 .register_template_string("t0", "{{foo this}}")
225 .is_ok());
226 assert!(handlebars
227 .register_template_string("t1", "{{#bar this}}nice{{/bar}}")
228 .is_ok());
229
230 let meta_helper = MetaHelper;
231 handlebars.register_helper("helperMissing", Box::new(meta_helper));
232 handlebars.register_helper("blockHelperMissing", Box::new(meta_helper));
233
234 let r0 = handlebars.render("t0", &true);
235 assert_eq!(r0.ok().unwrap(), "foo:true".to_string());
236
237 let r1 = handlebars.render("t1", &true);
238 assert_eq!(r1.ok().unwrap(), "bar:true->nice".to_string());
239 }
240
241 #[test]
242 fn test_helper_for_subexpression() {
243 let mut handlebars = Registry::new();
244 assert!(handlebars
245 .register_template_string("t2", "{{foo value=(bar 0)}}")
246 .is_ok());
247
248 handlebars.register_helper(
249 "helperMissing",
250 Box::new(
251 |h: &Helper<'_, '_>,
252 _: &Registry<'_>,
253 _: &Context,
254 _: &mut RenderContext<'_, '_>,
255 out: &mut dyn Output|
256 -> Result<(), RenderError> {
257 write!(out, "{}{}", h.name(), h.param(0).unwrap().value())?;
258 Ok(())
259 },
260 ),
261 );
262 handlebars.register_helper(
263 "foo",
264 Box::new(
265 |h: &Helper<'_, '_>,
266 _: &Registry<'_>,
267 _: &Context,
268 _: &mut RenderContext<'_, '_>,
269 out: &mut dyn Output|
270 -> Result<(), RenderError> {
271 write!(out, "{}", h.hash_get("value").unwrap().value().render())?;
272 Ok(())
273 },
274 ),
275 );
276
277 let mut data = BTreeMap::new();
278 // handlebars should never try to lookup this value because
279 // subexpressions are now resolved as string literal
280 data.insert("bar0".to_string(), true);
281
282 let r2 = handlebars.render("t2", &data);
283
284 assert_eq!(r2.ok().unwrap(), "bar0".to_string());
285 }
286}
287