Skip to main content

insta_fun_meta_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro_crate::{FoundCrate, crate_name};
3use quote::{format_ident, quote};
4use syn::parse::{Parse, ParseStream};
5use syn::{Expr, Ident, Result, Token, punctuated::Punctuated};
6
7struct MetadataField {
8    name: Ident,
9    value: Expr,
10}
11
12impl Parse for MetadataField {
13    fn parse(input: ParseStream<'_>) -> Result<Self> {
14        let name: Ident = input.parse()?;
15        input.parse::<Token![:]>()?;
16        let value: Expr = input.parse()?;
17        Ok(Self { name, value })
18    }
19}
20
21struct MetadataInput {
22    fields: Punctuated<MetadataField, Token![,]>,
23}
24
25impl Parse for MetadataInput {
26    fn parse(input: ParseStream<'_>) -> Result<Self> {
27        Ok(Self {
28            fields: Punctuated::<MetadataField, Token![,]>::parse_terminated(input)?,
29        })
30    }
31}
32
33fn resolve_insta_fun_root() -> proc_macro2::TokenStream {
34    let found = crate_name("insta-fun").or_else(|_| crate_name("insta_fun"));
35
36    match found {
37        Ok(FoundCrate::Itself) => quote!(::insta_fun),
38        Ok(FoundCrate::Name(name)) => {
39            let ident = format_ident!("{}", name.replace('-', "_"));
40            quote!(::#ident)
41        }
42        Err(_) => quote!(::insta_fun),
43    }
44}
45
46#[proc_macro]
47pub fn insta_fun_meta(input: TokenStream) -> TokenStream {
48    let input = syn::parse_macro_input!(input as MetadataInput);
49    let crate_root = resolve_insta_fun_root();
50
51    let expanded_fields = input.fields.iter().map(|field| {
52        let name = field.name.to_string();
53        let value = &field.value;
54        quote! {
55            #crate_root::meta::MetaField::new(#name, #value)
56        }
57    });
58
59    quote! {
60        #crate_root::meta::SnapshotMetadata::new(vec![
61            #(#expanded_fields),*
62        ])
63    }
64    .into()
65}