1use std::{collections::HashMap, fmt::Display};
2
3pub trait TurseElement {
4 const TAG: &'static str;
5 const ATTRIBUTES: &'static [&'static str];
6}
7
8pub enum Node {
9 Element {
10 tag: String,
11 attrs: HashMap<String, AttrValue>,
12 children: Vec<Node>,
13 },
14 Body(String),
15}
16
17pub trait IntoNode {
18 fn into_inner_node(self) -> Node;
19}
20
21impl IntoNode for String {
22 fn into_inner_node(self) -> Node {
23 Node::Body(self)
24 }
25}
26
27impl IntoNode for &str {
28 fn into_inner_node(self) -> Node {
29 Node::Body(self.to_string())
30 }
31}
32
33impl IntoNode for Node {
34 fn into_inner_node(self) -> Node {
35 self
36 }
37}
38
39impl From<String> for Node {
40 fn from(s: String) -> Self {
41 Node::Body(s)
42 }
43}
44
45impl From<&str> for Node {
46 fn from(s: &str) -> Self {
47 Node::Body(s.to_string())
48 }
49}
50
51impl<T> From<Vec<T>> for Node
52where
53 T: IntoNode,
54{
55 fn from(vec: Vec<T>) -> Self {
56 Node::Element {
57 tag: "fragment".to_string(),
58 attrs: HashMap::new(),
59 children: vec.into_iter().map(|t| t.into_inner_node()).collect(),
60 }
61 }
62}
63
64#[cfg(debug_assertions)]
65impl std::fmt::Debug for Node {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 Node::Element {
69 tag,
70 attrs,
71 children,
72 } => f
73 .debug_struct("Element")
74 .field("tag", tag)
75 .field("attrs", attrs)
76 .field("children", children)
77 .finish(),
78 Node::Body(s) => f.debug_tuple("Body").field(&s.to_string()).finish(),
79 }
80 }
81}
82
83impl Clone for Node {
84 fn clone(&self) -> Self {
85 match self {
86 Node::Element {
87 tag,
88 attrs,
89 children,
90 } => Node::Element {
91 tag: tag.clone(),
92 attrs: attrs.clone(),
93 children: children.clone(),
94 },
95 Node::Body(s) => Node::Body(s.clone()),
96 }
97 }
98}
99
100#[cfg(debug_assertions)]
101impl PartialEq for Node {
102 fn eq(&self, other: &Self) -> bool {
103 match (self, other) {
104 (
105 Node::Element {
106 tag: t1,
107 attrs: a1,
108 children: c1,
109 },
110 Node::Element {
111 tag: t2,
112 attrs: a2,
113 children: c2,
114 },
115 ) => t1 == t2 && a1 == a2 && c1 == c2,
116 (Node::Body(s1), Node::Body(s2)) => s1 == s2,
117 _ => false,
118 }
119 }
120}
121
122#[cfg_attr(debug_assertions, derive(Debug))]
123#[derive(Clone)]
124pub enum AttrValue {
125 Text(String),
126 Float(f64),
127 Int(i64),
128 Bool(bool),
129 Expr(fn() -> Box<dyn Display>),
130}
131
132#[cfg(debug_assertions)]
133impl PartialEq for AttrValue {
134 fn eq(&self, other: &Self) -> bool {
135 match (self, other) {
136 (Self::Text(lv), Self::Text(rv)) => lv == rv,
137 (Self::Float(lv), Self::Float(rv)) => lv == rv,
138 (Self::Int(lv), Self::Int(rv)) => lv == rv,
139 (Self::Bool(lv), Self::Bool(rv)) => lv == rv,
140 _ => false,
141 }
142 }
143}
144
145impl quote::ToTokens for AttrValue {
146 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
147 match self {
148 AttrValue::Text(s) => quote::quote!(AttrValue::Text(#s.to_string())).to_tokens(tokens),
149 AttrValue::Float(f) => quote::quote!(AttrValue::Float(#f)).to_tokens(tokens),
150 AttrValue::Int(i) => quote::quote!(AttrValue::Int(#i)).to_tokens(tokens),
151 AttrValue::Bool(b) => quote::quote!(AttrValue::Bool(#b)).to_tokens(tokens),
152 AttrValue::Expr(_) => {
153 quote::quote!(AttrValue::Expr(fn() -> Box<dyn Display>)).to_tokens(tokens)
154 }
155 }
156 }
157}
158
159pub trait IntoAttrValue {
160 fn into_attr_value(self) -> AttrValue;
161}
162
163impl<T> IntoAttrValue for T
164where
165 AttrValue: From<T>,
166{
167 fn into_attr_value(self) -> AttrValue {
168 AttrValue::from(self)
169 }
170}
171
172impl From<i64> for AttrValue {
173 fn from(v: i64) -> Self {
174 AttrValue::Int(v)
175 }
176}
177
178impl From<i32> for AttrValue {
179 fn from(v: i32) -> Self {
180 AttrValue::Int(v as i64)
181 }
182}
183
184impl From<i16> for AttrValue {
185 fn from(v: i16) -> Self {
186 AttrValue::Int(v as i64)
187 }
188}
189
190impl From<i8> for AttrValue {
191 fn from(v: i8) -> Self {
192 AttrValue::Int(v as i64)
193 }
194}
195
196impl From<u64> for AttrValue {
197 fn from(v: u64) -> Self {
198 AttrValue::Int(v as i64)
199 }
200}
201
202impl From<u32> for AttrValue {
203 fn from(v: u32) -> Self {
204 AttrValue::Int(v as i64)
205 }
206}
207
208impl From<u16> for AttrValue {
209 fn from(v: u16) -> Self {
210 AttrValue::Int(v as i64)
211 }
212}
213
214impl From<u8> for AttrValue {
215 fn from(v: u8) -> Self {
216 AttrValue::Int(v as i64)
217 }
218}
219
220impl From<f64> for AttrValue {
221 fn from(v: f64) -> Self {
222 AttrValue::Float(v)
223 }
224}
225
226impl From<f32> for AttrValue {
227 fn from(v: f32) -> Self {
228 AttrValue::Float(v as f64)
229 }
230}
231
232impl From<String> for AttrValue {
233 fn from(v: String) -> Self {
234 AttrValue::Text(v)
235 }
236}
237
238impl From<bool> for AttrValue {
239 fn from(v: bool) -> Self {
240 AttrValue::Bool(v)
241 }
242}
243
244impl From<char> for AttrValue {
245 fn from(v: char) -> Self {
246 AttrValue::Text(v.to_string())
247 }
248}
249
250impl From<&str> for AttrValue {
251 fn from(v: &str) -> Self {
252 AttrValue::Text(v.to_string())
253 }
254}
255
256#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
257#[derive(Clone)]
258pub struct Element {
259 pub inner: Option<Node>,
260}
261
262impl Element {
263 pub fn new(inner: Node) -> Self {
264 Self { inner: Some(inner) }
265 }
266
267 pub fn empty() -> Self {
268 Self { inner: None }
269 }
270}
271
272#[allow(non_camel_case_types)]
273#[allow(non_upper_case_globals)]
274pub mod elements {
275 use super::TurseElement;
276
277 pub struct block;
278 impl TurseElement for block {
279 const TAG: &'static str = "block";
280 const ATTRIBUTES: &'static [&'static str] = &["width"];
281 }
282 pub struct text;
283 impl TurseElement for text {
284 const TAG: &'static str = "text";
285 const ATTRIBUTES: &'static [&'static str] = &["width"];
286 }
287
288 pub struct input;
289 impl TurseElement for input {
290 const TAG: &'static str = "input";
291 const ATTRIBUTES: &'static [&'static str] = &["width"];
292 }
293
294 pub struct dropdown;
295 impl TurseElement for dropdown {
296 const TAG: &'static str = "dropdown";
297 const ATTRIBUTES: &'static [&'static str] = &["width"];
298 }
299}