yew_html_attributes_macro_derive/
lib.rs1mod consts;
4mod has_attributes;
5mod use_attributes;
6mod utils;
7
8extern crate proc_macro;
9
10use consts::{ATTRIBUTE_TAG, ELEMENT_ARG, EXCLUDE_ARG, INVISIBLE_ARG, EXCLUDE_ARG_REGEX};
11use has_attributes::transform_struct;
12use quote::{quote};
13use syn::{parse_macro_input, AttributeArgs, DeriveInput, NestedMeta};
14use use_attributes::{generate_set_instructions, generate_unset_instructions};
15
16fn parse_has_attributes_args(args: Vec<NestedMeta>) -> (bool, Option<String>, Vec<String>) {
18 let mut excluded = vec![];
19 let mut visible = true;
20 let mut element = None;
21 for arg in args {
22 if let syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) = arg {
23 if nv.path.is_ident(EXCLUDE_ARG) {
24 if let syn::Lit::Str(lit) = &nv.lit {
25 let ex = lit.value();
26 let re = regex::Regex::new(EXCLUDE_ARG_REGEX).unwrap();
27 excluded = re.find_iter(&ex).map(|m|m.as_str().to_string()).collect();
28
29 } else {
30 panic!("exclude argument expects a string")
31 }
32 }
33 if nv.path.is_ident(INVISIBLE_ARG) {
34 if let syn::Lit::Bool(lit) = &nv.lit {
35 let lit = lit.value();
36 visible = !lit;
37 } else {
38 panic!("invisble argument expects a boolean")
39 }
40 }
41 if nv.path.is_ident(ELEMENT_ARG) {
42 if let syn::Lit::Str(lit) = &nv.lit {
43 let lit = lit.value();
44 element = Some(lit);
45 } else {
46 panic!("element argument expects a string")
47 }
48 }
49 }
50 }
51 (visible, element, excluded)
52}
53
54
55#[proc_macro_attribute]
70pub fn has_html_attributes(
71 attr: proc_macro::TokenStream,
72 item: proc_macro::TokenStream,
73) -> proc_macro::TokenStream {
74 let args = parse_macro_input!(attr as AttributeArgs);
76
77 let (visible, element, excluded) = parse_has_attributes_args(args);
78
79 let input: DeriveInput = syn::parse(item).unwrap();
80 let mut output = input;
81 match &mut output.data {
82 syn::Data::Struct(strct) => {
83 transform_struct(
84 strct,
85 visible,
86 element.as_deref(),
87 &excluded,
88 );
89 }
90 _ => panic!("use_attributes can only be used on structs"),
91 }
92
93 let mut has_properties = false;
95 let mut has_html_attributes = false;
96 for attr in &output.attrs {
97 if attr.path.is_ident("derive") {
98 if let syn::Meta::List(list) = &attr.parse_meta().unwrap() {
99 for nested in &list.nested {
100 if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = nested {
101 if path.is_ident("Properties") {
102 has_properties = true;
103 }
104 if path.is_ident("HasHtmlAttributes") {
105 has_html_attributes = true;
106 }
107 }
108 }
109 }
110 }
111 }
112
113 if !has_properties {
114 panic!("has_attributes can only be used on structs with a Properties derive");
115 }
116 if !has_html_attributes {
117 panic!("has_attributes can only be used on structs with a HasHtmlAttributes derive");
118 }
119 quote!(
120 #output
121 )
122 .into()
123}
124
125#[proc_macro_derive(HasHtmlAttributes, attributes(htmlattr, htmlelem))]
127pub fn derive_has_html_attributes(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
128 let input: DeriveInput = syn::parse(item).unwrap();
129 let name = &input.ident;
130
131 let mut attr_fields = vec![];
132
133 match input.data {
134 syn::Data::Struct(data) => {
135 if let syn::Fields::Named(fields) = data.fields {
136 for field in fields.named {
137 if let Some(attr) = field.attrs.first() {
138 if attr.path.is_ident(ATTRIBUTE_TAG) {
139 attr_fields.push(field.ident.unwrap());
140 }
141 }
142 }
143 } else {
144 panic!("HasHtmlAttributes can only be used on structs with named fields");
145 }
146 }
147 _ => panic!("HasHtmlAttributes can only be used on structs"),
148 }
149
150 let set_instructions = generate_set_instructions(&attr_fields);
151 let unset_instructions = generate_unset_instructions(&attr_fields);
152 quote!(
153 impl HasHtmlAttributes for #name {
154 fn set_attributes(&self, node: &web_sys::HtmlElement) -> Vec<wasm_bindgen::closure::Closure<dyn Fn(Event)>> {
155 let mut listeners: Vec<wasm_bindgen::closure::Closure<dyn Fn(Event)>> = Vec::new();
156 #(#set_instructions)*
157 listeners
158 }
159 fn unset_attributes(&self, node: &web_sys::HtmlElement) {
160 #(#unset_instructions)*
161 }
162 }
163 ).into()
164}
165
166
167
168#[proc_macro]
186pub fn use_attributes(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
187 let input = parse_macro_input!(item as AttributeArgs);
189 if input.len() != 2 {
190 panic!("use_attributes takes 2 arguments");
191 }
192 let node_ref = match &input[0] {
193 syn::NestedMeta::Meta(syn::Meta::Path(path)) => path.get_ident().unwrap(),
194 _ => panic!("use_attributes first argument must be a path"),
195 };
196 let props = match &input[1] {
197 syn::NestedMeta::Meta(syn::Meta::Path(path)) => path.get_ident().unwrap(),
198 _ => panic!("use_attributes second argument must be a path"),
199 };
200
201 quote!(
202 use_effect_with_deps(|(node_ref, props)|{
203 let node = node_ref.cast::<web_sys::HtmlElement>().unwrap();
204 let props = props.clone();
205 let mut listeners: Vec<wasm_bindgen::closure::Closure::<dyn Fn(Event)>> = props.set_attributes(&node);
206 move || {
207 let node = node;
208 props.unset_attributes(&node);
209 drop(listeners);
210 }
211 }
212 , (#node_ref.clone(), #props.clone()))
213 )
214 .into()
215}