1 | use crate::context::Context; |
2 | use crate::error::RenderError; |
3 | use crate::registry::Registry; |
4 | use crate::render::{Decorator, RenderContext}; |
5 | |
6 | pub use self::inline::INLINE_DECORATOR; |
7 | |
8 | pub type DecoratorResult = Result<(), RenderError>; |
9 | |
10 | /// Decorator Definition |
11 | /// |
12 | /// Implement this trait to define your own decorators. Currently decorator |
13 | /// shares same definition with helper. |
14 | /// |
15 | /// In handlebars, it is recommended to use decorator to change context data and update helper |
16 | /// definition. |
17 | /// ## Updating context data |
18 | /// |
19 | /// In decorator, you can change some context data you are about to render. |
20 | /// |
21 | /// ``` |
22 | /// use handlebars::*; |
23 | /// |
24 | /// fn update_data<'reg: 'rc, 'rc>(_: &Decorator, _: &Handlebars, ctx: &Context, rc: &mut RenderContext) |
25 | /// -> Result<(), RenderError> { |
26 | /// // modify json object |
27 | /// let mut new_ctx = ctx.clone(); |
28 | /// { |
29 | /// let mut data = new_ctx.data_mut(); |
30 | /// if let Some(ref mut m) = data.as_object_mut() { |
31 | /// m.insert("hello" .to_string(), to_json("world" )); |
32 | /// } |
33 | /// } |
34 | /// rc.set_context(new_ctx); |
35 | /// Ok(()) |
36 | /// } |
37 | /// |
38 | /// ``` |
39 | /// |
40 | /// ## Define local helper |
41 | /// |
42 | /// You can override behavior of a helper from position of decorator to the end of template. |
43 | /// |
44 | /// ``` |
45 | /// use handlebars::*; |
46 | /// |
47 | /// fn override_helper(_: &Decorator, _: &Handlebars, _: &Context, rc: &mut RenderContext) |
48 | /// -> Result<(), RenderError> { |
49 | /// let new_helper = |h: &Helper, _: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output| |
50 | /// -> Result<(), RenderError> { |
51 | /// // your helper logic |
52 | /// Ok(()) |
53 | /// }; |
54 | /// rc.register_local_helper("distance" , Box::new(new_helper)); |
55 | /// Ok(()) |
56 | /// } |
57 | /// ``` |
58 | /// |
59 | pub trait DecoratorDef { |
60 | fn call<'reg: 'rc, 'rc>( |
61 | &'reg self, |
62 | d: &Decorator<'reg, 'rc>, |
63 | r: &'reg Registry<'reg>, |
64 | ctx: &'rc Context, |
65 | rc: &mut RenderContext<'reg, 'rc>, |
66 | ) -> DecoratorResult; |
67 | } |
68 | |
69 | /// Implement DecoratorDef for bare function so we can use function as decorator |
70 | impl< |
71 | F: for<'reg, 'rc> Fn( |
72 | &Decorator<'reg, 'rc>, |
73 | &'reg Registry<'reg>, |
74 | &'rc Context, |
75 | &mut RenderContext<'reg, 'rc>, |
76 | ) -> DecoratorResult, |
77 | > DecoratorDef for F |
78 | { |
79 | fn call<'reg: 'rc, 'rc>( |
80 | &'reg self, |
81 | d: &Decorator<'reg, 'rc>, |
82 | reg: &'reg Registry<'reg>, |
83 | ctx: &'rc Context, |
84 | rc: &mut RenderContext<'reg, 'rc>, |
85 | ) -> DecoratorResult { |
86 | (*self)(d, reg, ctx, rc) |
87 | } |
88 | } |
89 | |
90 | mod inline; |
91 | |
92 | #[cfg (test)] |
93 | mod test { |
94 | use crate::context::Context; |
95 | use crate::error::RenderError; |
96 | use crate::json::value::{as_string, to_json}; |
97 | use crate::output::Output; |
98 | use crate::registry::Registry; |
99 | use crate::render::{Decorator, Helper, RenderContext}; |
100 | |
101 | #[test ] |
102 | fn test_register_decorator() { |
103 | let mut handlebars = Registry::new(); |
104 | handlebars |
105 | .register_template_string("t0" , "{{*foo}}" .to_string()) |
106 | .unwrap(); |
107 | |
108 | let data = json!({ |
109 | "hello" : "world" |
110 | }); |
111 | |
112 | assert!(handlebars.render("t0" , &data).is_err()); |
113 | |
114 | handlebars.register_decorator( |
115 | "foo" , |
116 | Box::new( |
117 | |_: &Decorator<'_, '_>, |
118 | _: &Registry<'_>, |
119 | _: &Context, |
120 | _: &mut RenderContext<'_, '_>| |
121 | -> Result<(), RenderError> { Ok(()) }, |
122 | ), |
123 | ); |
124 | assert_eq!(handlebars.render("t0" , &data).ok().unwrap(), "" .to_string()); |
125 | } |
126 | |
127 | // updating context data disabled for now |
128 | #[test ] |
129 | fn test_update_data_with_decorator() { |
130 | let mut handlebars = Registry::new(); |
131 | handlebars |
132 | .register_template_string("t0" , "{{hello}}{{*foo}}{{hello}}" .to_string()) |
133 | .unwrap(); |
134 | |
135 | let data = json!({ |
136 | "hello" : "world" |
137 | }); |
138 | |
139 | handlebars.register_decorator( |
140 | "foo" , |
141 | Box::new( |
142 | |_: &Decorator<'_, '_>, |
143 | _: &Registry<'_>, |
144 | ctx: &Context, |
145 | rc: &mut RenderContext<'_, '_>| |
146 | -> Result<(), RenderError> { |
147 | // modify json object |
148 | let mut new_ctx = ctx.clone(); |
149 | { |
150 | let data = new_ctx.data_mut(); |
151 | if let Some(ref mut m) = data.as_object_mut().as_mut() { |
152 | m.insert("hello" .to_string(), to_json("war" )); |
153 | } |
154 | } |
155 | rc.set_context(new_ctx); |
156 | Ok(()) |
157 | }, |
158 | ), |
159 | ); |
160 | |
161 | assert_eq!( |
162 | handlebars.render("t0" , &data).ok().unwrap(), |
163 | "worldwar" .to_string() |
164 | ); |
165 | |
166 | let data2 = 0; |
167 | handlebars.register_decorator( |
168 | "bar" , |
169 | Box::new( |
170 | |d: &Decorator<'_, '_>, |
171 | _: &Registry<'_>, |
172 | _: &Context, |
173 | rc: &mut RenderContext<'_, '_>| |
174 | -> Result<(), RenderError> { |
175 | // modify value |
176 | let v = d |
177 | .param(0) |
178 | .and_then(|v| Context::wraps(v.value()).ok()) |
179 | .unwrap_or(Context::null()); |
180 | rc.set_context(v); |
181 | Ok(()) |
182 | }, |
183 | ), |
184 | ); |
185 | handlebars |
186 | .register_template_string("t1" , "{{this}}{{*bar 1}}{{this}}" .to_string()) |
187 | .unwrap(); |
188 | assert_eq!( |
189 | handlebars.render("t1" , &data2).ok().unwrap(), |
190 | "01" .to_string() |
191 | ); |
192 | |
193 | handlebars |
194 | .register_template_string( |
195 | "t2" , |
196 | "{{this}}{{*bar \"string_literal \"}}{{this}}" .to_string(), |
197 | ) |
198 | .unwrap(); |
199 | assert_eq!( |
200 | handlebars.render("t2" , &data2).ok().unwrap(), |
201 | "0string_literal" .to_string() |
202 | ); |
203 | |
204 | handlebars |
205 | .register_template_string("t3" , "{{this}}{{*bar}}{{this}}" .to_string()) |
206 | .unwrap(); |
207 | assert_eq!( |
208 | handlebars.render("t3" , &data2).ok().unwrap(), |
209 | "0" .to_string() |
210 | ); |
211 | } |
212 | |
213 | #[test ] |
214 | fn test_local_helper_with_decorator() { |
215 | let mut handlebars = Registry::new(); |
216 | handlebars |
217 | .register_template_string( |
218 | "t0" , |
219 | "{{distance 4.5}},{{*foo \"miles \"}}{{distance 10.1}},{{*bar}}{{distance 3.4}}" |
220 | .to_string(), |
221 | ) |
222 | .unwrap(); |
223 | |
224 | handlebars.register_helper( |
225 | "distance" , |
226 | Box::new( |
227 | |h: &Helper<'_, '_>, |
228 | _: &Registry<'_>, |
229 | _: &Context, |
230 | _: &mut RenderContext<'_, '_>, |
231 | out: &mut dyn Output| |
232 | -> Result<(), RenderError> { |
233 | write!( |
234 | out, |
235 | " {}m" , |
236 | h.param(0) |
237 | .as_ref() |
238 | .map(|v| v.value()) |
239 | .unwrap_or(&to_json(0)) |
240 | )?; |
241 | Ok(()) |
242 | }, |
243 | ), |
244 | ); |
245 | handlebars.register_decorator( |
246 | "foo" , |
247 | Box::new( |
248 | |d: &Decorator<'_, '_>, |
249 | _: &Registry<'_>, |
250 | _: &Context, |
251 | rc: &mut RenderContext<'_, '_>| |
252 | -> Result<(), RenderError> { |
253 | let new_unit = d |
254 | .param(0) |
255 | .as_ref() |
256 | .and_then(|v| as_string(v.value())) |
257 | .unwrap_or("" ) |
258 | .to_owned(); |
259 | let new_helper = move |h: &Helper<'_, '_>, |
260 | _: &Registry<'_>, |
261 | _: &Context, |
262 | _: &mut RenderContext<'_, '_>, |
263 | out: &mut dyn Output| |
264 | -> Result<(), RenderError> { |
265 | write!( |
266 | out, |
267 | " {}{}" , |
268 | h.param(0) |
269 | .as_ref() |
270 | .map(|v| v.value()) |
271 | .unwrap_or(&to_json(0)), |
272 | new_unit |
273 | )?; |
274 | Ok(()) |
275 | }; |
276 | |
277 | rc.register_local_helper("distance" , Box::new(new_helper)); |
278 | Ok(()) |
279 | }, |
280 | ), |
281 | ); |
282 | handlebars.register_decorator( |
283 | "bar" , |
284 | Box::new( |
285 | |_: &Decorator<'_, '_>, |
286 | _: &Registry<'_>, |
287 | _: &Context, |
288 | rc: &mut RenderContext<'_, '_>| |
289 | -> Result<(), RenderError> { |
290 | rc.unregister_local_helper("distance" ); |
291 | Ok(()) |
292 | }, |
293 | ), |
294 | ); |
295 | assert_eq!( |
296 | handlebars.render("t0" , &0).ok().unwrap(), |
297 | "4.5m,10.1miles,3.4m" .to_owned() |
298 | ); |
299 | } |
300 | } |
301 | |