#![warn(missing_docs)]
#![warn(clippy::missing_docs_in_private_items)]
use std::any::TypeId;
use proc_macro2::{Ident, TokenStream};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::{format_ident, quote, ToTokens};
use syn::{
braced,
parse::{Error, Parse, ParseStream, Result},
parse_macro_input,
punctuated::Punctuated,
token, Attribute, Field, LitStr, Token, Visibility,
};
struct NamedFieldsStruct {
#[allow(dead_code)]
attrs: Vec<Attribute>,
#[allow(dead_code)]
vis: Visibility,
#[allow(dead_code)]
struct_token: Token![struct],
ident: Ident,
#[allow(dead_code)]
brace_token: token::Brace,
fields: Punctuated<Field, Token![,]>,
}
impl Parse for NamedFieldsStruct {
fn parse(input: ParseStream) -> Result<Self> {
let content;
#[allow(clippy::eval_order_dependence)]
Ok(Self {
attrs: input.call(Attribute::parse_outer)?,
vis: input.parse()?,
struct_token: input.parse()?,
ident: input.parse()?,
brace_token: braced!(content in input),
fields: content.parse_terminated(Field::parse_named)?,
})
}
}
struct AttrNameValue<V> {
name: Ident,
#[allow(dead_code)]
eq_token: Token![=],
value: V,
}
impl<V> Parse for AttrNameValue<V>
where
V: Parse + 'static,
{
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
name: input.parse()?,
eq_token: input.parse()?,
value: {
if TypeId::of::<V>() == TypeId::of::<LitStr>() {
input.parse()?
} else {
let x: LitStr = input.parse()?;
x.parse()?
}
},
})
}
}
fn gen_insert(field: &Field) -> TokenStream {
let field_name =
field.ident.as_ref().expect("guaranteed to exist by NamedFieldsStruct");
let field_name_str =
LitStr::new(&field_name.to_string(), field_name.span());
let fmt: TokenStream = field
.attrs
.iter()
.find_map(|attr| {
if !attr.path.is_ident("far") {
return None;
}
let fmt = attr
.parse_args::<AttrNameValue<LitStr>>()
.map(|attr| {
if attr.name != "fmt" {
Error::new_spanned(attr.name, "unknown argument")
.into_compile_error()
} else {
attr.value.to_token_stream()
}
})
.unwrap_or_else(Error::into_compile_error);
Some(fmt)
})
.unwrap_or_else(|| {
LitStr::new("{}", field_name.span()).into_token_stream()
});
quote! {
replace.insert(#field_name_str, format!(#fmt, self.#field_name));
}
}
fn gen_key(field: &Field) -> TokenStream {
let field_name =
field.ident.as_ref().expect("guaranteed to exist by NamedFieldsStruct");
let field_name_str =
LitStr::new(&field_name.to_string(), field_name.span());
quote! {
keys.push(#field_name_str);
}
}
#[allow(missing_docs)]
#[proc_macro_derive(Render, attributes(far))]
pub fn main(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let named_fields_struct = parse_macro_input!(input as NamedFieldsStruct);
let struct_name = named_fields_struct.ident;
let crate_token = match crate_name("far") {
Ok(FoundCrate::Itself) => quote! { crate },
Ok(FoundCrate::Name(name)) => {
let crate_name = format_ident!("{}", name);
quote! { ::#crate_name }
}
Err(e) => panic!("failed to determine crate name: {}", e),
};
let inserts: TokenStream =
named_fields_struct.fields.iter().flat_map(gen_insert).collect();
let keys: TokenStream =
named_fields_struct.fields.iter().flat_map(gen_key).collect();
let replace_impl = quote! {
impl #crate_token::Render for #struct_name {
fn render(&self) -> std::collections::HashMap<&'static str, String>
{
let mut replace = std::collections::HashMap::new();
#inserts
replace
}
fn keys() -> Box<dyn Iterator<Item = &'static str>> {
let mut keys = Vec::new();
#keys
Box::new(keys.into_iter())
}
}
};
let tokens = quote! {
#replace_impl
};
tokens.into()
}