tbot/markup/
html.rs

1//! HTML markup utilities.
2
3use super::Nesting;
4use std::{
5    fmt::{self, Display, Formatter, Write},
6    ops::Deref,
7};
8
9/// Characters that need to be escaped to be interpreted as text.
10pub const ESCAPED_TEXT_CHARACTERS: [(char, &str); 3] =
11    [('<', "&lt;"), ('>', "&gt;"), ('&', "&amp;")];
12
13/// Represents a value that can be formatted for HTML.
14pub trait Formattable {
15    /// Writes formatted value to the formatter.
16    #[allow(clippy::missing_errors_doc)]
17    fn format(&self, formatter: &mut Formatter, _: Nesting) -> fmt::Result;
18}
19
20impl_primitives!(Formattable);
21impl_tuples!(Formattable);
22
23impl Formattable for char {
24    fn format(&self, formatter: &mut Formatter, _: Nesting) -> fmt::Result {
25        if let Some((_, escaped)) =
26            ESCAPED_TEXT_CHARACTERS.iter().find(|(c, _)| *c == *self)
27        {
28            formatter.write_str(escaped)
29        } else {
30            formatter.write_char(*self)
31        }
32    }
33}
34
35impl Formattable for &'_ str {
36    fn format(
37        &self,
38        formatter: &mut Formatter,
39        nesting: Nesting,
40    ) -> fmt::Result {
41        self.chars()
42            .map(|character| character.format(formatter, nesting))
43            .collect()
44    }
45}
46
47impl Formattable for String {
48    fn format(
49        &self,
50        formatter: &mut Formatter,
51        nesting: Nesting,
52    ) -> fmt::Result {
53        self.as_str().format(formatter, nesting)
54    }
55}
56
57impl<T: Formattable> Formattable for &'_ [T] {
58    fn format(
59        &self,
60        formatter: &mut Formatter,
61        nesting: Nesting,
62    ) -> fmt::Result {
63        self.iter().map(|x| x.format(formatter, nesting)).collect()
64    }
65}
66
67impl<T: Formattable> Formattable for Vec<T> {
68    fn format(
69        &self,
70        formatter: &mut Formatter,
71        nesting: Nesting,
72    ) -> fmt::Result {
73        self.as_slice().format(formatter, nesting)
74    }
75}
76
77impl<T: Formattable + ?Sized> Formattable for Box<T> {
78    fn format(
79        &self,
80        formatter: &mut Formatter,
81        nesting: Nesting,
82    ) -> fmt::Result {
83        self.deref().format(formatter, nesting)
84    }
85}
86
87/// Represents HTML text. Can be created with [`html`].
88///
89/// [`html`]: ./fn.html.html
90#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
91#[must_use = "HTML needs to be turned into a String with `.to_string()`"]
92pub struct Html<T>(T);
93
94/// Creates HTML text.
95pub fn html<T: Formattable>(content: T) -> Html<T> {
96    Html(content)
97}
98
99impl<T: Formattable> Formattable for Html<T> {
100    fn format(
101        &self,
102        formatter: &mut Formatter,
103        nesting: Nesting,
104    ) -> fmt::Result {
105        self.0.format(formatter, nesting)
106    }
107}
108
109impl<T: Formattable> Display for Html<T> {
110    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
111        self.format(formatter, Nesting::default())
112    }
113}