use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemStruct, Lit, Type}; fn is_bool_type(ty: &Type) -> bool { if let Type::Path(type_path) = ty { if let Some(segment) = type_path.path.segments.last() { return segment.ident == "bool"; } } false } #[proc_macro_attribute] pub fn annotations(attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemStruct); let name = input.ident.clone(); // Separate fields by type let mut bool_fields = Vec::new(); let mut custom_fields = Vec::new(); for field in input.fields.iter() { let field_name = field.ident.clone().unwrap(); let field_type = &field.ty; if is_bool_type(field_type) { bool_fields.push(field_name); } else { custom_fields.push((field_name, field_type)); } } let (custom_field_names, custom_field_types): (Vec<_>, Vec<_>) = custom_fields.into_iter().unzip(); // Parse comment literal let comm_lit = match parse_macro_input!(attr as Lit) { Lit::Str(lit_str) => lit_str.value(), _ => panic!("Expected a string literal"), }; TokenStream::from(quote! { #[derive(Default, Debug, Copy, Clone)] #input impl #name { /// Autogenerated by windmill-macros pub fn parse(code: &str) -> Self { let mut res = Self::default(); let mut lines = code.lines(); while let Some(line) = lines.next() { if line.trim().is_empty() { continue; } if !line.starts_with(#comm_lit) { break; } let line = line[#comm_lit.len()..].trim(); let (key, value) = line.split_once('=').unwrap_or((line, "")); match key { #( stringify!(#custom_field_names) => { if value.is_empty() { continue; } res.#custom_field_names = #custom_field_types::parse(value); } )* #( stringify!(#bool_fields) => { res.#bool_fields = true; } )* _ => { // Unknown key=value annotation continue; } } } res } } }) }