we_derive/
lib.rs

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            // flag for if this element will be a member field in the struct
89            let mut is_field = None;
90
91            // flag for if this element is a custom webelement that needs to be build
92            let mut is_custom = None;
93
94            // flag for if this element will be repeated
95            let mut is_repeat = None;
96
97            // list of attributes that the element will have. all crate options will be filtered out
98            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                    // the custom path will be generated from the elements name
105                    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                    // the custom element cant have any children because they can't be appended to it.
112                    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            // find the identifier for the element type in the static list
146            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            // no support for default element types yet.
155            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            // if the element is not custom set the path to it to the parent crate
161            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            // if the element is to be repeated set the field type to `Vec<Field_Type>`
169            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            // recursivly generate code for all the children of this element;
181            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            // some variables will be iterators over Options types because they are optional and when iterated will not generate any code
192            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 {}