vy_core/
lib.rs

1#![no_std]
2
3extern crate alloc;
4#[cfg(feature = "std")]
5extern crate std;
6
7mod buffer;
8pub mod either;
9pub mod escape;
10mod helpers;
11
12use alloc::string::String;
13
14pub use self::buffer::Buffer;
15use self::escape::escape_into;
16
17/// A type that can be represented as HTML.
18pub trait IntoHtml {
19    /// Converts this value into HTML by producing a type that implements
20    /// [`IntoHtml`].
21    ///
22    /// This method enables composition of HTML structures by delegating
23    /// rendering to the returned value. Use it to build nested HTML
24    /// elements, combine components, or leverage existing [`IntoHtml`]
25    /// implementations.
26    ///
27    /// # Examples
28    ///
29    /// Compose nested HTML elements using macros:
30    ///
31    /// ```
32    /// # use vy::*;
33    /// struct Article {
34    ///     title: String,
35    ///     content: String,
36    ///     author: String,
37    /// }
38    ///
39    /// impl IntoHtml for Article {
40    ///     fn into_html(self) -> impl IntoHtml {
41    ///         article!(
42    ///             h1!(self.title),
43    ///             p!(class = "content", self.content),
44    ///             footer!("Written by ", self.author)
45    ///         )
46    ///     }
47    /// }
48    /// ```
49    ///
50    /// Chain multiple implementations through delegation:
51    ///
52    /// ```
53    /// # use vy::*;
54    /// # struct Article;
55    /// # impl IntoHtml for Article {
56    /// #     fn into_html(self) -> impl IntoHtml {}
57    /// # }
58    /// struct ArticlePage {
59    ///     title: String,
60    ///     articles: Vec<Article>,
61    /// }
62    ///
63    /// impl IntoHtml for ArticlePage {
64    ///     fn into_html(self) -> impl IntoHtml {
65    ///         html!(head!(title!(self.title)), body!(self.articles))
66    ///     }
67    /// }
68    /// ```
69    ///
70    /// For "leaf" types (elements that render directly without children, like
71    /// primitive values), **always return `self`** to avoid infinite recursion:
72    ///
73    /// ```
74    /// # use vy::{prelude::*, escape::escape_into};
75    /// struct TextNode(String);
76    ///
77    /// impl IntoHtml for TextNode {
78    ///     fn into_html(self) -> impl IntoHtml {
79    ///         // Leaf type returns itself to terminate the rendering chain
80    ///         self
81    ///     }
82    ///
83    ///     fn escape_and_write(self, buf: &mut Buffer) {
84    ///         escape_into(buf, &self.0);
85    ///     }
86    ///
87    ///     fn size_hint(&self) -> usize {
88    ///         self.0.len()
89    ///     }
90    /// }
91    /// ```
92    fn into_html(self) -> impl IntoHtml;
93
94    /// Writes the HTML into the provided [`String`].
95    #[inline]
96    fn escape_and_write(self, buf: &mut Buffer)
97    where
98        Self: Sized,
99    {
100        self.into_html().escape_and_write(buf);
101    }
102
103    #[inline]
104    fn size_hint(&self) -> usize {
105        0
106    }
107
108    /// Allocates a new [`String`] containing the HTML.
109    fn into_string(self) -> String
110    where
111        Self: Sized,
112    {
113        let html = self.into_html();
114        let size = html.size_hint();
115        let mut buf = Buffer::with_capacity(size + (size / 10));
116        html.escape_and_write(&mut buf);
117        buf.into_string()
118    }
119}
120
121impl IntoHtml for &str {
122    #[inline]
123    fn into_html(self) -> impl IntoHtml {
124        self
125    }
126
127    #[inline]
128    fn escape_and_write(self, buf: &mut Buffer) {
129        escape_into(buf, self)
130    }
131
132    #[inline]
133    fn size_hint(&self) -> usize {
134        self.len()
135    }
136}
137
138impl IntoHtml for char {
139    #[inline]
140    fn into_html(self) -> impl IntoHtml {
141        self
142    }
143
144    #[inline]
145    fn escape_and_write(self, buf: &mut Buffer) {
146        escape_into(buf, self.encode_utf8(&mut [0; 4]));
147    }
148
149    #[inline]
150    fn size_hint(&self) -> usize {
151        self.len_utf8()
152    }
153}
154
155impl IntoHtml for String {
156    #[inline]
157    fn into_html(self) -> impl IntoHtml {
158        self
159    }
160
161    #[inline]
162    fn escape_and_write(self, buf: &mut Buffer) {
163        escape_into(buf, &self)
164    }
165
166    #[inline]
167    fn size_hint(&self) -> usize {
168        self.len()
169    }
170}
171
172impl IntoHtml for &String {
173    #[inline]
174    fn into_html(self) -> impl IntoHtml {
175        self.as_str()
176    }
177
178    #[inline]
179    fn size_hint(&self) -> usize {
180        self.len()
181    }
182}
183
184impl IntoHtml for bool {
185    #[inline]
186    fn into_html(self) -> impl IntoHtml {
187        if self {
188            "true"
189        } else {
190            "false"
191        }
192    }
193
194    #[inline]
195    fn size_hint(&self) -> usize {
196        5
197    }
198}
199
200impl<T: IntoHtml> IntoHtml for Option<T> {
201    #[inline]
202    fn into_html(self) -> impl IntoHtml {
203        self
204    }
205
206    #[inline]
207    fn escape_and_write(self, buf: &mut Buffer) {
208        if let Some(x) = self {
209            x.escape_and_write(buf)
210        }
211    }
212
213    #[inline]
214    fn size_hint(&self) -> usize {
215        if let Some(x) = self {
216            x.size_hint()
217        } else {
218            0
219        }
220    }
221}
222
223impl IntoHtml for () {
224    #[inline]
225    fn into_html(self) -> impl IntoHtml {
226        self
227    }
228
229    #[inline]
230    fn escape_and_write(self, _: &mut Buffer) {}
231}
232
233impl<F: FnOnce(&mut Buffer)> IntoHtml for F {
234    #[inline]
235    fn into_html(self) -> impl IntoHtml {
236        self
237    }
238
239    #[inline]
240    fn escape_and_write(self, buf: &mut Buffer) {
241        (self)(buf)
242    }
243}
244
245impl<B: IntoHtml, I: ExactSizeIterator, F> IntoHtml for core::iter::Map<I, F>
246where
247    F: FnMut(I::Item) -> B,
248{
249    #[inline]
250    fn into_html(self) -> impl IntoHtml {
251        self
252    }
253
254    #[inline]
255    fn escape_and_write(self, buf: &mut Buffer) {
256        let len = self.len();
257        for (i, x) in self.enumerate() {
258            if i == 0 {
259                buf.reserve(len * x.size_hint());
260            }
261            x.escape_and_write(buf);
262        }
263    }
264}
265
266impl<T: IntoHtml> IntoHtml for alloc::vec::Vec<T> {
267    #[inline]
268    fn into_html(self) -> impl IntoHtml {
269        self
270    }
271
272    #[inline]
273    fn escape_and_write(self, buf: &mut Buffer) {
274        for x in self {
275            x.escape_and_write(buf);
276        }
277    }
278
279    #[inline]
280    fn size_hint(&self) -> usize {
281        let mut n = 0;
282        for x in self {
283            n += x.size_hint();
284        }
285        n
286    }
287}
288
289impl<T: IntoHtml, const N: usize> IntoHtml for [T; N] {
290    #[inline]
291    fn into_html(self) -> impl IntoHtml {
292        self
293    }
294
295    #[inline]
296    fn escape_and_write(self, buf: &mut Buffer) {
297        for x in self {
298            x.escape_and_write(buf);
299        }
300    }
301
302    #[inline]
303    fn size_hint(&self) -> usize {
304        let mut n = 0;
305        for x in self {
306            n += x.size_hint();
307        }
308        n
309    }
310}