use proc_macro::{self, TokenStream};
use quote::quote;
use std::collections::HashMap;
use structype::{TypeMap, TypeMapVec};
use syn::{parse_macro_input, DataEnum, DataUnion, DeriveInput, FieldsNamed};
#[proc_macro_derive(StrucType, attributes(structype_meta))]
pub fn structmap(input: TokenStream) -> TokenStream {
let ast: DeriveInput = parse_macro_input!(input);
let name = &ast.ident;
let top_attr = &ast.attrs;
for attr in top_attr.iter() {
let meta = attr.parse_meta();
match meta {
_ => panic!("Cannot apply attribute outside a type. Applicable only inside the type on type fields."),
}
}
let description = match &ast.data {
syn::Data::Struct(s) => {
match &s.fields {
syn::Fields::Named(FieldsNamed { named, .. }) => {
let mut structype_map: TypeMapVec = Vec::new();
let iters = named.iter().map(|f| (&f.ident, &f.attrs));
for (if_ident, attrs) in iters {
if let Some(ident) = if_ident {
if !attrs.is_empty() {
let mut record = TypeMap {
field_name: ident.to_string(),
meta: HashMap::new(),
};
for attr in attrs.iter() {
let meta = attr.parse_meta().unwrap();
match meta {
syn::Meta::List(metalist) => {
let pairs = metalist
.nested
.into_pairs()
.map(|pair| pair.into_value());
for pair in pairs {
match pair {
syn::NestedMeta::Meta(meta) => match meta {
syn::Meta::Path(_) => {panic!(r#"invalid. Use the format structype_meta(label="foo", ord="10")"#)}
syn::Meta::List(_) => {panic!(r#"invalid. Use the format structype_meta(label="foo", ord="10")"#)}
syn::Meta::NameValue(meta_nameval) => {
let path = meta_nameval.path;
match meta_nameval.lit {
syn::Lit::Str(str_lit) => {
record.meta.insert(path.get_ident().unwrap().to_string(), str_lit.value());
}
_ => {panic!("Only string type is supported now")}
}
}
}
syn::NestedMeta::Lit(_) => {panic!("Lit is not applicable. Annotate as key-value")}
}
}
structype_map.push(record.clone());
}
_ => panic!(
r#"Not applicable. Present a list of key-value attributes like structype_meta(label="foo", ord="10")"#
),
}
}
} else {
let val = TypeMap {
field_name: ident.to_string(),
meta: HashMap::new(),
};
structype_map.push(val);
}
}
}
serde_json::to_string(&structype_map).unwrap()
}
syn::Fields::Unnamed(_) => panic!("Not applicable to Tuple structs"),
syn::Fields::Unit => panic!("Not applicable to Unit structs"),
}
}
syn::Data::Enum(DataEnum { variants, .. }) => {
let mut structype_map: TypeMapVec = Vec::new();
let iters = variants.iter().map(|f| (&f.ident, &f.attrs));
for (if_ident, attrs) in iters {
if !attrs.is_empty() {
let mut record = TypeMap {
field_name: if_ident.to_string(),
meta: HashMap::new(),
};
for attr in attrs.iter() {
let meta = attr.parse_meta().unwrap();
match meta {
syn::Meta::List(metalist) => {
let pairs =
metalist.nested.into_pairs().map(|pair| pair.into_value());
for pair in pairs {
match pair {
syn::NestedMeta::Meta(meta) => match meta {
syn::Meta::Path(_) => {
panic!(r#"invalid. Add as key="value#""#)
}
syn::Meta::List(_) => {
panic!(r#"invalid. Add as key="value#""#)
}
syn::Meta::NameValue(meta_nameval) => {
let path = meta_nameval.path;
match meta_nameval.lit {
syn::Lit::Str(str_lit) => {
record.meta.insert(
path.get_ident().unwrap().to_string(),
str_lit.value(),
);
}
_ => {
panic!("Only string type is supported now")
}
}
}
},
syn::NestedMeta::Lit(_) => {
panic!("Lit is not applicable. Annotate as key-value")
}
}
}
structype_map.push(record.clone());
}
_ => panic!(
r#"Not applicable. Present a list of key-value attributes like structype_meta(label="foo", ord="10")"#
),
}
}
} else {
let val = TypeMap {
field_name: if_ident.to_string(),
meta: HashMap::new(),
};
structype_map.push(val);
}
}
serde_json::to_string(&structype_map).unwrap()
}
syn::Data::Union(DataUnion {
fields: FieldsNamed { named, .. },
..
}) => {
let mut structype_map: TypeMapVec = Vec::new();
let iters = named.iter().map(|f| (&f.ident, &f.attrs));
for (if_ident, attrs) in iters {
if let Some(ident) = if_ident {
if !attrs.is_empty() {
let mut record = TypeMap {
field_name: ident.to_string(),
meta: HashMap::new(),
};
for attr in attrs.iter() {
let meta = attr.parse_meta().unwrap();
match meta {
syn::Meta::List(metalist) => {
let pairs =
metalist.nested.into_pairs().map(|pair| pair.into_value());
for pair in pairs {
match pair {
syn::NestedMeta::Meta(meta) => match meta {
syn::Meta::Path(_) => panic!(
r#"invalid. Use the format structype_meta(label="foo", ord="10")"#
),
syn::Meta::List(_) => panic!(
r#"invalid. Use the format structype_meta(label="foo", ord="10")"#
),
syn::Meta::NameValue(meta_nameval) => {
let path = meta_nameval.path;
match meta_nameval.lit {
syn::Lit::Str(str_lit) => {
record.meta.insert(
path.get_ident()
.unwrap()
.to_string(),
str_lit.value(),
);
}
_ => panic!(
"Only string type is supported now"
),
}
}
},
syn::NestedMeta::Lit(_) => panic!(
r#"Literal is not applicable. Annotate as key-value like structype_meta(label="foo#", ord="10")"#
),
}
}
structype_map.push(record.clone());
}
_ => panic!(
r#"Not applicable. Present a list of key-value attributes like structype_meta(label="foo", ord="10")"#
),
}
}
} else {
let val = TypeMap {
field_name: ident.to_string(),
meta: HashMap::new(),
};
structype_map.push(val);
}
}
}
serde_json::to_string(&structype_map).unwrap()
}
};
let output = quote! {
impl #name {
pub fn print_fields() {
println!("{}", #description);
}
pub fn as_string() -> String {
return #description.to_string()
}
}
};
output.into()
}