1#![deny(clippy::all)]
67#![deny(missing_docs)]
68
69use handlebars::*;
70
71#[derive(Clone, Copy)]
80pub struct RepeatHelper;
81
82impl HelperDef for RepeatHelper {
83 fn call<'reg: 'rc, 'rc>(
84 &self,
85 h: &Helper<'reg, 'rc>,
86 r: &'reg Handlebars<'reg>,
87 ctx: &'rc Context,
88 rc: &mut RenderContext<'reg, 'rc>,
89 out: &mut dyn Output,
90 ) -> HelperResult {
91 let value = h
92 .param(0)
93 .ok_or_else(|| RenderError::new("`repeat` helper: cannot read parameter `count`"))?
94 .value();
95
96 let count = value.as_u64().ok_or_else(|| {
97 RenderError::new(format!(
98 "`repeat` helper: received {:?} instead of u64",
99 value
100 ))
101 })?;
102
103 let template = h
104 .template()
105 .ok_or_else(|| RenderError::new("`repeat` helper: missing block"))?;
106
107 for i in 0..count {
108 let mut block = rc.block().cloned().unwrap_or_default();
109 block.set_local_var("index", i.into());
110 block.set_local_var("first", (i == 0).into());
111 block.set_local_var("last", (i == count - 1).into());
112 rc.push_block(block);
113
114 template.render(r, ctx, rc, out)?;
115
116 rc.pop_block();
117 }
118
119 if count == 0 {
120 if let Some(template) = h.inverse() {
121 template.render(r, ctx, rc, out)?;
122 }
123 }
124
125 Ok(())
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use rstest::*;
133 use serde_json::json;
134
135 const T: &str =
136 "{{#repeat count}}{{name}}:{{@index}}:{{@first}}:{{@last}} {{else}}bar{{/repeat}}";
137
138 #[inline]
139 fn render(template: &str, count: u64) -> Result<String, RenderError> {
140 let data = json!({"name": "foo", "count": count});
141
142 let mut reg = Handlebars::new();
143 reg.register_helper("repeat", Box::new(RepeatHelper));
144 reg.render_template(template, &data)
145 }
146
147 #[rstest]
148 #[case(0, "bar")]
149 #[case(1, "foo:0:true:true ")]
150 #[case(2, "foo:0:true:false foo:1:false:true ")]
151 #[case(3, "foo:0:true:false foo:1:false:false foo:2:false:true ")]
152 fn success(#[case] count: u64, #[case] output: &str) {
153 assert_eq!(render(T, count).unwrap(), output);
154 }
155
156 #[rstest]
157 #[case(0)]
158 #[case(1)]
159 #[case(2)]
160 #[case(3)]
161 fn missing_arg(#[case] count: u64) {
162 let template = "{{#repeat}}{{name}}{{/repeat}}";
163 let err = render(template, count).unwrap_err();
164 assert!(err.desc.contains("cannot read parameter"))
165 }
166
167 #[rstest]
168 #[case(0)]
169 #[case(1)]
170 #[case(2)]
171 #[case(3)]
172 fn wrong_arg_type(#[case] count: u64) {
173 let template = "{{#repeat \"foo\"}}{{name}}{{/repeat}}";
174 let err = render(template, count).unwrap_err();
175 assert!(err.desc.contains("instead of u64"))
176 }
177
178 #[rstest]
179 #[case(0)]
180 #[case(1)]
181 #[case(2)]
182 #[case(3)]
183 fn missing_block(#[case] count: u64) {
184 let template = "{{repeat count}}";
185 let err = render(template, count).unwrap_err();
186 assert!(err.desc.contains("missing block"))
187 }
188}