1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use std::fmt::Write;

use crate::{AttributeName, AttributeValue, escapes::StringEscaper, Attributes};

struct TagOpeningData<'a, 't, W: Write> {
    tag: &'t str,
    w: &'a mut W,
    compactability: Compactability,
}


/// Represents a tag that is being opened.
pub struct TagOpening<'a, 't, W: Write> {
    data: Option<TagOpeningData<'a, 't, W>>,
}

impl<'a, 't, W: Write> TagOpening<'a, 't, W> {
    /// Creates a new `TagOpening` that will write to `w`.
    pub fn new(tag: &'t str, w: &'a mut W, compactability: Compactability) -> Result<Self, std::fmt::Error> {
        w.write_str("<")?;
        w.write_str(tag)?;
        Ok(Self { data: Some(TagOpeningData { tag, w, compactability }) })
    }

    /// Adds an attribute to the tag.
    ///
    /// # Arguments
    /// * `name` - The name of the attribute.
    /// * `value` - The value of the attribute.
    pub fn attr<'s>(
        &'s mut self,
        name: impl AttributeName,
        value: impl AttributeValue
    ) -> Result<&'s mut Self, std::fmt::Error> {
        let data = self.data.as_mut().unwrap();
        data.w.write_str(" ").unwrap();

        assert!(name.is_valid_attribute_name());
        name.write_attribute_name(data.w)?;

        if value.is_unit() {
            return Ok(self);
        } else {
            data.w.write_str("=\"")?;
            value.write_attribute_value(&mut StringEscaper::new(data.w))?;
            data.w.write_str("\"")?;
        }

        Ok(self)
    }

    /// Adds an attribute to the tag.
    ///
    /// See [`attr`] for more information.
    pub fn with_attr(mut self, name: impl AttributeName, value: impl AttributeValue) -> Result<Self, std::fmt::Error> {
        self.attr(name, value)?;

        Ok(self)
    }

    /// Adds multiple attributes to the tag.
    pub fn with_attributes(mut self, attributes: impl Attributes) -> Result<Self, std::fmt::Error> {
        attributes.write_attributes(&mut self)?;
        Ok(self)
    }

    /// Finishes the opening of the tag and returns a [`InsideTagHtml`] that can be used to write the contents of the tag.
    pub fn inner_html(mut self) -> Result<InsideTagHtml<'a, 't, W>, std::fmt::Error> {
        // get the data out of self
        let data = self.data.take().unwrap();
        data.w.write_str(">")?;
        Ok(InsideTagHtml { tag: data.tag, w: data.w })
    }
}

impl<'a, 't, W: Write> Drop for TagOpening<'a, 't, W> {
    fn drop(&mut self) {
        if let Some(data) = self.data.take() {
            if let Compactability::Yes { final_slash } = data.compactability {
                let _ = data.w.write_str(if final_slash { "/>" } else { ">" });
            } else {
                let _ = data.w.write_fmt(format_args!("></{}>", data.tag));
            }
        }
    }
}

/// Represents the environment inside a tag.
pub struct InsideTagHtml<'a, 't, W: Write> {
    tag: &'t str,
    w: &'a mut W,
}

impl<'a, 't, W: Write> InsideTagHtml<'a, 't, W> {
    // TODO
}

impl<'a, 't, W: Write> Drop for InsideTagHtml<'a, 't, W> {
    fn drop(&mut self) {
        let _ = self.w.write_str("</");
        let _ = self.w.write_str(self.tag);
        let _ = self.w.write_str(">");
    }
}

impl<'a, 't, W: Write> Write for InsideTagHtml<'a, 't, W> {
    fn write_str(&mut self, s: &str) -> std::fmt::Result {
        self.w.write_str(s)
    }
}

/// Represents the compactability of a tag.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Compactability {
    /// The tag is not compactable.
    ///
    /// This means that the tag will always be written as `<tag></tag>`.
    No,

    /// The tag is compactable.
    ///
    /// This means that the tag will be written as `<tag/>` if it has no contents.
    ///
    /// The `final_slash` parameter determines whether the tag will be written as `<tag>` or `<tag/>`.
    Yes {
        /// Wether a compacted tag will be written as `<tag/>` or `<tag>`.
        final_slash: bool
    },
}

impl Compactability {
    /// Returns wether the tag is compactable.
    pub fn is_compactable(&self) -> bool {
        match self {
            Compactability::No => false,
            Compactability::Yes { .. } => true,
        }
    }
}

impl From<bool> for Compactability {
    fn from(b: bool) -> Self {
        if b {
            Compactability::Yes { final_slash: true }
        } else {
            Compactability::No
        }
    }
}

impl From<Compactability> for bool {
    fn from(c: Compactability) -> Self {
        match c {
            Compactability::No => false,
            Compactability::Yes { .. } => true,
        }
    }
}