use darling::{FromDeriveInput, FromField};
use quote::{quote, ToTokens};
use syn::parse_str;
#[derive(Clone, Debug, FromField)]
#[darling(forward_attrs(doc))]
struct StructField {
ident: Option<syn::Ident>,
ty: syn::Type,
attrs: Vec<syn::Attribute>,
}
#[derive(Debug, FromDeriveInput)]
#[darling(supports(struct_named))]
struct Cat2 {
ident: syn::Ident,
data: darling::ast::Data<(), StructField>,
generics: syn::Generics,
}
impl ToTokens for Cat2 {
fn to_tokens(&self, out: &mut proc_macro2::TokenStream) {
let Cat2 {
ref ident,
ref generics,
ref data,
} = *self;
dbg!(&ident);
let (imp, ty, wher) = generics.split_for_impl();
let fields = data.as_ref().take_struct().unwrap().fields;
let fields = fields
.into_iter()
.map(|field| {
let StructField {
ref ident,
ref ty,
ref attrs,
..
} = *field;
let doc = attrs
.iter()
.filter(|attr| attr.path.is_ident("doc"))
.map(|attr| attr.parse_meta().unwrap())
.map(|meta| match meta {
syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(lit_str),
..
}) => lit_str.value(),
_ => panic!("Expected doc attribute to be a string"),
})
.fold(String::new(), |mut acc, s| {
if !acc.is_empty() {
acc.push(' ');
}
acc.push_str(s.trim());
acc
});
let doc = if doc.is_empty() {
"undocumented".to_string()
} else {
doc
};
dbg!(ty);
quote! {
(stringify!(#ident).to_string(), "<" + stringigy!(#ty) + ">" + #doc).into()
}
})
.collect::<Vec<_>>();
out.extend(quote! {
impl #imp Cat2 for #ident #ty #wher {
fn describe(&self) -> Vec<Format> {
vec![
#(#fields),*
].into()
}
}
})
}
}
fn main() {
let input = r#"#[derive(Cat2)]
pub struct Foo {
/// Hello I'm bar
/// And I'm a multiline doc
#[my_trait(volume = "whisper")]
bar: bool,
/// Hello I'm baz
baz: i64,
foo: Vec<String>,
}"#;
let parsed = parse_str(input).unwrap();
let receiver = Cat2::from_derive_input(&parsed).unwrap();
let tokens = quote!(#receiver);
println!(
r#"
INPUT:
{}
PARSED AS:
{:?}
EMITS:
{}
"#,
input, receiver, tokens
);
}