Skip to main content

workflow_html_macros/
lib.rs

1//! Procedural macros backing the `workflow-html` crate, providing the
2//! `tree!`, `html!`, and `html_str!` function-like macros for building HTML
3//! node trees, plus the `#[renderable]` attribute macro for deriving HTML
4//! rendering on custom structs.
5
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::{
9    DeriveInput, Meta, Token,
10    ext::IdentExt,
11    parse::{Parse, ParseStream},
12    parse_macro_input,
13    punctuated::Punctuated,
14};
15mod element;
16//mod state;
17mod attributes;
18use element::Nodes;
19//use state::set_attributes;
20use attributes::{AttributeName, AttributeNameString};
21use proc_macro_error3::proc_macro_error;
22
23/// Parses an HTML node tree and expands to the constructed element object
24/// tree without rendering it, allowing the resulting structure to be
25/// inspected or rendered later.
26#[proc_macro]
27#[proc_macro_error]
28pub fn tree(input: TokenStream) -> TokenStream {
29    let nodes = parse_macro_input!(input as Nodes);
30    let ts = quote! {#nodes};
31    //println!("\n===========> Nodes Object tree <===========\n{}\n", ts.to_string());
32    ts.into()
33}
34
35/// Parses an HTML node tree and expands to an expression that builds the
36/// element tree and renders it via `render_tree()`, yielding the resulting
37/// `Html` collection of nodes.
38#[proc_macro]
39#[proc_macro_error]
40pub fn html(input: TokenStream) -> TokenStream {
41    let nodes = parse_macro_input!(input as Nodes);
42    let ts = quote! {#nodes};
43    //println!("\n===========> Nodes Object tree <===========\n{}\n", ts.to_string());
44    quote!({
45        let elements = #ts;
46
47        elements.render_tree()
48    })
49    .into()
50}
51
52/// Parses an HTML node tree and expands to an expression that builds the
53/// element tree and immediately renders it to an HTML `String` via `html()`.
54#[proc_macro]
55#[proc_macro_error]
56pub fn html_str(input: TokenStream) -> TokenStream {
57    let nodes = parse_macro_input!(input as Nodes);
58    let ts = quote! {#nodes};
59    //println!("\n===========> Nodes Object tree <===========\n{}\n", ts.to_string());
60    quote!({
61        let elements = #ts;
62
63        elements.html()
64    })
65    .into()
66}
67
68struct RenderableAttributes {
69    pub tag_name: String,
70}
71
72impl Parse for RenderableAttributes {
73    fn parse(input: ParseStream) -> syn::Result<Self> {
74        let tag_name = AttributeName::parse_separated_nonempty_with(input, syn::Ident::parse_any)?;
75        Ok(RenderableAttributes {
76            tag_name: tag_name.to_string(),
77        })
78    }
79}
80
81/// Attribute macro that turns a named struct into a renderable HTML element.
82///
83/// The macro argument supplies the HTML tag name (e.g. `#[renderable(div)]`),
84/// and each struct field becomes an attribute of that tag. It generates
85/// implementations of the `Render` and `ElementDefaults` traits so the struct
86/// can be emitted as `<tag attrs>children</tag>`. Fields named `children` hold
87/// nested content; `bool` fields render as boolean attributes and `Option`
88/// fields are omitted when `None`. A field-level `#[..(name = "...")]` attribute
89/// overrides the rendered attribute name.
90#[proc_macro_attribute]
91//#[proc_macro_derive(Renderable)]
92#[proc_macro_error]
93pub fn renderable(attr: TokenStream, item: TokenStream) -> TokenStream {
94    let renderable_attr = parse_macro_input!(attr as RenderableAttributes);
95    let tag_name = renderable_attr.tag_name;
96    let format_str = format!("<{tag_name} {{}}>{{}}</{tag_name}>");
97    //println!("renderable_attr: {:?}", tag_name);
98    //let def:proc_macro2::TokenStream = item.clone().into();
99    let ast = parse_macro_input!(item as DeriveInput);
100    let struct_name = &ast.ident;
101    let struct_params = &ast.generics;
102    let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
103    /*let generics_only = ast.generics.clone();
104    let where_clause = match generics_only.where_clause.clone() {
105        Some(where_clause) => quote!{ #where_clause },
106        None => quote!{}
107    };
108    */
109
110    //println!("struct_params: {:#?}", struct_params);
111
112    let mut field_visibility_vec = vec![];
113    let mut field_ident_vec = vec![];
114    let mut field_type_vec = vec![];
115    let mut attrs_ts_vec = vec![];
116    let mut field_names: Vec<String> = vec![];
117
118    //let mut children_field_ts = quote!();
119    if let syn::Data::Struct(syn::DataStruct {
120        fields: syn::Fields::Named(ref fields),
121        ..
122    }) = ast.data
123    {
124        //let mut has_children_field = false;
125        for field in fields.named.iter() {
126            let field_name: syn::Ident = field.ident.as_ref().unwrap().clone();
127            field_ident_vec.push(&field.ident);
128            field_visibility_vec.push(&field.vis);
129            field_type_vec.push(&field.ty);
130            let mut attr_name = field_name.to_string();
131            if attr_name.eq("children") {
132                //has_children_field = true;
133                continue;
134            }
135            field_names.push(attr_name.clone());
136            //let name: String = field_name.to_string();
137            //println!("\n\n----->name: {}, \ntype: {:?}, \nattrs: {:?}", field_name, field.ty, field.attrs);
138            //println!("\n\n----->name: {}, \ntype: {:?}", field_name, field.ty);
139            let mut attrs: Vec<_> = field.attrs.iter().collect();
140
141            if !attrs.is_empty() {
142                let attr = attrs.remove(0);
143                if let Meta::List(list) = &attr.meta {
144                    let nested = list
145                        .parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
146                        .unwrap();
147                    for item in nested.iter() {
148                        if let Meta::NameValue(name_value) = item {
149                            let key = name_value.path.get_ident().unwrap().to_string();
150                            let value: String = match &name_value.value {
151                                syn::Expr::Lit(syn::ExprLit { lit, .. }) => match lit {
152                                    syn::Lit::Int(v) => v.to_string(),
153                                    syn::Lit::Str(v) => v.value(),
154                                    syn::Lit::Bool(v) => v.value().to_string(),
155                                    _ => "".to_string(),
156                                },
157                                _ => "".to_string(),
158                            };
159                            //println!("key: {}, value: {}", key, value);
160                            if key.eq("name") {
161                                attr_name = value;
162                            }
163                        }
164                    }
165                }
166            }
167
168            let field_type = match &field.ty {
169                syn::Type::Path(a) => match a.path.get_ident() {
170                    Some(a) => a.to_string(),
171                    None => {
172                        if !a.path.segments.is_empty() {
173                            a.path.segments[0].ident.to_string()
174                        } else {
175                            "".to_string()
176                        }
177                    }
178                },
179                syn::Type::Reference(a) => match a.elem.as_ref() {
180                    syn::Type::Path(a) => match a.path.get_ident() {
181                        Some(a) => a.to_string(),
182                        None => {
183                            if !a.path.segments.is_empty() {
184                                a.path.segments[0].ident.to_string()
185                            } else {
186                                "".to_string()
187                            }
188                        }
189                    },
190                    _ => "".to_string(),
191                },
192                _ => "".to_string(),
193            };
194
195            if field_type.eq("bool") {
196                let fmt_str = attr_name.to_string();
197                attrs_ts_vec.push(quote!(
198                    if self.#field_name{
199                        attrs.push(#fmt_str.to_string());
200                    }
201                ));
202            } else {
203                let fmt_str = format!("{attr_name}=\"{{}}\"");
204                let mut borrow = quote!();
205                if field_type.eq("String") {
206                    borrow = quote!(&);
207                }
208                if field_type.eq("Option") {
209                    attrs_ts_vec.push(quote!(
210                        match &self.#field_name{
211                            Some(value)=>{
212                                attrs.push(format!(#fmt_str, workflow_html::escape_attr(value)));
213                            }
214                            None=>{
215
216                            }
217                        }
218                    ));
219                } else {
220                    attrs_ts_vec.push(quote!(
221                        attrs.push(format!(#fmt_str, workflow_html::escape_attr(#borrow self.#field_name)));
222                    ));
223                }
224            }
225        }
226        //if !has_children_field{
227        //    children_field_ts = quote!(
228        //children:Option<R>
229        //    );
230        //}
231    }
232
233    //set_attributes(struct_name.to_string(), field_names);
234    let ts = quote!(
235        #[derive(Clone, Default)]
236        pub struct #struct_name #struct_params #where_clause {
237            #( #field_visibility_vec #field_ident_vec : #field_type_vec ),*,
238            //#children_field_ts
239        }
240
241        impl #impl_generics workflow_html::Render for #struct_name #type_generics #where_clause {
242            fn render(&self, w:&mut Vec<String>)->workflow_html::ElementResult<()>{
243                let attr = self.get_attributes();
244                let children = self.get_children();
245                w.push(format!(#format_str, attr, children));
246                Ok(())
247            }
248        }
249        impl #impl_generics workflow_html::ElementDefaults for #struct_name #type_generics #where_clause {
250            fn _get_attributes(&self)->String{
251                let mut attrs:Vec<String> = vec![];
252                #(#attrs_ts_vec)*
253                attrs.join(" ")
254            }
255            fn _get_children(&self)->String{
256                match &self.children{
257                    Some(children)=>{
258                        children.html()
259                    }
260                    None=>{
261                        "".to_string()
262                    }
263                }
264            }
265        }
266    );
267    //println!("\n===========> element({}) ts: <===========\n{}", struct_name, ts);
268    ts.into()
269}