#![cfg_attr(docsrs, feature(doc_cfg))]
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::{ItemStruct, parse_macro_input};
#[proc_macro_derive(
CharmingSetters,
attributes(charming_skip_setter, charming_type, charming_set_vec)
)]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as ItemStruct);
let struct_ident = input.ident;
let mut fields_init_values = Vec::with_capacity(input.fields.len());
let mut fields_setter = Vec::with_capacity(input.fields.len());
for field in input.fields {
let mut generate_setter = true;
let field_ident = field.ident.unwrap();
let field_string_shorthand = field_ident
.to_string()
.chars()
.next()
.unwrap()
.to_string()
.to_uppercase();
let field_ident_shorthand = Ident::new(&field_string_shorthand, Span::call_site());
match field.ty {
syn::Type::Path(type_path) => {
let type_wrapper = &type_path.path.segments.first().unwrap().ident.to_string();
for attribute in field.attrs {
match attribute.meta {
syn::Meta::Path(path) => {
if let Some(segment) = path.segments.last() {
match segment.ident.to_string().as_str() {
"charming_skip_setter" => {
if type_wrapper == "Option" {
fields_init_values.push(quote! { #field_ident: None });
} else if type_wrapper == "Vec" {
fields_init_values
.push(quote! { #field_ident: Vec::new() });
} else {
fields_init_values
.push(quote! { #field_ident: Default::default() });
}
generate_setter = false;
}
"charming_set_vec" => {
let field_string_lowercase_shorthand =
field_string_shorthand.to_lowercase();
let field_ident_lowercase_shorthand = Ident::new(
&field_string_lowercase_shorthand,
Span::call_site(),
);
let field_generic_type = &type_path
.path
.segments
.iter()
.next()
.unwrap()
.arguments;
fields_init_values
.push(quote! { #field_ident: Vec::new() });
fields_setter.push(quote! {
pub fn #field_ident<#field_ident_shorthand: Into #field_generic_type>(mut self, #field_ident: Vec<#field_ident_shorthand>) -> Self {
self.#field_ident = #field_ident.into_iter().map(|#field_ident_lowercase_shorthand| #field_ident_lowercase_shorthand.into()).collect();
self
}
});
generate_setter = false;
}
_ => {}
}
}
}
syn::Meta::List(_meta_list) => (),
syn::Meta::NameValue(meta_name_value) => {
if let Some(segment) = meta_name_value.path.segments.last() {
if let "charming_type" = segment.ident.to_string().as_str() {
match &meta_name_value.value {
syn::Expr::Lit(expr_lit) => {
if let syn::Lit::Str(lit_str) = &expr_lit.lit {
let value = lit_str.value();
fields_init_values.push(
quote! { #field_ident: #value.to_string() },
)
}
}
_ => {
panic!("charming_type needs a string literal")
}
}
}
}
}
}
}
if type_wrapper == "Option" && generate_setter {
let field_generic_type =
&type_path.path.segments.iter().next().unwrap().arguments;
fields_init_values.push(quote! { #field_ident: None });
fields_setter.push(quote! {
pub fn #field_ident<#field_ident_shorthand: Into #field_generic_type >(mut self, #field_ident: #field_ident_shorthand) -> Self {self.#field_ident = Some(#field_ident.into());
self
}
});
} else if type_wrapper == "Vec" && generate_setter {
let field_generic_type =
&type_path.path.segments.iter().next().unwrap().arguments;
fields_init_values.push(quote! { #field_ident: Vec::new() });
fields_setter.push(quote! {
pub fn #field_ident<#field_ident_shorthand: Into #field_generic_type >(mut self, #field_ident: #field_ident_shorthand) -> Self {self.#field_ident.push(#field_ident.into());
self}
});
} else if type_wrapper == "DataFrame" && generate_setter {
fields_init_values.push(quote! { #field_ident: DataFrame::default() });
fields_setter.push(quote! {
pub fn data<D: Into<DataPoint>>(mut self, data: Vec<D>) -> Self {
self.data = data.into_iter().map(|d| d.into()).collect();
self
}
});
};
}
_ => todo!(),
}
}
quote! {
impl #struct_ident {
pub fn new() -> #struct_ident {
#struct_ident {
#(#fields_init_values),*
}
}
#(#fields_setter)*
}
impl Default for #struct_ident {
fn default() -> Self {
Self::new()
}
}
}
.into()
}