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
10pub trait TypedElement {
12 type Attributes;
14
15 fn from_attributes(
17 attributes: Self::Attributes,
18 other_attributes: Vec<(String, Option<String>)>,
19 ) -> Self;
20
21 fn into_node(self, children: Option<Vec<Node>>) -> Node;
23}
24
25pub trait TypedAttributes {
27 fn into_attributes(self) -> Vec<(String, Option<String>)>;
29}
30
31#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
33pub enum Attribute<T> {
34 Present(T),
40
41 Empty,
47
48 #[default]
50 Missing,
51}
52
53impl<T> Attribute<T> {
54 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}