use proc_macro2::Ident;
use quote::ToTokens;
use syn::{
parse::{Parse, ParseStream},
token, AngleBracketedGenericArguments, Attribute, LitStr, Visibility,
};
struct Doc(pub String);
impl Parse for Doc {
fn parse(input: ParseStream) -> syn::Result<Self> {
input.parse::<token::Eq>()?;
let s = input.parse::<LitStr>()?.value();
Ok(Doc(s.trim_start().to_string()))
}
}
pub struct Top {
abga: Option<AngleBracketedGenericArguments>,
ident: Ident,
where_clause: Option<syn::WhereClause>,
fields: Vec<Ident>,
default_fields: Vec<Ident>,
expressions: Vec<syn::Expr>,
}
impl Parse for Top {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let _attrs = input.call(Attribute::parse_outer)?;
let _vis = input.parse::<Visibility>()?;
if !input.peek(token::Struct) {
return Err(input.error("Only structs are supported"));
}
input.parse::<token::Struct>()?;
let ident = input.parse::<Ident>()?;
let abga = if input.peek(token::Lt) {
Some(input.parse::<AngleBracketedGenericArguments>()?)
} else {
None
};
let where_clause = input.parse::<syn::WhereClause>().ok();
if !input.peek(token::Brace) {
return Err(input.error("Only structs with named fields are supported"));
}
let raw_fields = input.parse::<syn::FieldsNamed>()?;
let mut default_fields = vec![];
let mut expressions = vec![];
let fields = raw_fields
.named
.iter()
.filter_map(|f| {
let field = f.ident.as_ref()?;
for attr in &f.attrs {
if attr.path.is_ident("clearable") {
match attr.parse_meta().expect("invalid clearable attr") {
syn::Meta::List(list) => {
for meta in list.nested.iter() {
match meta {
syn::NestedMeta::Meta(syn::Meta::Path(p))
if p.is_ident("skip") =>
{
return None;
}
syn::NestedMeta::Meta(syn::Meta::Path(p))
if p.is_ident("default") =>
{
default_fields.push(field.clone());
return None;
}
syn::NestedMeta::Meta(syn::Meta::NameValue(
syn::MetaNameValue {
path,
eq_token: _,
lit: syn::Lit::Str(expr),
},
)) if path.is_ident("expr") => {
let expr = expr
.value()
.replace("{}", &format!("self.{}", field));
let expr: syn::Expr =
syn::parse_str(&expr).expect("Invalid expression");
expressions.push(expr);
return None;
}
syn::NestedMeta::Meta(syn::Meta::NameValue(
syn::MetaNameValue {
path,
eq_token: _,
lit: syn::Lit::Str(expr),
},
)) if path.is_ident("raw_expr") => {
let expr: syn::Expr = syn::parse_str(&expr.value())
.expect("Invalid expression");
expressions.push(expr);
return None;
}
syn::NestedMeta::Meta(meta)
if meta.path().is_ident("skip") =>
{
panic!(
"Invalid skip attr: {}\n\
Only clearable(skip) is valid",
meta.into_token_stream()
)
}
syn::NestedMeta::Meta(meta)
if meta.path().is_ident("default") =>
{
panic!(
"Invalid skip attr: {}\n\
Only clearable(default) is valid",
meta.into_token_stream()
)
}
meta => {
panic!(
"Invalid clearable attr: {}",
meta.into_token_stream()
)
}
}
}
}
meta => {
panic!(
"Expected a list of meta attrs for clearable: {}",
meta.into_token_stream()
)
}
}
}
}
Some(field.clone())
})
.collect::<Vec<_>>();
Ok(Self {
abga,
ident,
where_clause,
fields,
default_fields,
expressions,
})
}
}
impl ToTokens for Top {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::quote;
let Top {
abga,
ident,
where_clause,
fields,
default_fields,
expressions,
} = self;
quote! {
impl #abga ::kmacros::Clearable for #ident #abga #where_clause {
fn clear(&mut self) {
#(::kmacros::Clearable::clear(&mut self.#fields);)*
#(self.#default_fields = Default::default();)*
#(#expressions;)*
}
}
}
.to_tokens(tokens);
}
}