1use std::str::FromStr;
2
3use html_parser::{Dom, Node};
4use proc_macro2::{Ident, LineColumn, TokenStream, TokenTree};
5
6use quote::{format_ident, quote, quote_spanned};
7use syn::{parse::Parser, parse_macro_input, DeriveInput};
8
9struct DomParsed {
10 fields: Vec<syn::Field>,
11 root_type: Option<syn::Path>,
12 root_is_element: bool,
13 build: TokenStream,
14 errors: TokenStream,
15}
16
17static ELEM_INPUT: &[(&str, &str, &str)] = &[
18 ("body", "Base", "HtmlElement"),
19 ("div", "Div", "HtmlElement"),
20 ("p", "Paragraph", "HtmlElement"),
21 ("span", "Span", "HtmlSpanElement"),
22 ("input", "Input", "HtmlInputElement"),
23 ("button", "Button", "HtmlButtonElement"),
24];
25
26fn parse_args(args: TokenStream, s_fields: &syn::FieldsNamed) -> DomParsed {
27 let args: Vec<TokenTree> = args.into_iter().collect();
28 let dom = parse_dom(&args);
29 match dom {
30 Ok(dom) => gen_element(dom, s_fields),
31 Err(e) => {
32 let e = e.to_string();
33 let dom_start = args.first().expect("dom has a start").span();
34 let dom_end = args.last().expect("dom has an end").span();
35 let dom_span = dom_start.join(dom_end).expect("creating dom span");
36 DomParsed {
37 fields: Default::default(),
38 root_type: None,
39 root_is_element: true,
40 build: quote! {},
41 errors: quote_spanned! {
42 dom_span => compile_error!(#e)
43 },
44 }
45 }
46 }
47}
48
49fn parse_dom(input: &[TokenTree]) -> html_parser::Result<Dom> {
50 let mut html = String::new();
51 let mut end: Option<LineColumn> = None;
52 let mut offset: Option<usize> = None;
53 for token in input {
54 let span = token.span().start();
55 if offset.is_none() {
56 offset = Some(span.column);
57 }
58 if let Some(end) = end {
59 if span.line > end.line {
60 html.push('\n');
61 html.push_str(
62 &" ".repeat(
63 span.column
64 .saturating_sub(offset.expect("html span cannot underflow")),
65 ),
66 )
67 } else {
68 html.push_str(&" ".repeat(span.column - end.column))
69 }
70 } else {
71 html.push_str(
72 &" ".repeat(
73 span.column
74 .saturating_sub(offset.expect("html span cannot underflow")),
75 ),
76 )
77 }
78 end = Some(token.span().end());
79 html.push_str(&token.to_string());
80 }
81 Dom::parse(&html)
82}
83
84fn walk_dom(dom: &[Node], refs: &mut Vec<(Ident, syn::Path)>) -> Vec<(bool, TokenStream)> {
85 let mut elements = Vec::new();
86 for node in dom {
87 if let Node::Element(element) = node {
88 let mut is_field = None;
90
91 let mut is_custom = None;
93
94 let mut is_repeat = None;
96
97 let mut attributes = Vec::new();
99
100 for (key, value) in element.attributes.iter() {
101 if key == "we_field" {
102 is_field = value.clone()
103 } else if key == "we_element" {
104 let custom = syn::parse2::<syn::Path>(
106 TokenStream::from_str(&element.name).expect("custom path name tokenstream"),
107 )
108 .expect("custom path tokenstream");
109 is_custom = Some(custom);
110
111 if !element.children.is_empty() {
113 return vec![(
114 false,
115 quote! {
116 compile_error!("`we_element` element cant have any children")
117 },
118 )];
119 }
120 } else if key == "we_repeat" {
121 if let Some(n) = value {
122 if let Ok(n) = n.parse::<i64>() {
123 is_repeat = Some(n);
124 } else {
125 return vec![(
126 false,
127 quote! {
128 compile_error!("`we_repeat` mut have a positive interger value")
129 },
130 )];
131 }
132 } else {
133 return vec![(
134 false,
135 quote! {
136 compile_error!("`we_repeat` needs a value")
137 },
138 )];
139 }
140 } else {
141 attributes.push((key, value));
142 }
143 }
144 let name = &element.name;
145 let field = ELEM_INPUT.iter().find_map(|s| {
147 if name.to_lowercase() == s.0 {
148 Some(format_ident!("{}", s.1))
149 } else {
150 None
151 }
152 });
153
154 if field.is_none() && is_custom.is_none() {
156 let error = format!("element `{}` not implemented", name.to_lowercase());
157 return vec![(false, quote! { compile_error!(#error) })];
158 }
159
160 let elem_type = is_custom.clone().unwrap_or_else(|| {
162 let field = syn::parse2::<syn::Path>(quote! { webelements::elem::#field })
163 .expect("custom element field name");
164 syn::parse2::<syn::Path>(quote! { webelements::Element<#field> })
165 .expect("custom element field path")
166 });
167
168 let field_type = if is_repeat.is_some() {
170 syn::parse2::<syn::Path>(quote! { Vec<#elem_type> }).expect("field type name")
171 } else {
172 elem_type.clone()
173 };
174
175 if let Some(field) = is_field.as_ref() {
176 let field = format_ident!("{}", field);
177 refs.push((field, field_type.clone()));
178 }
179
180 let children = walk_dom(&element.children, refs);
182
183 let ident = format_ident!("_e_{}", element.name);
184 let text = element.children.iter().find_map(|n| {
185 if let Node::Text(s) = n {
186 Some(s.clone())
187 } else {
188 None
189 }
190 });
191 let text = text.iter();
193
194 let classes = element.classes.iter();
195 let attributes = attributes.iter().map(|&(k, v)| {
196 let v = v.clone().unwrap_or_else(|| "".to_owned());
197 quote! { (#k, #v) }
198 });
199 let mut field_ident = is_field.iter().map(|s| format_ident!("_m_{}", s));
200 let repeat_field = field_ident.clone();
201 let element_builder = match is_custom.as_ref() {
202 Some(custom) => {
203 quote! { <#custom as webelements::WebElementBuilder>::build() }
204 }
205 None => quote! { <#elem_type>::new() },
206 };
207 if is_field.is_some() && is_repeat.is_some() {
208 field_ident.next();
209 }
210 let single = children
211 .iter()
212 .filter_map(|(r, c)| if !*r { Some(c) } else { None });
213 let lists = children
214 .iter()
215 .filter_map(|(r, c)| if *r { Some(c) } else { None });
216
217 let mut tokens = quote! {
218 let mut #ident = #element_builder?;
219 #( #ident.append(&{#single})?; )*
220 #( #ident.append_list({#lists})?; )*
221 #( #ident.add_class(#classes); )*
222 #(
223 let (key, value) = #attributes;
224 #ident.set_attr(key, value)?;
225 )*
226 #( #ident.set_text(#text); )*
227 #( #field_ident = Some(#ident.clone()); )*
228 #ident
229 };
230 if let Some(n) = is_repeat {
231 let n = n as usize;
232 let iter = (0..n).map(|n| n.to_string());
233 tokens = quote! {
234 let mut _elem_list = Vec::with_capacity(#n);
235 #(_elem_list.push({
236 let i = #iter;
237 #tokens
238 });)*
239 #( #repeat_field = Some(_elem_list.clone()); )*
240 _elem_list
241 };
242 }
243 elements.push((is_repeat.is_some(), tokens));
244 }
245 }
246 elements
247}
248
249fn gen_element(dom: Dom, s_fields: &syn::FieldsNamed) -> DomParsed {
250 let mut refs: Vec<(Ident, syn::Path)> = Vec::new();
251 let mut errors = quote! {};
252 if dom.children.len() != 1 {
253 errors = quote! {
254 #errors
255 compile_error!("DOM should contain 1 root")
256 };
257 }
258 let mut root_is_element = true;
259 let root_type = dom
260 .children
261 .first()
262 .map(|e| {
263 if let Node::Element(e) = e {
264 let name = ELEM_INPUT.iter().find_map(|s| {
265 if e.name.to_lowercase() == s.0 {
266 Some(format_ident!("{}", s.1))
267 } else {
268 None
269 }
270 });
271 if let Some(name) = name {
272 syn::parse2::<syn::Path>(quote! { webelements::elem::#name }).ok()
273 } else {
274 root_is_element = false;
275 let name = format_ident!("{}", e.name);
276 syn::parse2::<syn::Path>(quote! { #name }).ok()
277 }
278 } else {
279 None
280 }
281 })
282 .unwrap_or_else(|| {
283 errors = quote! { #errors; compile_error!("no root found") };
284 None
285 });
286 let elements = walk_dom(&dom.children, &mut refs);
287 let root = &elements.first().expect("element needs to have a root").1;
288 let ref_name: Vec<Ident> = refs.iter().map(|(s, _)| format_ident!("{}", s)).collect();
289 let ref_value: Vec<Ident> = refs
290 .iter()
291 .map(|(s, _)| format_ident!("_m_{}", s))
292 .collect();
293 let fields = s_fields.named.iter().map(|f| f.ident.as_ref()).flatten();
294 let types = s_fields.named.iter().map(|f| &f.ty);
295 let token = quote!(
296 fn build() -> webelements::Result<Self> {
297 #( let mut #ref_value = None; )*
298 let _e_root = {#root};
299 let mut element = Self {
300 root: _e_root,
301 #( #fields: <#types as Default>::default(),)*
302 #( #ref_name: #ref_value.unwrap(),)*
303 };
304 <Self as webelements::WebElement>::init(&mut element)?;
305 Ok(element)
306 }
307 );
308 DomParsed {
309 fields: refs
310 .iter()
311 .map(|(ident, ty)| {
312 syn::Field::parse_named
313 .parse2(quote! { pub #ident: #ty })
314 .expect("fields name")
315 })
316 .collect(),
317 root_type,
318 root_is_element,
319 build: token,
320 errors,
321 }
322}
323
324#[proc_macro_attribute]
325pub fn we_builder(
326 args: proc_macro::TokenStream,
327 input: proc_macro::TokenStream,
328) -> proc_macro::TokenStream {
329 let tokens = {
330 let mut ast = parse_macro_input!(input as DeriveInput);
331 let ident = ast.ident.clone();
332 if let syn::Data::Struct(ref mut struct_data) = &mut ast.data {
333 if let syn::Fields::Named(s_fields) = &mut struct_data.fields {
334 let DomParsed {
335 fields,
336 root_type,
337 root_is_element,
338 build,
339 errors,
340 } = parse_args(args.into(), s_fields);
341 let elem = if root_is_element {
342 quote! { #root_type }
343 } else {
344 quote! { <#root_type as WebElementBuilder>::Elem }
345 };
346 let root = if root_is_element {
347 quote! { webelements::Element<#root_type> }
348 } else {
349 quote! { #root_type }
350 };
351 s_fields.named.push(
352 syn::Field::parse_named
353 .parse2(quote! { pub root: #root })
354 .expect("root field token failed"),
355 );
356 for field in fields.iter() {
357 s_fields.named.push(field.clone())
358 }
359
360 return quote! {
361 #errors
362 #ast
363
364 impl webelements::WebElementBuilder for #ident {
365 type Elem = #elem;
366
367 #build
368 }
369
370 impl AsRef<webelements::Element<<Self as webelements::WebElementBuilder>::Elem>> for #ident {
371 fn as_ref(&self) -> &webelements::Element<<Self as webelements::WebElementBuilder>::Elem> {
372 self.root.as_ref()
373 }
374 }
375
376 impl std::ops::Deref for #ident {
377 type Target=webelements::Element<<Self as webelements::WebElementBuilder>::Elem>;
378 fn deref(&self) -> &Self::Target {
379 self.root.as_ref()
380 }
381 }
382 }
383 .into();
384 }
385 }
386 (quote! {
387 compile_error!("`we_element` is only valid on structs")
388 })
389 .into()
390 };
391 println!("{}", tokens);
392 tokens
393}
394
395#[proc_macro_derive(WebElement)]
396pub fn we_element_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
397 let ast = parse_macro_input!(input as DeriveInput);
398 let ident = ast.ident;
399
400 (quote! {
401 impl webelements::WebElement for #ident {
402 fn init(&mut self) -> webelements::Result<()> { Ok(()) }
403 }
404 })
405 .into()
406}
407
408#[proc_macro]
409pub fn element_types(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
410 let elems = ELEM_INPUT.iter().map(|s| s.0);
411 let names = ELEM_INPUT.iter().map(|s| format_ident!("{}", s.1));
412 let types = ELEM_INPUT.iter().map(|s| format_ident!("{}", s.2));
413 let tokens = quote! {
414 #(
415 #[derive(Debug, Clone)]
416 pub struct #names;
417 impl ElemTy for #names {
418 type Elem = web_sys::#types;
419
420 fn make() -> crate::Result<Self::Elem> {
421 crate::document()?
422 .create_element(#elems)?
423 .dyn_into::<web_sys::#types>()
424 .map_err(|e| crate::Error::Cast(std::any::type_name::<web_sys::#types>()))
425 }
426 }
427 )*
428 };
429 tokens.into()
430}
431
432#[cfg(test)]
433mod tests {}