@@ -139,3 +139,210 @@ impl<'a> ToHtmlEscapingWriter<'a> {
139139 Ok ( 1 )
140140 }
141141}
142+
143+ /// Adapter interface providing `join_html` method.
144+ pub trait JoinHtml < I : Iterator > {
145+ /// Format the items of the given iterator, separated by `sep`.
146+ ///
147+ /// The formatting is done by a given template (or template-like function).
148+ ///
149+ /// # Examples
150+ ///
151+ /// ```
152+ /// use ructe::templates::{JoinHtml, Html};
153+ /// # fn main() -> std::io::Result<()> {
154+ /// assert_eq!(
155+ /// [("Rasmus", "kaj"), ("Kalle", "karl")]
156+ /// .iter()
157+ /// .join_html(
158+ /// |o, (name, user)| {
159+ /// write!(o, "<a href=\"/profile/{}\">{}</a>", user, name)
160+ /// },
161+ /// Html("<br/>\n"),
162+ /// )
163+ /// .to_buffer()?,
164+ /// "<a href=\"/profile/kaj\">Rasmus</a><br/>\
165+ /// \n<a href=\"/profile/karl\">Kalle</a>"
166+ /// );
167+ /// # Ok(())
168+ /// # }
169+ /// ```
170+ ///
171+ /// Note that the callback function is responsible for any html
172+ /// escaping of the argument.
173+ /// The closure with the write function above don't do any
174+ /// escaping, it worked fine only because the names and user-names
175+ /// in the example did not contain any characters requireing escaping.
176+ ///
177+ /// One nice way to get a function that handles escaping is to use
178+ /// a template function as the formatting callback.
179+ ///
180+ /// If the the following template is `link.rs.html`:
181+ /// ```ructe
182+ /// @((title, slug): &(&str, &str))
183+ /// <a href=\"/album/@slug\">@title</a>
184+ /// ```
185+ ///
186+ /// It can be used like this in rust code:
187+ /// ```
188+ /// # // Mock the above template
189+ /// # use std::io;
190+ /// # use ructe::templates::ToHtml;
191+ /// # fn link(o: &mut dyn io::Write, (title, slug): &(&str, &str)) -> io::Result<()> {
192+ /// # o.write_all(b"<a href=\"/album/")?;
193+ /// # slug.to_html(o)?;
194+ /// # o.write_all(b"\">")?;
195+ /// # title.to_html(o)?;
196+ /// # o.write_all(b"</a>")
197+ /// # }
198+ /// use ructe::templates::{Html, JoinHtml};
199+ /// # fn main() -> std::io::Result<()> {
200+ /// assert_eq!(
201+ /// [("Spirou & Fantasio", "spirou"), ("Tom & Jerry", "tom_jerry")]
202+ /// .iter()
203+ /// .join_html(link, Html("<br/>\n"))
204+ /// .to_buffer()?,
205+ /// "<a href=\"/album/spirou\">Spirou & Fantasio</a><br/>\
206+ /// \n<a href=\"/album/tom_jerry\">Tom & Jerry</a>"
207+ /// );
208+ /// # Ok(())
209+ /// # }
210+ /// ```
211+ ///
212+ /// Or like this in a template, giving similar result:
213+ /// ```ructe
214+ /// @use super::{link, Html, JoinHtml};
215+ ///
216+ /// @(comics: &[(&str, &str)])
217+ /// <div class="containing markup">
218+ /// @comics.iter().to_html(link, Html("<br/>"))
219+ /// </div>
220+ /// ```
221+ fn join_html <
222+ F : ' static + Fn ( & mut dyn Write , I :: Item ) -> io:: Result < ( ) > ,
223+ Sep : ' static + ToHtml ,
224+ > (
225+ self ,
226+ item_template : F ,
227+ sep : Sep ,
228+ ) -> Box < dyn ToHtml > ;
229+ }
230+
231+ /// Adapter interface providing `join_to_html` method.
232+ pub trait JoinToHtml < Item : ToHtml , I : Iterator < Item = Item > > {
233+ /// Format the items of the given iterator, separated by `sep`.
234+ ///
235+ /// # Example
236+ ///
237+ /// ```
238+ /// use ructe::templates::JoinToHtml;
239+ /// # fn main() -> std::io::Result<()> {
240+ /// assert_eq!(
241+ /// ["foo", "b<a", "baz"]
242+ /// .iter()
243+ /// .join_to_html(" & ")
244+ /// .to_buffer()?,
245+ /// "foo & b<a & baz"
246+ /// );
247+ /// # Ok(())
248+ /// # }
249+ /// ```
250+ fn join_to_html < Sep : ' static + ToHtml > ( self , sep : Sep )
251+ -> Box < dyn ToHtml > ;
252+ }
253+
254+ impl < I : ' static + Iterator + Clone > JoinHtml < I > for I {
255+ fn join_html <
256+ F : ' static + Fn ( & mut dyn Write , I :: Item ) -> io:: Result < ( ) > ,
257+ Sep : ' static + ToHtml ,
258+ > (
259+ self ,
260+ item_template : F ,
261+ sep : Sep ,
262+ ) -> Box < dyn ToHtml > {
263+ Box :: new ( HtmlJoiner {
264+ items : self ,
265+ f : item_template,
266+ sep,
267+ } )
268+ }
269+ }
270+
271+ impl < Item : ToHtml , Iter : ' static + Iterator < Item = Item > + Clone >
272+ JoinToHtml < Item , Iter > for Iter
273+ {
274+ fn join_to_html < Sep : ' static + ToHtml > (
275+ self ,
276+ sep : Sep ,
277+ ) -> Box < dyn ToHtml > {
278+ Box :: new ( HtmlJoiner {
279+ items : self ,
280+ f : |o, i| i. to_html ( o) ,
281+ sep,
282+ } )
283+ }
284+ }
285+
286+ struct HtmlJoiner <
287+ Items : Iterator + Clone ,
288+ F : Fn ( & mut dyn Write , Items :: Item ) -> io:: Result < ( ) > ,
289+ Sep : ToHtml ,
290+ > {
291+ items : Items ,
292+ f : F ,
293+ sep : Sep ,
294+ }
295+
296+ impl <
297+ Items : Iterator + Clone ,
298+ F : Fn ( & mut dyn Write , Items :: Item ) -> io:: Result < ( ) > ,
299+ Sep : ToHtml ,
300+ > ToHtml for HtmlJoiner < Items , F , Sep >
301+ {
302+ fn to_html ( & self , out : & mut dyn Write ) -> io:: Result < ( ) > {
303+ let mut iter = self . items . clone ( ) ;
304+ if let Some ( first) = iter. next ( ) {
305+ ( self . f ) ( out, first) ?;
306+ } else {
307+ return Ok ( ( ) ) ;
308+ }
309+ for item in iter {
310+ self . sep . to_html ( out) ?;
311+ ( self . f ) ( out, item) ?;
312+ }
313+ Ok ( ( ) )
314+ }
315+ }
316+
317+ #[ test]
318+ fn test_join_to_html ( ) {
319+ assert_eq ! (
320+ [ "foo" , "b<a" , "baz" ]
321+ . iter( )
322+ . join_to_html( ", " )
323+ . to_buffer( )
324+ . unwrap( ) ,
325+ "foo, b<a, baz"
326+ )
327+ }
328+
329+ #[ test]
330+ fn test_join_to_html_empty ( ) {
331+ use std:: iter:: empty;
332+ assert_eq ! ( empty:: <& str >( ) . join_to_html( ", " ) . to_buffer( ) . unwrap( ) , "" )
333+ }
334+
335+ #[ test]
336+ fn test_join_html_empty ( ) {
337+ use std:: iter:: empty;
338+ assert_eq ! (
339+ empty:: <& str >( )
340+ . join_html(
341+ |_o, _s| panic!( "The callback should never be called" ) ,
342+ ", " ,
343+ )
344+ . to_buffer( )
345+ . unwrap( ) ,
346+ ""
347+ )
348+ }
0 commit comments