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}