html_node_core/typed/
mod.rs

1#![allow(clippy::module_name_repetitions)]
2#![allow(non_snake_case)]
3
4pub mod elements;
5#[doc(hidden)]
6pub use paste::paste;
7
8use crate::Node;
9
10/// A typed HTML element.
11pub trait TypedElement {
12    /// The attributes of the element.
13    type Attributes;
14
15    /// Create an element from its attributes.
16    fn from_attributes(
17        attributes: Self::Attributes,
18        other_attributes: Vec<(String, Option<String>)>,
19    ) -> Self;
20
21    /// Convert the typed element into a [`Node`].
22    fn into_node(self, children: Option<Vec<Node>>) -> Node;
23}
24
25/// A typed set of HTML attributes.
26pub trait TypedAttributes {
27    /// Convert the typed attributes into a set of attributes.
28    fn into_attributes(self) -> Vec<(String, Option<String>)>;
29}
30
31/// A typed attribute.
32#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
33pub enum Attribute<T> {
34    /// The attribute is present and has a value.
35    ///
36    /// ```html
37    /// <div id="test"></div>
38    /// ```
39    Present(T),
40
41    /// The attribute is present but has no value.
42    ///
43    /// ```html
44    /// <div hidden></div>
45    /// ```
46    Empty,
47
48    /// The attribute is not present.
49    #[default]
50    Missing,
51}
52
53impl<T> Attribute<T> {
54    /// Convert the attribute into a double layered [`Option`].
55    pub fn into_option(self) -> Option<Option<T>> {
56        match self {
57            Self::Present(value) => Some(Some(value)),
58            Self::Empty => Some(None),
59            Self::Missing => None,
60        }
61    }
62}
63
64#[allow(missing_docs)]
65#[macro_export]
66macro_rules! typed_elements {
67    ($vis:vis $($ElementName:ident $(($name:literal))? $([$AttributeName:ident])? $({ $($attribute:ident),* $(,)? })?;)*) => {
68        $(
69            $crate::typed_element!{
70                $vis $ElementName $(($name))? $([$AttributeName])? $({ $($attribute),* })?
71            }
72        )*
73    };
74}
75
76#[allow(missing_docs)]
77#[macro_export]
78macro_rules! typed_element {
79    ($vis:vis $ElementName:ident $(($name:literal))? $([$AttributeName:ident])? $({ $($attribute:ident $(: $atype:ty)?),* $(,)? })?) => {
80        $crate::typed_attributes!{
81            ($vis $ElementName) $([$vis $AttributeName])? $({
82                accesskey,
83                autocapitalize,
84                autofocus,
85                class,
86                contenteditable,
87                dir,
88                draggable,
89                enterkeyhint,
90                exportparts,
91                hidden,
92                id,
93                inert,
94                inputmode,
95                is,
96                itemid,
97                itemprop,
98                itemref,
99                itemscope,
100                itemtype,
101                lang,
102                nonce,
103                part,
104                popover,
105                role,
106                slot,
107                spellcheck,
108                style,
109                tabindex,
110                title,
111                translate,
112                virtualkeyboardpolicy,
113                $($attribute $(: $atype)?),*
114            })?
115        }
116
117        #[derive(::std::fmt::Debug, ::std::default::Default)]
118        #[allow(non_camel_case_types)]
119        #[allow(missing_docs)]
120        $vis struct $ElementName {
121            $vis attributes: <Self as $crate::typed::TypedElement>::Attributes,
122            $vis other_attributes: ::std::vec::Vec<(::std::string::String, ::std::option::Option<::std::string::String>)>,
123        }
124
125        impl $crate::typed::TypedElement for $ElementName {
126            type Attributes = $crate::typed_attributes!(@NAME ($ElementName) $([$AttributeName])?);
127
128            fn from_attributes(
129                attributes: Self::Attributes,
130                other_attributes: ::std::vec::Vec<(::std::string::String, ::std::option::Option<::std::string::String>)>,
131            ) -> Self {
132                Self { attributes, other_attributes }
133            }
134
135            fn into_node(mut self, children: ::std::option::Option<::std::vec::Vec<$crate::Node>>) -> $crate::Node {
136                let mut attributes = $crate::typed::TypedAttributes::into_attributes(self.attributes);
137                attributes.append(&mut self.other_attributes);
138
139                $crate::Node::Element(
140                    $crate::Element {
141                        name: ::std::convert::From::from($crate::typed_element!(@NAME_STR $ElementName$(($name))?)),
142                        attributes,
143                        children,
144                    }
145                )
146            }
147        }
148    };
149    (@NAME_STR $ElementName:ident) => {
150        stringify!($ElementName)
151    };
152    (@NAME_STR $ElementName:ident($name:literal)) => {
153        $name
154    };
155}
156
157#[allow(missing_docs)]
158#[macro_export]
159macro_rules! typed_attributes {
160    {
161        $(($vise:vis $ElementName:ident))? $([$visa:vis $AttributeName:ident])? {
162            $($attribute:ident $(: $atype:ty)?),* $(,)?
163        }
164    } => {
165        $crate::typed_attributes!(@STRUCT $(($vise $ElementName))? $([$visa $AttributeName])? { $($attribute $(: $atype)?),* });
166
167        impl $crate::typed::TypedAttributes for $crate::typed_attributes!(@NAME $(($ElementName))? $([$AttributeName])?) {
168            fn into_attributes(self) -> ::std::vec::Vec<(::std::string::String, ::std::option::Option<::std::string::String>)> {
169                [$((::std::stringify!($attribute), self.$attribute.into_option().map(|opt| opt.map(|a| ::std::string::ToString::to_string(&a))))),*]
170                    .into_iter()
171                    .flat_map(|(key, maybe_value)| {
172                        maybe_value.map(|value| (key.strip_prefix("r#").unwrap_or(key).replace('_', "-"), value))
173                    })
174                    .collect()
175            }
176        }
177    };
178    (($_vise:vis $_ElementName:ident) $([$_visa:vis $_AttributeName:ident])?) => {};
179    (@NAME ($ElementName:ident)) => {
180        $crate::typed::paste!([< $ElementName:camel Attributes >])
181    };
182    (@NAME $(($ElementName:ident))? [$AttributeName:ident]) => {
183        $AttributeName
184    };
185    {
186        @STRUCT ($vis:vis $ElementName:ident) {
187            $($attribute:ident $(:$atype:ty)?),* $(,)?
188        }
189    } => {
190        $crate::typed::paste! {
191            #[derive(::std::fmt::Debug, ::std::default::Default)]
192            #[allow(missing_docs)]
193            $vis struct [< $ElementName:camel Attributes >] {
194                $($vis $attribute: $crate::typed::Attribute<$crate::typed_attributes!(@ATTR_TYPE $($atype)?)>),*
195            }
196        }
197    };
198    {
199        @STRUCT $(($_vis:vis $ElementName:ident))? [$vis:vis $AttributeName:ident] {
200            $($attribute:ident $(: $atype:ty)?),* $(,)?
201        }
202    } => {
203        #[derive(::std::fmt::Debug, ::std::default::Default)]
204        #[allow(missing_docs)]
205        $vis struct $AttributeName {
206            $($vis $attribute: $crate::typed::Attribute<$crate::typed_attributes!(@ATTR_TYPE $($atype)?)>),*
207        }
208    };
209    (@ATTR_TYPE $atype:ty) => {$atype};
210    (@ATTR_TYPE) => {::std::string::String};
211}
212
213#[allow(missing_docs)]
214#[macro_export]
215macro_rules! typed_component_attributes {
216    {
217        $(($vise:vis $ElementName:ident))? $([$visa:vis $AttributeName:ident])? {
218            $($attribute:ident: $atype:ty),* $(,)?
219        }
220    } => {
221        $crate::typed_component_attributes!(@STRUCT $(($vise $ElementName))? $([$visa $AttributeName])? { $($attribute: $atype),* });
222    };
223    (($_vise:vis $_ElementName:ident) $([$_visa:vis $_AttributeName:ident])?) => {};
224    {
225        @STRUCT ($vis:vis $ElementName:ident) {
226            $($attribute:ident: $atype:ty),* $(,)?
227        }
228    } => {
229        $crate::typed::paste! {
230            #[derive(::std::fmt::Debug)]
231            #[allow(missing_docs)]
232            $vis struct [< $ElementName:camel Attributes >] {
233                $($vis $attribute: $atype),*
234            }
235        }
236    };
237    {
238        @STRUCT $(($_vis:vis $ElementName:ident))? [$vis:vis $AttributeName:ident] {
239            $($attribute:ident: $atype:ty),* $(,)?
240        }
241    } => {
242        #[derive(::std::fmt::Debug)]
243        #[allow(missing_docs)]
244        $vis struct $AttributeName {
245            $($vis $attribute: $atype),*
246        }
247    };
248}
249
250#[allow(missing_docs)]
251#[macro_export]
252macro_rules! typed_component {
253    (
254        $vis:vis $ElementName:ident $([$AttributeName:ident])? $({
255                $($attribute:ident $(: $atype:ty)?),* $(,)?
256        })?;
257
258        |$attributes:pat_param, $extra_attributes:pat_param, $children:pat_param| $body:expr
259    ) => {
260        $crate::typed_component_attributes!{
261            ($vis $ElementName) $([$vis $AttributeName])? $({
262                $($attribute $(: $atype)?),*
263            })?
264        }
265
266        #[derive(::std::fmt::Debug)]
267        #[allow(non_camel_case_types)]
268        #[allow(missing_docs)]
269        $vis struct $ElementName {
270            $vis attributes: <Self as $crate::typed::TypedElement>::Attributes,
271            $vis extra_attributes: ::std::vec::Vec<(::std::string::String, ::std::option::Option<::std::string::String>)>,
272        }
273
274        impl $crate::typed::TypedElement for $ElementName {
275            type Attributes = $crate::typed_attributes!(@NAME ($ElementName) $([$AttributeName])?);
276
277            fn from_attributes(
278                attributes: Self::Attributes,
279                extra_attributes: ::std::vec::Vec<(::std::string::String, ::std::option::Option<::std::string::String>)>,
280            ) -> Self {
281                Self { attributes, extra_attributes }
282            }
283
284            fn into_node(self, $children: ::std::option::Option<::std::vec::Vec<$crate::Node>>) -> $crate::Node {
285                let $attributes = self.attributes;
286                let $extra_attributes = self.extra_attributes;
287
288                {
289                    $body
290                }
291            }
292        }
293    };
294}