use proc_macro::TokenStream;
use syn::{DeriveInput, parse_macro_input};
mod gen_inherit;
mod gen_merge;
mod gen_ranges;
mod gen_structs;
mod gen_validate;
mod parse;
#[proc_macro_derive(ThemeWidget, attributes(theme, theme_layer, theme_inherit))]
pub fn derive_theme_widget(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match derive_inner(input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
fn derive_inner(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let opt_name = &input.ident;
let layer = parse::parse_layer_attrs(&input.attrs)?;
let inherit_meta = parse::parse_inherit_attrs(&input.attrs)?;
let fields = match &input.data {
syn::Data::Struct(data) => parse::parse_fields(&data.fields)?,
_ => {
return Err(syn::Error::new_spanned(
&input.ident,
"ThemeWidget can only be derived on structs",
));
}
};
let doc_attrs: Vec<_> = input
.attrs
.iter()
.filter(|a| a.path().is_ident("doc"))
.cloned()
.collect();
let structs = gen_structs::gen_structs(opt_name, &fields, &layer, &doc_attrs);
let merge = gen_merge::gen_merge(opt_name, &fields);
let validate = gen_validate::gen_validate(opt_name, &fields, &layer);
let ranges = gen_ranges::gen_ranges(opt_name, &fields, &layer);
let inherit = gen_inherit::gen_inherit(opt_name, &fields, &layer);
let border_inherit = gen_inherit::gen_border_inherit(opt_name, &inherit_meta);
let font_inherit = gen_inherit::gen_font_inherit(opt_name, &inherit_meta);
let inventory = gen_inventory_submit(opt_name, &layer);
Ok(quote::quote! {
#structs
#merge
#validate
#ranges
#inherit
#border_inherit
#font_inherit
#inventory
})
}
fn gen_inventory_submit(
opt_name: &syn::Ident,
layer: &parse::LayerMeta,
) -> proc_macro2::TokenStream {
if layer.skip_inventory {
return proc_macro2::TokenStream::new();
}
let name_str = opt_name.to_string();
let widget_name = to_snake_case(name_str.strip_suffix("Theme").unwrap_or(&name_str));
quote::quote! {
inventory::submit!(crate::resolve::WidgetFieldInfo {
widget_name: #widget_name,
field_names: #opt_name::FIELD_NAMES,
});
}
}
fn to_snake_case(s: &str) -> String {
let mut result = String::with_capacity(s.len().saturating_add(4));
for (i, ch) in s.chars().enumerate() {
if ch.is_uppercase() && i > 0 {
result.push('_');
}
result.push(ch.to_ascii_lowercase());
}
result
}
#[proc_macro_derive(ThemeFields, attributes(theme_layer))]
pub fn derive_theme_fields(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match derive_fields_inner(input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
fn derive_fields_inner(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let struct_name = &input.ident;
let layer = parse::parse_layer_attrs(&input.attrs)?;
let field_names: Vec<String> = if let Some(ref explicit) = layer.explicit_fields {
explicit.clone()
} else {
let data = match &input.data {
syn::Data::Struct(s) => s,
_ => {
return Err(syn::Error::new_spanned(
&input.ident,
"ThemeFields can only be derived on structs",
));
}
};
parse::parse_fields(&data.fields)?
.iter()
.map(|f| {
f.serde_rename
.clone()
.unwrap_or_else(|| f.ident.to_string())
})
.collect()
};
let struct_name_str = struct_name.to_string();
let entries: Vec<proc_macro2::TokenStream> =
field_names.iter().map(|n| quote::quote! { #n, }).collect();
Ok(quote::quote! {
inventory::submit!(crate::resolve::FieldInfo {
struct_name: #struct_name_str,
field_names: &[#(#entries)*],
});
})
}