use proc_macro2::TokenStream;
use quote::quote;
use syn::{DeriveInput, Data, Fields, Error};
pub fn derive_styleable_impl(input: DeriveInput) -> Result<TokenStream, Error> {
let name = &input.ident;
let base_field = find_base_field(&input)?;
let base_ident = base_field.ident.as_ref()
.ok_or_else(|| Error::new_spanned(&input, "Base field must be named"))?;
let expanded = quote! {
impl #name {
pub fn class(mut self, class: &str) -> Self {
self.#base_ident.classes.add(class);
self
}
pub fn classes(mut self, classes: &[&str]) -> Self {
for class in classes {
self.#base_ident.classes.add(*class);
}
self
}
pub fn id(mut self, id: &str) -> Self {
self.#base_ident.element_id = Some(id.to_string());
self
}
pub fn remove_class(&mut self, class: &str) {
self.#base_ident.classes.remove(class);
}
pub fn toggle_class(&mut self, class: &str) {
self.#base_ident.classes.toggle(class);
}
pub fn has_class(&self, class: &str) -> bool {
self.#base_ident.classes.contains(class)
}
pub fn get_classes(&self) -> &crate::css::ClassList {
&self.#base_ident.classes
}
pub fn get_id(&self) -> Option<&str> {
self.#base_ident.element_id.as_deref()
}
}
};
Ok(expanded)
}
fn find_base_field(input: &DeriveInput) -> Result<&syn::Field, Error> {
let fields = match &input.data {
Data::Struct(data) => &data.fields,
_ => return Err(Error::new_spanned(input, "Styleable can only be derived for structs")),
};
let named = match fields {
Fields::Named(named) => named,
_ => return Err(Error::new_spanned(input, "Styleable requires named fields")),
};
for field in &named.named {
if field.attrs.iter().any(|a| a.path().is_ident("base")) {
return Ok(field);
}
}
for field in &named.named {
if let Some(ident) = &field.ident {
if ident == "base" {
return Ok(field);
}
}
}
Err(Error::new_spanned(
input,
"Styleable requires a field marked with #[base] or named 'base'"
))
}