rstml_component/fmt.rs
1use bytes::{BufMut, Bytes, BytesMut};
2use std::fmt;
3
4mod escape;
5
6/// A formatter for serializing HTML attribute values.
7pub struct HtmlAttributeFormatter<'a> {
8 any_written: bool,
9 buffer: &'a mut BytesMut,
10}
11
12impl<'a> HtmlAttributeFormatter<'a> {
13 /// Creates a new `HtmlAttributeFormatter` instance with the provided buffer.
14 ///
15 /// # Arguments
16 ///
17 /// - `buffer`: A mutable reference to the [BytesMut] buffer where the formatted content will be written.
18 ///
19 /// # Returns
20 ///
21 /// A new `HtmlAttributeFormatter` instance associated with the provided buffer.
22 fn new(buffer: &'a mut BytesMut) -> Self {
23 Self {
24 any_written: false,
25 buffer,
26 }
27 }
28
29 /// Write raw bytes to the attribute formatter. Note that this method does no sanitization or escaping of
30 /// the values. If you want the values to be sanitized, use the [write] method insted.
31 ///
32 /// # Arguments
33 ///
34 /// - `raw`: A reference to the raw byte slice that will be written to the buffer.
35 ///
36 /// [write]: Self::write
37 pub fn write_bytes(&mut self, raw: &[u8]) {
38 self.buffer.reserve(raw.len() + 3);
39 if !self.any_written {
40 self.any_written = true;
41 self.buffer.put_slice(b"=\"");
42 }
43
44 self.buffer.put_slice(raw);
45 }
46
47 /// Writes escaped bytes to the attribute formatter, ensuring valid HTML attribute characters.
48 ///
49 /// This method accepts a reference to a byte slice containing the content to be written to the
50 /// formatter's buffer. The provided `value` is escaped to ensure that it only contains valid HTML
51 /// attribute characters, preventing any unintentional issues with attribute values. The escaped
52 /// content is then written to the formatter's buffer.
53 ///
54 /// # Arguments
55 ///
56 /// - `value`: A reference to the raw byte slice containing the content to be escaped and written.
57 pub fn write(&mut self, value: &[u8]) {
58 self.write_bytes(&escape::attribute(value))
59 }
60
61 /// Reserves space in the buffer for writing additional bytes without re-allocation.
62 ///
63 /// This method ensures that enough space is reserved in the formatter's buffer to accommodate
64 /// the writing of `additional` bytes without needing to reallocate memory. It's useful to call
65 /// this method before writing a significant amount of content, as it can help prevent frequent
66 /// reallocations and improve performance.
67 ///
68 /// # Arguments
69 ///
70 /// - `additional`: The number of additional bytes to reserve space for in the buffer.
71 pub fn reserve(&mut self, additional: usize) {
72 // add 3 for the opening and closing constant parts
73 self.buffer.reserve(additional + 3);
74 }
75
76 fn write_value(buffer: &mut BytesMut, value: impl HtmlAttributeValue) -> fmt::Result {
77 let mut attribute_formatter = HtmlAttributeFormatter::new(buffer);
78
79 value.fmt(&mut attribute_formatter)?;
80 if attribute_formatter.any_written {
81 buffer.reserve(1);
82 buffer.put_slice(b"\"");
83 }
84
85 Ok(())
86 }
87}
88
89/// A formatter for serializing HTML nodes and content.
90///
91/// The `HtmlFormatter` struct provides a versatile way to serialize HTML nodes and content,
92/// ensuring proper spacing, indentation, and formatting. It's designed to handle various
93/// types of HTML content and produce well-structured and readable HTML output.
94///
95/// NOTE: Currently, no indentation/readibility is supported. The plan is to implement that
96/// later.
97pub struct HtmlFormatter<'a> {
98 buffer: &'a mut BytesMut,
99}
100
101impl<'a> AsMut<HtmlFormatter<'a>> for HtmlFormatter<'a> {
102 fn as_mut(&mut self) -> &mut HtmlFormatter<'a> {
103 self
104 }
105}
106
107impl<'a> HtmlFormatter<'a> {
108 /// Creates a new `HtmlFormatter` instance with the provided buffer.
109 ///
110 /// # Arguments
111 ///
112 /// - `buffer`: A mutable reference to the [BytesMut] buffer where the formatted content will be written.
113 ///
114 /// # Returns
115 ///
116 /// A new `HtmlFormatter` instance associated with the provided buffer.
117 pub fn new(buffer: &'a mut BytesMut) -> Self {
118 Self { buffer }
119 }
120
121 /// Writes raw bytes to the formatter's buffer without escaping.
122 ///
123 /// This method appends the specified raw bytes to the formatter's buffer without performing any
124 /// additional escaping or modification. It provides a low-level, raw API for directly writing
125 /// content to the buffer, which can be useful for situations where the content is already
126 /// properly formatted and safe to include as-is.
127 ///
128 /// # Arguments
129 ///
130 /// - `raw`: A reference to the raw byte slice that will be written to the buffer.
131 pub fn write_bytes(&mut self, raw: &[u8]) {
132 self.buffer.extend_from_slice(raw);
133 }
134
135 /// Writes escaped bytes to the formatter's buffer, ensuring valid HTML characters.
136 ///
137 /// This method accepts a reference to a byte slice containing the content to be written to
138 /// the formatter's buffer. The provided `value` is escaped to ensure that it only contains
139 /// valid HTML characters, preventing any unintentional issues with the produced HTML content.
140 ///
141 /// # Arguments
142 ///
143 /// - `value`: A reference to the raw byte slice containing the content to be escaped and written.
144 pub fn write(&mut self, value: &[u8]) {
145 self.write_bytes(&escape::text(value))
146 }
147
148 // Writes a DOCTYPE declaration to the formatter's buffer.
149 ///
150 /// This method appends a DOCTYPE declaration to the formatter's buffer. The provided `value` is
151 /// escaped to ensure that it only contains valid characters for a DOCTYPE declaration. The resulting
152 /// DOCTYPE declaration is properly formatted and follows the standard syntax of "<!DOCTYPE ...>".
153 ///
154 /// # Arguments
155 ///
156 /// - `value`: A reference to the raw byte slice containing the content for the DOCTYPE declaration.
157 pub fn write_doctype(&mut self, value: &[u8]) {
158 const DOCTYPE_PREFIX: &[u8] = b"<!DOCTYPE ";
159 const DOCTYPE_SUFFIX: &[u8] = b">";
160
161 let escaped = escape::text(value);
162 self
163 .buffer
164 .reserve(escaped.len() + DOCTYPE_PREFIX.len() + DOCTYPE_SUFFIX.len());
165
166 self.write_bytes(DOCTYPE_PREFIX);
167 self.write_bytes(&escaped);
168 self.write_bytes(DOCTYPE_SUFFIX);
169 }
170
171 /// Writes the start of an opening HTML tag to the formatter's buffer.
172 ///
173 /// This method appends the start of an opening HTML tag to the formatter's buffer. The provided `tag`
174 /// is used as the tag name, and the tag is not closed. is commonly followed by either [write_attribute_name],
175 /// [write_self_close_tag], or [write_open_tag_end].
176 ///
177 /// # Arguments
178 ///
179 /// - `tag`: A reference to the raw byte slice containing the tag name for the opening tag.
180 ///
181 /// [write_attribute_name]: Self::write_attribute_name
182 /// [write_self_close_tag]: Self::write_self_close_tag
183 /// [write_open_tag_end]: Self::write_open_tag_end
184 pub fn write_open_tag_start(&mut self, tag: &[u8]) {
185 self.buffer.reserve(tag.len() + 1);
186 self.write_bytes(b"<");
187 self.write_bytes(tag);
188 }
189
190 /// Writes an HTML attribute name to the formatter's buffer.
191 ///
192 /// This method appends an HTML attribute name to the formatter's buffer. The provided `name` is
193 /// used as the attribute name.
194 ///
195 /// # Arguments
196 ///
197 /// - `name`: A reference to the raw byte slice containing the attribute name.
198 pub fn write_attribute_name(&mut self, name: &[u8]) {
199 self.buffer.reserve(name.len() + 1);
200 self.write_bytes(b" ");
201 self.write_bytes(name);
202 }
203
204 /// Writes an HTML attribute value to the formatter's buffer.
205 ///
206 /// This method appends an HTML attribute value to the formatter's buffer. The provided `value` is
207 /// an instance of a type implementing the [HtmlAttributeValue] trait. The value is written to the
208 /// buffer, ensuring proper formatting and escaping if required.
209 ///
210 /// # Arguments
211 ///
212 /// - `value`: An instance implementing the [HtmlAttributeValue] trait, representing the attribute value.
213 ///
214 /// # Returns
215 ///
216 /// A [std::fmt::Result] indicating the success or failure of the writing operation.
217 pub fn write_attribute_value(&mut self, value: impl HtmlAttributeValue) -> fmt::Result {
218 HtmlAttributeFormatter::write_value(self.buffer, value)
219 }
220
221 pub fn write_attributes(&mut self, values: impl HtmlAttributes) -> fmt::Result {
222 let mut attribute_formatter = HtmlAttributesFormatter { inner: self };
223 values.fmt(&mut attribute_formatter)
224 }
225
226 /// Writes a self-closing indicator to the formatter's buffer.
227 ///
228 /// This method appends a self-closing indicator " />" to the formatter's buffer. It's commonly used
229 /// after writing an opening tag to indicate that the tag is self-closing and has no associated content.
230 pub fn write_self_close_tag(&mut self) {
231 self.write_bytes(b" />");
232 }
233
234 /// Writes the end of an opening HTML tag to the formatter's buffer.
235 ///
236 /// This method appends the end of an opening HTML tag ">" to the formatter's buffer. It's commonly
237 /// used after writing the tag name and its attributes to indicate the completion of the tag's opening.
238 pub fn write_open_tag_end(&mut self) {
239 self.write_bytes(b">");
240 }
241
242 /// Writes an HTML end tag to the formatter's buffer.
243 ///
244 /// This method appends an HTML end tag "</tag>" to the formatter's buffer. The provided `tag` is used
245 /// as the tag name for the end tag.
246 ///
247 /// # Arguments
248 ///
249 /// - `tag`: A reference to the raw byte slice containing the tag name for the end tag.
250 pub fn write_end_tag(&mut self, tag: &[u8]) {
251 self.buffer.reserve(tag.len() + 3);
252 self.write_bytes(b"</");
253 self.write_bytes(tag);
254 self.write_bytes(b">");
255 }
256
257 /// Writes HTML content to the formatter's buffer.
258 ///
259 /// This method appends HTML content to the formatter's buffer. The provided `content` is an
260 /// instance of a type implementing the [HtmlContent] trait. The content is formatted and written
261 /// to the buffer according to the implementation of the [HtmlContent] trait.
262 ///
263 /// # Arguments
264 ///
265 /// - `content`: An instance implementing the [HtmlContent] trait, representing the HTML content to write.
266 ///
267 /// # Returns
268 ///
269 /// A [std::fmt::Result] indicating the success or failure of the writing operation.
270 pub fn write_content(&mut self, content: impl HtmlContent) -> fmt::Result {
271 content.fmt(self)
272 }
273
274 /// Writes an HTML comment to the formatter's buffer.
275 ///
276 /// This method appends an HTML comment to the formatter's buffer. The provided `comment` is escaped
277 /// to ensure that it only contains valid characters for an HTML comment.
278 ///
279 /// # Arguments
280 ///
281 /// - `comment`: A reference to the raw byte slice containing the content for the HTML comment.
282 pub fn write_comment(&mut self, comment: &[u8]) {
283 const COMMENT_PREFIX: &[u8] = b"<!--";
284 const COMMENT_SUFFIX: &[u8] = b"-->";
285
286 let escaped = escape::text(comment);
287 self
288 .buffer
289 .reserve(escaped.len() + COMMENT_PREFIX.len() + COMMENT_SUFFIX.len());
290
291 self.write_bytes(COMMENT_PREFIX);
292 self.write_bytes(&escaped);
293 self.write_bytes(COMMENT_SUFFIX);
294 }
295
296 /// Reserves space in the buffer for writing additional bytes without reallocation.
297 ///
298 /// This method ensures that enough space is reserved in the formatter's buffer to accommodate
299 /// the writing of `additional` bytes without needing to reallocate memory. It's useful to call
300 /// this method before writing a significant amount of content, as it can help prevent frequent
301 /// reallocations and improve performance.
302 ///
303 /// # Arguments
304 ///
305 /// - `additional`: The number of additional bytes to reserve space for in the buffer.
306 pub fn reserve(&mut self, additional: usize) {
307 self.buffer.reserve(additional);
308 }
309}
310
311pub struct HtmlAttributesFormatter<'a, 'b> {
312 inner: &'a mut HtmlFormatter<'b>,
313}
314
315impl<'a, 'b> HtmlAttributesFormatter<'a, 'b> {
316 pub fn write_attribute(&mut self, name: &[u8], value: impl HtmlAttributeValue) -> fmt::Result {
317 self.inner.write_attribute_name(name);
318 self.inner.write_attribute_value(value)
319 }
320}
321
322/// A trait representing content that can be formatted into HTML representation.
323///
324/// Types that implement this trait define how they should be formatted as HTML content.
325/// This trait provides methods to write the formatted content to various output formats,
326/// such as a byte buffer or a string.
327pub trait HtmlContent: Sized {
328 /// Formats the content and writes it to the provided [HtmlFormatter].
329 ///
330 /// This method defines how the implementing type's content should be formatted as HTML.
331 /// Implementations of this method should use the provided [HtmlFormatter] to write the
332 /// formatted content according to the desired syntax and structure.
333 ///
334 /// # Arguments
335 ///
336 /// - `formatter`: A mutable reference to the [HtmlFormatter] that handles the output.
337 ///
338 /// # Returns
339 ///
340 /// A [std::fmt::Result] indicating the success or failure of the formatting operation.
341 fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result;
342
343 /// Writes the formatted content to the provided byte buffer.
344 ///
345 /// This method creates an [HtmlFormatter] that writes to the given `buffer` and uses
346 /// the `fmt` method to write the formatted content into the buffer.
347 ///
348 /// # Arguments
349 ///
350 /// - `buffer`: A mutable reference to the byte buffer where the formatted content will be written.
351 ///
352 /// # Returns
353 ///
354 /// A [std::fmt::Result] indicating the success or failure of the formatting operation.
355 fn write_to(self, buffer: &mut BytesMut) -> fmt::Result {
356 let mut formatter = HtmlFormatter::new(buffer);
357 self.fmt(&mut formatter)
358 }
359
360 /// Converts the formatted content into a [Bytes] buffer.
361 ///
362 /// This method writes the formatted content to a byte buffer and returns it as a [Bytes] object.
363 ///
364 /// # Returns
365 ///
366 /// A [Result] containing the [Bytes] object if successful, or a [std::fmt::Error] if formatting fails.
367 fn into_bytes(self) -> Result<Bytes, fmt::Error> {
368 let mut buffer = BytesMut::new();
369
370 self.write_to(&mut buffer)?;
371 Ok(buffer.freeze())
372 }
373
374 /// Converts the formatted content into a [String].
375 ///
376 /// This method writes the formatted content to a byte buffer, then attempts to convert it into
377 /// a [String].
378 ///
379 /// # Returns
380 ///
381 /// A [Result] containing the [String] if successful, or a [std::fmt::Error] if formatting or
382 /// conversion to [String] fails.
383 fn into_string(self) -> Result<String, fmt::Error> {
384 let bytes = self.into_bytes()?;
385 String::from_utf8(bytes.to_vec()).map_err(|_| fmt::Error)
386 }
387}
388
389pub trait HtmlAttributes {
390 fn fmt(self, formatter: &mut HtmlAttributesFormatter) -> fmt::Result;
391}
392
393/// A trait representing a value that can be used as an attribute value in HTML components.
394///
395/// Types that implement this trait allow customization of how their values are formatted
396/// when used as attribute values in HTML tags. This trait is primarily used in conjunction
397/// with the [HtmlAttributeFormatter] to control the serialization of attribute values.
398pub trait HtmlAttributeValue {
399 /// Formats the value and writes it to the provided [HtmlAttributeFormatter].
400 ///
401 /// This method is used to customize how the implementing type's value is serialized as an
402 /// attribute value in HTML. Implementations of this method should write the formatted value
403 /// to the provided [HtmlAttributeFormatter] using the appropriate formatting syntax.
404 ///
405 /// # Arguments
406 ///
407 /// - `formatter`: A mutable reference to the [HtmlAttributeFormatter] that handles the output.
408 ///
409 /// # Returns
410 ///
411 /// A [std::fmt::Result] indicating the success or failure of the formatting operation.
412 fn fmt(self, formatter: &mut HtmlAttributeFormatter) -> fmt::Result;
413}
414
415/// A struct for embedding raw, unsanitized HTML content.
416///
417/// The `RawText` struct allows you to include raw HTML content without any sanitization or
418/// modification. This is useful when you need to merge multiple HTML fragments that are known
419/// to be safe or pre-sanitized. The `RawText` content is intended for situations where you have
420/// direct control over the content being embedded and ensure its safety.
421pub struct RawText<V>(V);
422
423impl<V: AsRef<[u8]>> RawText<V> {
424 /// Creates a new `RawText` instance with the given raw HTML content.
425 ///
426 /// # Arguments
427 ///
428 /// - `value`: The raw HTML content as a byte slice.
429 ///
430 /// # Returns
431 ///
432 /// A `RawText` instance wrapping the raw HTML content.
433 pub fn new(value: V) -> Self {
434 Self(value)
435 }
436}
437
438impl<V: AsRef<[u8]>> HtmlContent for RawText<V> {
439 fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result {
440 formatter.write_bytes(self.0.as_ref());
441 Ok(())
442 }
443}
444
445impl<V: AsRef<[u8]>> HtmlAttributeValue for RawText<V> {
446 fn fmt(self, formatter: &mut HtmlAttributeFormatter) -> fmt::Result {
447 formatter.write_bytes(self.0.as_ref());
448 Ok(())
449 }
450}
451
452impl<F> HtmlContent for F
453where
454 F: FnOnce(&mut HtmlFormatter) -> fmt::Result,
455{
456 fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result {
457 self(formatter)
458 }
459}
460
461impl HtmlContent for () {
462 fn fmt(self, _formatter: &mut HtmlFormatter) -> fmt::Result {
463 Ok(())
464 }
465}
466
467impl HtmlAttributeValue for () {
468 fn fmt(self, _formatter: &mut HtmlAttributeFormatter) -> fmt::Result {
469 Ok(())
470 }
471}
472
473impl HtmlAttributes for () {
474 fn fmt(self, _formatter: &mut HtmlAttributesFormatter) -> fmt::Result {
475 Ok(())
476 }
477}
478
479impl<T: HtmlContent> HtmlContent for Option<T> {
480 fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result {
481 match self {
482 None => Ok(()),
483 Some(template) => template.fmt(formatter),
484 }
485 }
486}
487
488impl<T: HtmlAttributeValue> HtmlAttributeValue for Option<T> {
489 fn fmt(self, formatter: &mut HtmlAttributeFormatter) -> fmt::Result {
490 match self {
491 None => Ok(()),
492 Some(template) => template.fmt(formatter),
493 }
494 }
495}
496
497impl<T: HtmlAttributes> HtmlAttributes for Option<T> {
498 fn fmt(self, formatter: &mut HtmlAttributesFormatter) -> fmt::Result {
499 match self {
500 None => Ok(()),
501 Some(template) => template.fmt(formatter),
502 }
503 }
504}
505
506impl<N: AsRef<[u8]>, T: HtmlAttributeValue> HtmlAttributes for (N, T) {
507 fn fmt(self, formatter: &mut HtmlAttributesFormatter) -> fmt::Result {
508 let (name, value) = self;
509 formatter.write_attribute(name.as_ref(), value)
510 }
511}
512
513fn display(value: fmt::Arguments, mut write: impl FnMut(&[u8])) -> fmt::Result {
514 match value.as_str() {
515 Some(s) => {
516 write(s.as_bytes());
517 Ok(())
518 }
519
520 None => {
521 use fmt::Write;
522 struct Writer<F> {
523 writer: F,
524 }
525
526 impl<F> Write for Writer<F>
527 where
528 F: FnMut(&[u8]),
529 {
530 fn write_str(&mut self, s: &str) -> fmt::Result {
531 (self.writer)(s.as_bytes());
532 Ok(())
533 }
534 }
535
536 let mut writer = Writer { writer: &mut write };
537
538 write!(&mut writer, "{}", value)
539 }
540 }
541}
542
543impl<'a> HtmlContent for fmt::Arguments<'a> {
544 fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result {
545 display(self, |value| formatter.write(value))
546 }
547}
548
549impl<'a> HtmlAttributeValue for fmt::Arguments<'a> {
550 fn fmt(self, formatter: &mut HtmlAttributeFormatter) -> fmt::Result {
551 display(self, |value| formatter.write(value))
552 }
553}
554
555macro_rules! impl_simple_write {
556 ($ty:ty, as_ref) => {
557 impl HtmlAttributeValue for $ty {
558 fn fmt(self, formatter: &mut HtmlAttributeFormatter) -> fmt::Result {
559 formatter.write(self.as_ref());
560 Ok(())
561 }
562 }
563
564 impl HtmlContent for $ty {
565 fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result {
566 formatter.write(self.as_ref());
567 Ok(())
568 }
569 }
570 };
571 ($ty:ty, raw Display) => {
572 impl HtmlAttributeValue for $ty {
573 fn fmt(self, formatter: &mut HtmlAttributeFormatter) -> fmt::Result {
574 display(format_args!("{}", self), |value| {
575 formatter.write_bytes(value)
576 })
577 }
578 }
579
580 impl HtmlContent for $ty {
581 fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result {
582 display(format_args!("{}", self), |value| {
583 formatter.write_bytes(value)
584 })
585 }
586 }
587 };
588}
589
590impl_simple_write!(String, as_ref);
591impl_simple_write!(&str, as_ref);
592impl_simple_write!(&String, as_ref);
593impl_simple_write!(Bytes, as_ref);
594impl_simple_write!(bool, raw Display);
595impl_simple_write!(u8, raw Display);
596impl_simple_write!(u16, raw Display);
597impl_simple_write!(u32, raw Display);
598impl_simple_write!(u64, raw Display);
599impl_simple_write!(u128, raw Display);
600impl_simple_write!(usize, raw Display);
601impl_simple_write!(i8, raw Display);
602impl_simple_write!(i16, raw Display);
603impl_simple_write!(i32, raw Display);
604impl_simple_write!(i64, raw Display);
605impl_simple_write!(i128, raw Display);
606impl_simple_write!(isize, raw Display);
607impl_simple_write!(f32, raw Display);
608impl_simple_write!(f64, raw Display);
609
610macro_rules! impl_tuple_write {
611 ((
612 $($i:ident,)+
613 )) => {
614 #[automatically_derived]
615 impl<$($i,)+> HtmlContent for ($($i,)+)
616 where
617 $($i: HtmlContent,)+
618 {
619 fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result {
620 #[allow(non_snake_case)]
621 let ($($i,)+) = self;
622 $(
623 $i.fmt(formatter)?;
624 )+
625 Ok(())
626 }
627 }
628
629 #[automatically_derived]
630 impl<$($i,)+> HtmlAttributeValue for ($($i,)+)
631 where
632 $($i: HtmlAttributeValue,)+
633 {
634 fn fmt(self, formatter: &mut HtmlAttributeFormatter) -> fmt::Result {
635 #[allow(non_snake_case)]
636 let ($($i,)+) = self;
637 $(
638 $i.fmt(formatter)?;
639 )+
640 Ok(())
641 }
642 }
643 };
644
645 ($f:ident) => {
646 impl_tuple_write!(($f,));
647 };
648
649 ($f:ident $($i:ident)+) => {
650 impl_tuple_write!(($f, $($i,)+));
651 impl_tuple_write!($($i)+);
652 };
653}
654
655impl_tuple_write!(A B C D E F G H I J K L M N O P Q R S T U V W X Y Z);