pochoir-macros 0.12.2

Macro utilities for the pochoir template engine
Documentation
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{Data, Fields, Ident, Index};

fn impl_from_value(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;

    if let Data::Struct(data) = &ast.data {
        let returned = match &data.fields {
            Fields::Named(fields) => {
                let fields = fields
                .named
                .iter()
                .map(|f| {
                    let name = f.ident.as_ref().expect("named structure field must have an identifier");
                    let ty = &f.ty;

                    quote! { #name: <#ty>::from_value(val.remove(stringify!(#name)).ok_or(pochoir::lang::Error::MissingField(stringify!(#name).to_string()))?)? }
                }).collect::<Vec<_>>();

                quote! { {#(#fields),*} }
            }
            Fields::Unnamed(fields) => {
                let fields = fields
                    .unnamed
                    .iter()
                    .enumerate()
                    .map(|(i, f)| {
                        let name = format!("__field{i}");
                        let ty = &f.ty;

                        quote! { <#ty>::from_value(val.remove(#name).ok_or(pochoir::lang::Error::MissingField(#name.to_string()))?)? }
                    }).collect::<Vec<_>>();

                quote! { (#(#fields),*) }
            }
            Fields::Unit => {
                quote! {}
            }
        };

        quote! {
            impl pochoir::lang::FromValue for #name {
                fn from_value(val: Value) -> ::std::result::Result<Self, Box<dyn ::std::error::Error>> {
                    if let Value::Object(mut val) = val {
                        Ok(Self #returned)
                    } else {
                        Err(Box::new(pochoir::lang::Error::MismatchedTypes {
                            expected: "Object".to_string(),
                            found: val.type_name().to_string(),
                        }))
                    }
                }
            }
        }
        .into()
    } else if let Data::Enum(data) = &ast.data {
        let fields = data.variants.iter().map(|v| {
            let variant_name = &v.ident;

            match &v.fields {
                Fields::Named(fields) => {
                    let fields = fields
                    .named
                    .iter()
                    .map(|f| {
                        let name = f.ident.as_ref().expect("named structure field must have an identifier");
                        let ty = &f.ty;

                        quote! { #name: <#ty>::from_value(val.remove(stringify!(#name)).ok_or(pochoir::lang::Error::MissingField(stringify!(#name).to_string()))?)? }
                    }).collect::<Vec<_>>();

                    quote! { stringify!(#variant_name) => Self::#variant_name { #(#fields),* } }
                }
                Fields::Unnamed(fields) => {
                    let fields = fields
                        .unnamed
                        .iter()
                        .enumerate()
                        .map(|(i, f)| {
                            let name = format!("__field{i}");
                            let ty = &f.ty;

                            quote! { <#ty>::from_value(val.remove(#name).ok_or(pochoir::lang::Error::MissingField(#name.to_string()))?)? }
                        }).collect::<Vec<_>>();

                    quote! { stringify!(#variant_name) => Self::#variant_name ( #(#fields),* ) }
                }
                Fields::Unit => {
                    quote! { stringify!(#variant_name) => Self::#variant_name }
                }
            }
        }).collect::<Vec<_>>();

        quote! {
            impl pochoir::lang::FromValue for #name {
                fn from_value(val: Value) -> ::std::result::Result<Self, Box<dyn ::std::error::Error>> {
                    if let Value::Object(mut val) = val {
                        Ok(match val.get("__enum_variant")
                           .ok_or(pochoir::lang::Error::MissingField("__enum_variant".to_string()))?
                           .as_string().expect("__enum_variant should be a string")
                        {
                            #(#fields,)*
                            v => return Err(Box::new(pochoir::lang::Error::UnknownEnumVariant(v.to_string()))),
                        })
                    } else {
                        Err(Box::new(pochoir::lang::Error::MismatchedTypes {
                            expected: "Object".to_string(),
                            found: val.type_name().to_string(),
                        }))
                    }
                }
            }
        }
        .into()
    } else {
        panic!("FromValue works only on structures and enumerations")
    }
}

fn impl_into_value(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;

    if let Data::Struct(data) = &ast.data {
        let fields = match &data.fields {
            Fields::Named(fields) => fields
                .named
                .iter()
                .map(|f| {
                    let name = f
                        .ident
                        .as_ref()
                        .expect("named structure field must have an identifier");
                    quote! { stringify!(#name) => self.#name.into_value() }
                })
                .collect::<Vec<_>>(),
            Fields::Unnamed(fields) => (0..fields.unnamed.len())
                .map(|i| {
                    let name = Ident::new(&format!("__field{i}"), Span::call_site());
                    let i = Index::from(i);
                    quote! { stringify!(#name) => self.#i.into_value() }
                })
                .collect::<Vec<_>>(),
            Fields::Unit => {
                vec![]
            }
        };

        quote! {
            impl pochoir::lang::IntoValue for #name {
                fn into_value(self) -> pochoir::lang::Value {
                    pochoir::lang::object! {
                        #(#fields),*
                    }.into_value()
                }
            }
        }
        .into()
    } else if let Data::Enum(data) = &ast.data {
        let fields = data
            .variants
            .iter()
            .map(|v| {
                let variant_name = &v.ident;

                match &v.fields {
                    Fields::Named(fields) => {
                        let fields = fields.named.iter().map(|f| f.ident.as_ref().expect("named structure field must have an identifier")).collect::<Vec<_>>();

                        quote! { Self::#variant_name { #(#fields),* } => pochoir::lang::object! {"__enum_variant" => stringify!(#variant_name), #(stringify!(#fields) => #fields.into_value()),* }.into_value() }
                    },
                    Fields::Unnamed(fields) => {
                        let fields = (0..fields.unnamed.len()).map(|i| Ident::new(&format!("__field{i}"), Span::call_site())).collect::<Vec<_>>();

                        quote! { Self::#variant_name(#(#fields),*) => pochoir::lang::object! {"__enum_variant" => stringify!(#variant_name), #(stringify!(#fields) => #fields.into_value()),* }.into_value() }
                    },
                    Fields::Unit => {
                        quote! { Self::#variant_name => pochoir::lang::object! { "__enum_variant" => stringify!(#variant_name) }.into_value() }
                    }
                }
            });

        quote! {
            impl pochoir::lang::IntoValue for #name {
                fn into_value(self) -> pochoir::lang::Value {
                    match self {
                        #(#fields),*
                    }
                }
            }
        }
        .into()
    } else {
        panic!("IntoValue works only on structures and enumerations")
    }
}

/// Implements `pochoir_lang::FromValue` for structures and enumerations.
///
/// If **one field** uses an `Option`, it can automatically be converted from `Value::Null`
/// but if **all fields** of the structure are `Option`s, the structure **itself** can be
/// converted from a `Value::Null` (all the fields will be `None` in this case).
#[proc_macro_derive(FromValue)]
pub fn from_value_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_from_value(&ast)
}

/// Implements `pochoir_lang::IntoValue` for structures and enumerations.
#[proc_macro_derive(IntoValue)]
pub fn into_value_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_into_value(&ast)
}