templr/
template.rs

1use std::{fmt, io, marker::PhantomData};
2
3pub use templr_macros::Template;
4
5use crate::Result;
6
7/// Asserts that [`Template`] is object safe.
8struct _DynTemplate(dyn Template);
9
10/// A trait to convert a type to a [`Template`]. Used when deriving [`Template`].
11///
12/// Useful to implement on `struct`s.
13///
14/// ```rust
15/// # use templr::{templ, ToTemplate, Template};
16/// #[derive(Template)]
17/// struct Greet<'a> {
18///     name: &'a str,
19/// }
20///
21/// impl ToTemplate for Greet<'_> {
22///     fn to_template(&self) -> impl Template + '_ {
23///         templ! {
24///             Hello, {self.name}!
25///         }
26///     }
27/// }
28///
29/// let t = templ! {
30///     #(Greet { name: "baba" });
31/// };
32/// let html = t.render(&()).unwrap();
33/// assert_eq!(html, "Hello, baba!");
34/// ```
35pub trait ToTemplate<Ctx: ?Sized = ()> {
36    /// Converts this type into the template.
37    /// This function should be as lightweight as possible.
38    fn to_template(&self) -> impl Template<Ctx> + '_;
39}
40
41impl<Ctx: ?Sized, T: ToTemplate<Ctx> + ?Sized> ToTemplate<Ctx> for &'_ T {
42    fn to_template(&self) -> impl Template<Ctx> + '_ {
43        T::to_template(self)
44    }
45}
46
47// impl<Ctx: ?Sized, T: ToTemplate<Ctx>> Template<Ctx> for T {
48//     fn size_hint(&self) -> usize {
49//         self.to_template().size_hint()
50//     }
51//     fn render_with_children_into(
52//         &self,
53//         writer: &mut dyn fmt::Write,
54//         ctx: &Ctx,
55//         children: &dyn Template<Ctx>,
56//     ) -> Result<()> {
57//         self.to_template()
58//             .render_with_children_into(writer, ctx, children)
59//     }
60//     fn render_into(&self, writer: &mut dyn fmt::Write, ctx: &Ctx) -> Result<()> {
61//         self.to_template().render_into(writer, ctx)
62//     }
63//     fn write_into(&self, writer: &mut dyn io::Write, ctx: &Ctx) -> io::Result<()> {
64//         self.to_template().write_into(writer, ctx)
65//     }
66//     fn render(&self, ctx: &Ctx) -> Result<String> {
67//         self.to_template().render(ctx)
68//     }
69// }
70
71// impl<Ctx: ?Sized, T: Template<Ctx> + ?Sized> ToTemplate<Ctx> for T {
72//     fn to_template(&self) -> impl Template<Ctx> + '_ {
73//         self
74//     }
75// }
76
77/// Main template trait.
78/// An implementation can be generated using the [`templ!`](crate::templ) macro.
79pub trait Template<Ctx: ?Sized = ()> {
80    /// Provides a rough estimate of the expanded length of the rendered template.
81    /// Larger values result in higher memory usage but fewer reallocations.
82    /// Smaller values result in the opposite. This value only affects render.
83    /// It does not take effect when calling [`render_into`](Template::render_into),
84    /// [`write_into`](Template::write_into), and the [`fmt::Display`] (when implemented).
85    // implementation, or the blanket `ToString::to_string` implementation.
86    fn size_hint(&self) -> usize;
87
88    /// Renders the template to the given fmt writer.
89    fn render_with_children_into(
90        &self,
91        writer: &mut dyn fmt::Write,
92        ctx: &Ctx,
93        children: &dyn Template<Ctx>,
94    ) -> Result<()>;
95
96    /// Renders the template to the given fmt writer,
97    /// assuming that there are not children.
98    fn render_into(&self, writer: &mut dyn fmt::Write, ctx: &Ctx) -> Result<()> {
99        self.render_with_children_into(writer, ctx, &())
100    }
101
102    fn render(&self, ctx: &Ctx) -> Result<String> {
103        let mut buf = String::new();
104        let _ = buf.try_reserve(self.size_hint());
105        self.render_into(&mut buf, ctx)?;
106        Ok(buf)
107    }
108
109    /// Renders the template to the given IO writer.
110    fn write_into(&self, writer: &mut dyn io::Write, ctx: &Ctx) -> io::Result<()> {
111        // Create a shim which translates an `io::Write` to an `fmt::Write` and saves
112        // off I/O errors. instead of discarding them
113        struct Adapter<'a, T: ?Sized + 'a> {
114            inner: &'a mut T,
115            error: io::Result<()>,
116        }
117
118        impl<T: io::Write + ?Sized> fmt::Write for Adapter<'_, T> {
119            fn write_str(&mut self, s: &str) -> fmt::Result {
120                match self.inner.write_all(s.as_bytes()) {
121                    Ok(()) => Ok(()),
122                    Err(e) => {
123                        self.error = Err(e);
124                        Err(fmt::Error)
125                    }
126                }
127            }
128        }
129
130        let mut output = Adapter {
131            inner: writer,
132            error: Ok(()),
133        };
134        match self.render_into(&mut output, ctx) {
135            Ok(()) => Ok(()),
136            Err(err) => {
137                // check if the error came from the underlying `Write` or not
138                if output.error.is_err() {
139                    output.error
140                } else {
141                    Err(io::Error::new(io::ErrorKind::Other, err))
142                }
143            }
144        }
145    }
146}
147
148impl<Ctx: ?Sized> Template<Ctx> for () {
149    fn size_hint(&self) -> usize {
150        0
151    }
152    fn render_with_children_into(
153        &self,
154        _writer: &mut dyn fmt::Write,
155        _ctx: &Ctx,
156        _children: &dyn Template<Ctx>,
157    ) -> Result<()> {
158        Ok(())
159    }
160    fn render_into(&self, _writer: &mut dyn fmt::Write, _ctx: &Ctx) -> Result<()> {
161        Ok(())
162    }
163    fn render(&self, _ctx: &Ctx) -> Result<String> {
164        Ok(String::new())
165    }
166    fn write_into(&self, _writer: &mut dyn io::Write, _ctx: &Ctx) -> io::Result<()> {
167        Ok(())
168    }
169}
170
171impl<Ctx: ?Sized, T: Template<Ctx> + ?Sized> Template<Ctx> for &'_ T {
172    fn size_hint(&self) -> usize {
173        T::size_hint(self)
174    }
175    fn render_with_children_into(
176        &self,
177        writer: &mut dyn fmt::Write,
178        ctx: &Ctx,
179        children: &dyn Template<Ctx>,
180    ) -> Result<()> {
181        T::render_with_children_into(self, writer, ctx, children)
182    }
183    fn render_into(&self, writer: &mut dyn fmt::Write, ctx: &Ctx) -> Result<()> {
184        T::render_into(self, writer, ctx)
185    }
186    fn render(&self, ctx: &Ctx) -> Result<String> {
187        T::render(self, ctx)
188    }
189    fn write_into(&self, writer: &mut dyn io::Write, ctx: &Ctx) -> io::Result<()> {
190        T::write_into(self, writer, ctx)
191    }
192}
193
194/// A wrapper for the [`render_with_children_into`](Template::render_with_children_into)
195/// closure that implements [`Template`].
196#[derive(Debug)]
197pub struct FnTemplate<F, Ctx: ?Sized = ()>
198where
199    F: Fn(&mut dyn fmt::Write, &Ctx, &dyn Template<Ctx>) -> Result<()>,
200{
201    size_hint: usize,
202    render_with_children_into: F,
203    _phantom: PhantomData<Ctx>,
204}
205
206impl<F, Ctx: ?Sized> FnTemplate<F, Ctx>
207where
208    F: Fn(&mut dyn fmt::Write, &Ctx, &dyn Template<Ctx>) -> Result<()>,
209{
210    /// Creates a new [`FnTemplate`] from
211    /// a [`render_with_children_into`](Template::render_with_children_into) closure.
212    /// This template will have the default size.
213    #[inline]
214    pub fn new(render_with_children_into: F) -> Self {
215        Self {
216            size_hint: 80,
217            render_with_children_into,
218            _phantom: PhantomData,
219        }
220    }
221
222    /// Creates a new [`FnTemplate`] from a size hint (see [`Template::size_hint`]) and
223    /// a [`render_with_children_into`](Template::render_with_children_into) closure
224    #[inline]
225    pub fn new_sized(size_hint: usize, render_with_children_into: F) -> Self {
226        Self {
227            size_hint,
228            render_with_children_into,
229            _phantom: PhantomData,
230        }
231    }
232}
233
234impl<F, Ctx: ?Sized> Template<Ctx> for FnTemplate<F, Ctx>
235where
236    F: Fn(&mut dyn fmt::Write, &Ctx, &dyn Template<Ctx>) -> Result<()>,
237{
238    fn size_hint(&self) -> usize {
239        self.size_hint
240    }
241    fn render_with_children_into(
242        &self,
243        writer: &mut dyn fmt::Write,
244        ctx: &Ctx,
245        children: &dyn Template<Ctx>,
246    ) -> Result<()> {
247        (self.render_with_children_into)(writer, ctx, children)
248    }
249}
250
251impl<F> fmt::Display for FnTemplate<F>
252where
253    F: Fn(&mut dyn fmt::Write, &(), &dyn Template<()>) -> crate::Result<()> + Send,
254{
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        self.render_into(f, &()).map_err(|_| fmt::Error)
257    }
258}
259
260/// Return type for functions that return [`templ! { ... }`](crate::templ).
261/// This takes a lifetime (defaults to `'static`) and a context type (defaults to `()`).
262///
263/// ```rust
264/// # use templr::{templ, templ_ret};
265/// fn hello(name: &str) -> templ_ret!['_, ()] {
266///     templ! {
267///         Hello, {name}!
268///     }
269/// }
270/// ```
271/// Instead of:
272/// ```rust
273/// # use std::fmt;
274/// # use templr::{templ, templ_ret, FnTemplate, Template, Result};
275/// fn hello(
276///     name: &str,
277/// ) -> FnTemplate<impl '_ + Fn(&mut dyn fmt::Write, &(), &dyn Template) -> Result<()>> {
278///     templ! {
279///         Hello, {name}!
280///     }
281/// }
282/// ```
283#[macro_export]
284macro_rules! templ_ret {
285    ($lt:lifetime, $ctx:ty $(,)?) => {
286        $crate::FnTemplate<
287            impl $lt + Fn(
288                &mut dyn ::std::fmt::Write,
289                &$ctx,
290                &dyn $crate::Template<$ctx>,
291            ) -> $crate::Result<()>,
292            $ctx,
293        >
294    };
295    ($lt:lifetime $(,)?) => { $crate::templ_ret![$lt, ()] };
296    ($ctx:ty $(,)?) => { $crate::templ_ret!['static, $ctx] };
297    () => { $crate::templ_ret!['static, ()] };
298}