another_html_builder/
attribute.rs1use std::fmt::{Display, Write};
5
6pub struct EscapedValue<'a>(pub &'a str);
8
9impl std::fmt::Display for EscapedValue<'_> {
10 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11 if self.0.is_empty() {
12 return Ok(());
13 }
14 let mut start: usize = 0;
15 while let Some(index) = self.0[start..].find('"') {
16 if index > 0 {
17 f.write_str(&self.0[start..(start + index)])?;
18 }
19 f.write_str("\\\"")?;
20 let end = start + index + 1;
21 debug_assert!(start < end && end <= self.0.len());
22 start = end;
23 }
24 f.write_str(&self.0[start..])?;
25 Ok(())
26 }
27}
28
29macro_rules! attribute_value {
30 ($type:ty) => {
31 impl AttributeValue for $type {
32 fn render(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 write!(f, "{self}")
34 }
35 }
36 };
37}
38
39pub trait AttributeName {
41 fn render(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
42}
43
44impl AttributeName for &str {
45 fn render(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 f.write_str(self)
47 }
48}
49
50pub trait AttributeValue {
55 fn render(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
56}
57
58impl AttributeValue for &str {
59 fn render(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 EscapedValue(self).fmt(f)
61 }
62}
63
64#[inline]
65fn render_attr_name_only<N: AttributeName>(
66 f: &mut std::fmt::Formatter<'_>,
67 name: &N,
68) -> std::fmt::Result {
69 f.write_char(' ')?;
70 name.render(f)
71}
72
73#[inline]
74fn render_attr<N: AttributeName, V: AttributeValue>(
75 f: &mut std::fmt::Formatter<'_>,
76 name: &N,
77 value: &V,
78) -> std::fmt::Result {
79 render_attr_name_only(f, name)?;
80 f.write_char('=')?;
81 f.write_char('"')?;
82 value.render(f)?;
83 f.write_char('"')
84}
85
86pub struct Attribute<T>(pub T);
138
139impl<N: AttributeName> std::fmt::Display for Attribute<Option<N>> {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 if let Some(ref inner) = self.0 {
142 render_attr_name_only(f, inner)
143 } else {
144 Ok(())
145 }
146 }
147}
148
149impl<N: AttributeName> std::fmt::Display for Attribute<N> {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 render_attr_name_only(f, &self.0)
152 }
153}
154
155impl<N: AttributeName, V: AttributeValue> std::fmt::Display for Attribute<Option<(N, V)>> {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 if let Some((name, value)) = &self.0 {
158 render_attr(f, name, value)
159 } else {
160 Ok(())
161 }
162 }
163}
164
165impl<N: AttributeName, V: AttributeValue> std::fmt::Display for Attribute<(N, V)> {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 let (name, value) = &self.0;
168 render_attr(f, name, value)
169 }
170}
171
172attribute_value!(bool);
173attribute_value!(u8);
174attribute_value!(u16);
175attribute_value!(u32);
176attribute_value!(u64);
177attribute_value!(usize);
178attribute_value!(i8);
179attribute_value!(i16);
180attribute_value!(i32);
181attribute_value!(i64);
182attribute_value!(isize);
183
184#[cfg(test)]
185mod tests {
186 #[test_case::test_case("hello world", "hello world"; "without character to escape")]
187 #[test_case::test_case("a\"b", "a\\\"b"; "with special in the middle")]
188 #[test_case::test_case("\"a", "\\\"a"; "with special at the beginning")]
189 #[test_case::test_case("a\"", "a\\\""; "with special at the end")]
190 fn escaping_attribute(input: &str, expected: &str) {
191 assert_eq!(format!("{}", super::EscapedValue(input)), expected);
192 }
193}