horrorshow/
template.rs

1use core::fmt;
2
3#[cfg(feature = "alloc")]
4use alloc::string::String;
5
6#[cfg(feature = "std")]
7use std::io;
8
9use crate::error::{self, Error};
10use crate::render::RenderOnce;
11
12/// A template that can be rendered into something.
13///
14/// Don't let the single impl below fool you, these methods are available on all `Render*`'s
15/// (through impls on references and boxes).
16pub trait Template: RenderOnce + Sized {
17    /// Render this into a new String.
18    ///
19    /// FEATURE: requires "alloc".
20    #[cfg(feature = "alloc")]
21    fn into_string(self) -> Result<String, Error> {
22        let mut string = String::with_capacity(self.size_hint());
23        self.write_to_string(&mut string)?;
24        string.shrink_to_fit();
25        Ok(string)
26    }
27
28    /// Render this into an existing String.
29    ///
30    /// Note: You could also use render_into_fmt but this is noticeably faster.
31    ///
32    /// FEATURE: requires "alloc".
33    #[cfg(feature = "alloc")]
34    fn write_to_string(self, string: &mut String) -> Result<(), Error> {
35        let mut buffer = TemplateBuffer {
36            writer: InnerTemplateWriter::Str(string),
37            error: Default::default(),
38        };
39        self.render_once(&mut buffer);
40        buffer.into_result()
41    }
42
43    /// Render this into something that implements fmt::Write.
44    ///
45    /// FnRenderer also implements Display but that's about twice as slow...
46    fn write_to_fmt(self, writer: &mut dyn fmt::Write) -> Result<(), Error> {
47        let mut buffer = TemplateBuffer {
48            writer: InnerTemplateWriter::Fmt(writer),
49            error: Default::default(),
50        };
51        self.render_once(&mut buffer);
52        buffer.into_result()
53    }
54
55    /// Render this into something that implements io::Write.
56    ///
57    /// Note: If you're writing directly to a file/socket etc., you should *seriously* consider
58    /// wrapping your writer in a BufWriter. Otherwise, you'll end up making quite a few unnecessary
59    /// system calls.
60    ///
61    /// FEATURE: requires "std".
62    #[cfg(feature = "std")]
63    fn write_to_io(self, writer: &mut dyn io::Write) -> Result<(), Error> {
64        let mut buffer = TemplateBuffer {
65            writer: InnerTemplateWriter::Io(writer),
66            error: Default::default(),
67        };
68        self.render_once(&mut buffer);
69        buffer.into_result()
70    }
71}
72
73impl<T: RenderOnce + Sized> Template for T {}
74
75/// A template buffer. This is the type that gets passed to closures inside templates.
76///
77/// Example:
78///
79/// ```
80/// # #[macro_use] extern crate horrorshow;
81/// # fn main() {
82///     html! {
83///         |tmpl /*: &mut TemplateBuffer */| tmpl << "Some String";
84///     };
85/// # }
86/// ```
87pub struct TemplateBuffer<'a> {
88    writer: InnerTemplateWriter<'a>,
89    error: Error,
90}
91
92enum InnerTemplateWriter<'a> {
93    Fmt(&'a mut dyn fmt::Write),
94    #[cfg(feature = "alloc")]
95    Str(&'a mut String),
96    #[cfg(feature = "std")]
97    Io(&'a mut dyn io::Write),
98}
99
100impl<'a> TemplateBuffer<'a> {
101    /// Record an error. If a template calls this function, rendering will be
102    /// short-circuited and the error will be returned to the user.
103    ///
104    /// FEATURE:
105    ///
106    ///  * With "alloc" but without "std", accepts anything that can be
107    ///    converted to a string (anything implementing `ToString`).
108    ///  * Without "std" _or_ "alloc", accepts a static `&str`. Multiple calls
109    ///    will record the _first_ error.
110    #[cold]
111    #[cfg(feature = "std")]
112    pub fn record_error<E: Into<Box<dyn std::error::Error + Send + Sync>>>(&mut self, e: E) {
113        self.error.render.push(e.into());
114    }
115
116    #[cold]
117    #[cfg(all(not(feature = "std"), feature = "alloc"))]
118    pub fn record_error<E: alloc::string::ToString>(&mut self, e: E) {
119        self.error.render.push(e.to_string());
120    }
121
122    #[cold]
123    #[cfg(not(feature = "alloc"))]
124    pub fn record_error(&mut self, e: &'static str) {
125        if self.error.render.is_none() {
126            self.error.render = Some(e);
127        }
128    }
129
130    /// Write a raw string to the template output.
131    // NEVER REMOVE THIS INLINE!
132    #[inline(always)]
133    pub fn write_raw(&mut self, text: &str) {
134        use alloc::fmt::Write;
135        let _ = self.as_raw_writer().write_str(text);
136    }
137
138    /// Escape and write the formatted arguments to the template output.
139    ///
140    /// Example:
141    ///
142    /// ```
143    /// # #[macro_use] extern crate horrorshow;
144    /// # use horrorshow::prelude::*;
145    /// # fn main() {
146    /// #     let result = html! {
147    /// #         |tmpl| {
148    /// write!(tmpl, "{} + {}", 0, 1);
149    /// #         }
150    /// #     };
151    /// #     assert_eq!(result.into_string().unwrap(), "0 + 1");
152    /// # }
153    /// ```
154    #[inline]
155    pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) {
156        use alloc::fmt::Write;
157        let _ = self.as_writer().write_fmt(args);
158    }
159
160    /// Escape and write a string to the template output.
161    #[inline]
162    pub fn write_str(&mut self, text: &str) {
163        use alloc::fmt::Write;
164        let _ = self.as_writer().write_str(text);
165    }
166
167    /// Returns an escaping Write adapter.
168    #[inline]
169    pub fn as_writer<'b>(&'b mut self) -> TemplateWriter<'a, 'b> {
170        TemplateWriter(self)
171    }
172
173    /// Returns a non-escaping Write adapter.
174    #[inline]
175    pub fn as_raw_writer<'b>(&'b mut self) -> RawTemplateWriter<'a, 'b> {
176        RawTemplateWriter(self)
177    }
178
179    fn into_result(self) -> Result<(), Error> {
180        if error::is_empty(&self.error) {
181            Ok(())
182        } else {
183            Err(self.error)
184        }
185    }
186}
187
188#[cfg(not(feature = "std"))]
189#[inline(always)]
190fn new_fmt_err() -> fmt::Error {
191    fmt::Error
192}
193
194#[cfg(feature = "std")]
195#[inline(always)]
196fn new_fmt_err() -> io::Error {
197    io::Error::new(io::ErrorKind::Other, "Format Error")
198}
199
200/// Write adapter that forwards writes to the underlying template. This writer
201/// will never return an error. Any errors encountered will be recorded
202/// internally.
203pub struct RawTemplateWriter<'a, 'b>(&'b mut TemplateBuffer<'a>);
204
205impl<'a, 'b> fmt::Write for RawTemplateWriter<'a, 'b> {
206    // This is the fast-path.
207    // Inlining this allows LLVM to optimize it significantly.
208    #[inline(always)]
209    fn write_str(&mut self, text: &str) -> fmt::Result {
210        use self::InnerTemplateWriter::*;
211        if !error::is_empty(&self.0.error) {
212            return Ok(());
213        }
214        match self.0.writer {
215            Fmt(ref mut writer) => {
216                if writer.write_str(text).is_err() {
217                    self.0.error.write = Some(new_fmt_err());
218                }
219            }
220            #[cfg(feature = "alloc")]
221            Str(ref mut writer) => {
222                let _ = writer.write_str(text);
223            }
224            #[cfg(feature = "std")]
225            Io(ref mut writer) => {
226                self.0.error.write = writer.write_all(text.as_bytes()).err();
227            }
228        }
229        Ok(())
230    }
231}
232
233/// Write adapter that escapes writes then forwards them to the underlying
234/// template. This writer will never return an error. Any errors encountered
235/// will be recorded internally.
236pub struct TemplateWriter<'a, 'b>(&'b mut TemplateBuffer<'a>);
237
238impl<'a, 'b> fmt::Write for TemplateWriter<'a, 'b> {
239    fn write_str(&mut self, text: &str) -> fmt::Result {
240        // NOTE to future self: don't try to be fancy here. This optimizes well
241        // and all you're fancy algorithms are actually slower.
242        //
243        // Also, don't try short-circuiting entirely. That is, don't check if we
244        // need to escape and use write_raw otherwise. It's slower.
245        use self::InnerTemplateWriter::*;
246        if !error::is_empty(&self.0.error) {
247            return Ok(());
248        }
249
250        fn should_escape(b: u8) -> bool {
251            (b | 0x4) == b'&' || (b | 0x2) == b'>'
252        }
253
254        match self.0.writer {
255            Fmt(ref mut writer) => {
256                for c in text.chars() {
257                    if (match (c.is_ascii() && should_escape(c as u8), c as u8) {
258                        (true, b'&') => writer.write_str("&amp;"),
259                        (true, b'"') => writer.write_str("&quot;"),
260                        (true, b'<') => writer.write_str("&lt;"),
261                        (true, b'>') => writer.write_str("&gt;"),
262                        _ => writer.write_char(c),
263                    })
264                    .is_err()
265                    {
266                        self.0.error.write = Some(new_fmt_err());
267                        break;
268                    }
269                }
270            }
271            #[cfg(feature = "alloc")]
272            Str(ref mut writer) => {
273                for b in text.bytes() {
274                    match (should_escape(b), b) {
275                        (true, b'&') => writer.push_str("&amp;"),
276                        (true, b'"') => writer.push_str("&quot;"),
277                        (true, b'<') => writer.push_str("&lt;"),
278                        (true, b'>') => writer.push_str("&gt;"),
279                        // NOTE: Do not add an unreachable case. It makes this slower.
280                        _ => unsafe { writer.as_mut_vec() }.push(b),
281                    }
282                }
283            }
284            #[cfg(feature = "std")]
285            Io(ref mut writer) => {
286                for b in text.bytes() {
287                    if let Err(e) = match (should_escape(b), b) {
288                        (true, b'&') => writer.write_all(b"&amp;"),
289                        (true, b'"') => writer.write_all(b"&quot;"),
290                        (true, b'<') => writer.write_all(b"&lt;"),
291                        (true, b'>') => writer.write_all(b"&gt;"),
292                        _ => writer.write_all(&[b] as &[u8]),
293                    } {
294                        self.0.error.write = Some(e);
295                        break;
296                    }
297                }
298            }
299        }
300        Ok(())
301    }
302}