Files
windmill/backend/windmill-macros/src/lib.rs
T
2025-12-16 19:07:19 +02:00

88 lines
2.7 KiB
Rust

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
}
}
})
}