extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields, Type, Ident};
#[proc_macro_derive(Orz, attributes(name, skip, debug, to_string_with))]
pub fn derive_field_values(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;
let fields = match &input.data {
Data::Struct(data) => match &data.fields {
syn::Fields::Named(named) => &named.named,
_ => {
return syn::Error::new_spanned(
&input,
"FieldValues 只支持具名字段结构体",
)
.to_compile_error()
.into();
}
},
_ => {
return syn::Error::new_spanned(&input, "FieldValues 只支持结构体")
.to_compile_error()
.into();
}
};
let mut field_names_lit = Vec::new();
let mut value_exprs = Vec::new();
let mut ref_exprs = Vec::new();
let mut all_asref_str = true;
for field in fields {
let field_name = &field.ident;
let field_type = &field.ty;
let mut skip = false;
let mut custom_func = None;
let mut display_name = None;
let mut use_debug = false;
for attr in &field.attrs {
if attr.path().is_ident("name") {
if let Ok(lit_str) = attr.parse_args::<syn::LitStr>() {
display_name = Some(lit_str.value());
} else {
return syn::Error::new_spanned(
attr,
"name attribute must be in the format #[name(\"value\")]"
)
.to_compile_error()
.into();
}
} else if attr.path().is_ident("skip") {
skip = true;
} else if attr.path().is_ident("debug") {
use_debug = true;
} else if attr.path().is_ident("to_string_with") {
if let Ok(lit_str) = attr.parse_args::<syn::LitStr>() {
custom_func = Some(lit_str.value());
} else {
return syn::Error::new_spanned(
attr,
"to_string_with attribute must be in the format #[to_string_with(\"function\")]"
)
.to_compile_error()
.into();
}
}
}
if skip {
continue;
}
let field_display_name = if let Some(name) = display_name {
name
} else {
field_name.as_ref().unwrap().to_string()
};
field_names_lit.push(field_display_name);
let value_expr = if let Some(ref func_name) = custom_func {
let func_ident: Ident = syn::parse_str(func_name).unwrap();
quote! { #func_ident(&self.#field_name) }
} else if use_debug {
quote! { format!("{:?}", &self.#field_name) }
} else {
quote! { self.#field_name.to_string() }
};
value_exprs.push(value_expr);
if !is_type_asref_str(field_type) {
all_asref_str = false;
} else {
ref_exprs.push(quote! { self.#field_name.as_ref() });
}
}
let field_names_tokens: Vec<_> = field_names_lit.iter()
.map(|name| {
let name_lit = syn::LitStr::new(name, proc_macro2::Span::call_site());
quote! { #name_lit }
})
.collect();
let field_count = field_names_lit.len();
let expanded = quote! {
impl #struct_name {
pub fn field_names() -> Vec<&'static str> {
vec![#(#field_names_tokens),*]
}
pub fn field_values(&self) -> Vec<String> {
vec![#(#value_exprs),*]
}
pub fn field_info(&self) -> Vec<(&'static str, String)> {
let names = Self::field_names();
let values = self.field_values();
names.into_iter().zip(values.into_iter()).collect()
}
pub fn field_count() -> usize {
#field_count
}
}
};
let expanded = if all_asref_str && !ref_exprs.is_empty() {
quote! {
#expanded
impl #struct_name {
pub fn field_values_ref(&self) -> Vec<&str> {
vec![#(#ref_exprs),*]
}
}
}
} else {
expanded
};
TokenStream::from(expanded)
}
fn is_type_asref_str(ty: &Type) -> bool {
match ty {
syn::Type::Reference(t) => {
if let syn::Type::Path(p) = &*t.elem {
if p.path.segments.last().map(|s| s.ident == "str").unwrap_or(false) {
return true;
}
}
}
syn::Type::Path(p) => {
if p.path.segments.last().map(|s| s.ident == "String").unwrap_or(false) {
return true;
}
}
_ => {}
}
false
}