#![recursion_limit = "128"]
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{Data, DeriveInput, Fields, Ident, Type};
use std::collections::BTreeMap;
const RENAME_ERROR_MSG: &str = "Must be `#[rename(name = 'VALUE')]`";
#[proc_macro_derive(FromMap)]
pub fn from_map(input: TokenStream) -> TokenStream {
let ast = syn::parse_macro_input!(input as DeriveInput);
let fields = match ast.data {
Data::Struct(st) => st.fields,
_ => panic!("Implementation must be a struct"),
};
let idents: Vec<&Ident> = fields
.iter()
.filter_map(|field| field.ident.as_ref())
.collect::<Vec<&Ident>>();
let keys: Vec<String> = idents
.clone()
.iter()
.map(|ident| ident.to_string())
.collect::<Vec<String>>();
let typecalls: Vec<Ident> = fields
.iter()
.map(|field| match field.ty.clone() {
Type::Path(typepath) => {
let typename: String = quote! {#typepath}.to_string();
Ident::new(&typename, Span::mixed_site())
}
_ => unimplemented!(),
})
.collect::<Vec<Ident>>();
let name: &Ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let tokens = quote! {
use structmap::value::Value;
use structmap::{StringMap, GenericMap};
impl #impl_generics FromMap for #name #ty_generics #where_clause {
fn from_stringmap(mut hashmap: StringMap) -> #name {
let mut settings = #name::default();
#(
match hashmap.entry(String::from(#keys)) {
::std::collections::btree_map::Entry::Occupied(entry) => {
let value = match entry.get().parse::<#typecalls>() {
Ok(val) => val,
_ => panic!("Cannot parse out map entry")
};
settings.#idents = value;
},
_ => ()
}
)*
settings
}
fn from_genericmap(mut hashmap: GenericMap) -> #name {
let mut settings = #name::default();
#(
match hashmap.entry(String::from(#keys)) {
::std::collections::btree_map::Entry::Occupied(entry) => {
let value = match entry.get().#typecalls() {
Some(val) => val,
None => panic!("Cannot parse out map entry")
};
settings.#idents = value;
},
_ => (),
}
)*
settings
}
}
};
TokenStream::from(tokens)
}
#[proc_macro_derive(ToMap, attributes(rename))]
pub fn to_map(input_struct: TokenStream) -> TokenStream {
let ast = syn::parse_macro_input!(input_struct as DeriveInput);
let fields = match ast.data {
Data::Struct(st) => st.fields,
_ => panic!("Implementation must be a struct"),
};
let rename_map = parse_rename_attrs(&fields);
let idents: Vec<&Ident> = fields
.iter()
.filter_map(|field| field.ident.as_ref())
.collect::<Vec<&Ident>>();
let keys: Vec<String> = idents
.clone()
.iter()
.map(|ident| ident.to_string())
.map(|name| match rename_map.contains_key(&name) {
true => rename_map.get(&name).unwrap().clone(),
false => name,
})
.collect::<Vec<String>>();
let name: &Ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let tokens = quote! {
impl #impl_generics ToMap for #name #ty_generics #where_clause {
fn to_stringmap(mut input_struct: #name) -> structmap::StringMap {
let mut map = structmap::StringMap::new();
#(
map.insert(#keys.to_string(), input_struct.#idents.to_string());
)*
map
}
fn to_genericmap(mut input_struct: #name) -> structmap::GenericMap {
let mut map = structmap::GenericMap::new();
#(
map.insert(#keys.to_string(), structmap::value::Value::new(input_struct.#idents));
)*
map
}
}
};
TokenStream::from(tokens)
}
fn parse_rename_attrs(fields: &Fields) -> BTreeMap<String, String> {
let mut rename: BTreeMap<String, String> = BTreeMap::new();
if let Fields::Named(_) = fields {
} else {
panic!("Must have named fields.");
}
for field in fields.iter() {
for attr in field.attrs.iter() {
let field_name = field.ident.as_ref().unwrap().to_string();
if rename.contains_key(&field_name) {
panic!("Cannot redefine field name multiple times.");
}
match attr.parse_meta() {
Ok(syn::Meta::List(lst)) => {
match lst.nested.first() {
Some(syn::NestedMeta::Meta(syn::Meta::NameValue(nm))) => {
let path = nm.path.get_ident().unwrap().to_string();
if path != "name" {
panic!("{}", RENAME_ERROR_MSG);
}
let lit = match &nm.lit {
syn::Lit::Str(val) => val.value(),
_ => {
panic!("{}", RENAME_ERROR_MSG);
}
};
rename.insert(field_name, lit);
}
_ => {
panic!("{}", RENAME_ERROR_MSG);
}
}
}
_ => {
panic!("{}", RENAME_ERROR_MSG);
}
}
}
}
rename
}