write_html/
tag.rs

1use std::fmt::Write;
2
3use crate::{AttributeName, AttributeValue, escapes::StringEscaper, Attributes};
4
5struct TagOpeningData<'a, 't, W: Write> {
6    tag: &'t str,
7    w: &'a mut W,
8    compactability: Compactability,
9}
10
11
12/// Represents a tag that is being opened.
13pub struct TagOpening<'a, 't, W: Write> {
14    data: Option<TagOpeningData<'a, 't, W>>,
15}
16
17impl<'a, 't, W: Write> TagOpening<'a, 't, W> {
18    /// Creates a new `TagOpening` that will write to `w`.
19    pub fn new(tag: &'t str, w: &'a mut W, compactability: Compactability) -> Result<Self, std::fmt::Error> {
20        w.write_str("<")?;
21        w.write_str(tag)?;
22        Ok(Self { data: Some(TagOpeningData { tag, w, compactability }) })
23    }
24
25    /// Adds an attribute to the tag.
26    ///
27    /// # Arguments
28    /// * `name` - The name of the attribute.
29    /// * `value` - The value of the attribute.
30    pub fn attr<'s>(
31        &'s mut self,
32        name: impl AttributeName,
33        value: impl AttributeValue
34    ) -> Result<&'s mut Self, std::fmt::Error> {
35        let data = self.data.as_mut().unwrap();
36        data.w.write_str(" ").unwrap();
37
38        assert!(name.is_valid_attribute_name());
39        name.write_attribute_name(data.w)?;
40
41        if value.is_unit() {
42            return Ok(self);
43        } else {
44            data.w.write_str("=\"")?;
45            value.write_attribute_value(&mut StringEscaper::new(data.w))?;
46            data.w.write_str("\"")?;
47        }
48
49        Ok(self)
50    }
51
52    /// Adds an attribute to the tag.
53    ///
54    /// See [`attr`] for more information.
55    pub fn with_attr(mut self, name: impl AttributeName, value: impl AttributeValue) -> Result<Self, std::fmt::Error> {
56        self.attr(name, value)?;
57
58        Ok(self)
59    }
60
61    /// Adds multiple attributes to the tag.
62    pub fn with_attributes(mut self, attributes: impl Attributes) -> Result<Self, std::fmt::Error> {
63        attributes.write_attributes(&mut self)?;
64        Ok(self)
65    }
66
67    /// Finishes the opening of the tag and returns a [`InsideTagHtml`] that can be used to write the contents of the tag.
68    pub fn inner_html(mut self) -> Result<InsideTagHtml<'a, 't, W>, std::fmt::Error> {
69        // get the data out of self
70        let data = self.data.take().unwrap();
71        data.w.write_str(">")?;
72        Ok(InsideTagHtml { tag: data.tag, w: data.w })
73    }
74}
75
76impl<'a, 't, W: Write> Drop for TagOpening<'a, 't, W> {
77    fn drop(&mut self) {
78        if let Some(data) = self.data.take() {
79            if let Compactability::Yes { final_slash } = data.compactability {
80                let _ = data.w.write_str(if final_slash { "/>" } else { ">" });
81            } else {
82                let _ = data.w.write_fmt(format_args!("></{}>", data.tag));
83            }
84        }
85    }
86}
87
88/// Represents the environment inside a tag.
89pub struct InsideTagHtml<'a, 't, W: Write> {
90    tag: &'t str,
91    w: &'a mut W,
92}
93
94impl<'a, 't, W: Write> InsideTagHtml<'a, 't, W> {
95    // TODO
96}
97
98impl<'a, 't, W: Write> Drop for InsideTagHtml<'a, 't, W> {
99    fn drop(&mut self) {
100        let _ = self.w.write_str("</");
101        let _ = self.w.write_str(self.tag);
102        let _ = self.w.write_str(">");
103    }
104}
105
106impl<'a, 't, W: Write> Write for InsideTagHtml<'a, 't, W> {
107    fn write_str(&mut self, s: &str) -> std::fmt::Result {
108        self.w.write_str(s)
109    }
110}
111
112/// Represents the compactability of a tag.
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
114pub enum Compactability {
115    /// The tag is not compactable.
116    ///
117    /// This means that the tag will always be written as `<tag></tag>`.
118    No,
119
120    /// The tag is compactable.
121    ///
122    /// This means that the tag will be written as `<tag/>` if it has no contents.
123    ///
124    /// The `final_slash` parameter determines whether the tag will be written as `<tag>` or `<tag/>`.
125    Yes {
126        /// Wether a compacted tag will be written as `<tag/>` or `<tag>`.
127        final_slash: bool
128    },
129}
130
131impl Compactability {
132    /// Returns wether the tag is compactable.
133    pub fn is_compactable(&self) -> bool {
134        match self {
135            Compactability::No => false,
136            Compactability::Yes { .. } => true,
137        }
138    }
139}
140
141impl From<bool> for Compactability {
142    fn from(b: bool) -> Self {
143        if b {
144            Compactability::Yes { final_slash: true }
145        } else {
146            Compactability::No
147        }
148    }
149}
150
151impl From<Compactability> for bool {
152    fn from(c: Compactability) -> Self {
153        match c {
154            Compactability::No => false,
155            Compactability::Yes { .. } => true,
156        }
157    }
158}