1 | use crate::context::Context; |
2 | use crate::error::RenderError; |
3 | use crate::json::value::ScopedJson; |
4 | use crate::output::Output; |
5 | use crate::registry::Registry; |
6 | use crate::render::{do_escape, Helper, RenderContext}; |
7 | |
8 | pub use self::helper_each::EACH_HELPER; |
9 | pub use self::helper_if::{IF_HELPER, UNLESS_HELPER}; |
10 | pub use self::helper_log::LOG_HELPER; |
11 | pub use self::helper_lookup::LOOKUP_HELPER; |
12 | pub use self::helper_raw::RAW_HELPER; |
13 | pub use self::helper_with::WITH_HELPER; |
14 | |
15 | /// A type alias for `Result<(), RenderError>` |
16 | pub 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 | /// |
78 | pub 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 |
146 | impl< |
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 | |
168 | mod block_util; |
169 | mod helper_each; |
170 | pub(crate) mod helper_extras; |
171 | mod helper_if; |
172 | mod helper_log; |
173 | mod helper_lookup; |
174 | mod helper_raw; |
175 | mod helper_with; |
176 | #[cfg (feature = "script_helper" )] |
177 | pub(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)] |
186 | mod 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 | |