btmgmt-packet-macros 0.2.2

btmgmt-packet helper macros.
Documentation
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DataEnum, DataStruct, DeriveInput, Fields, Ident};

fn derive_unit(item: &DeriveInput, _: &DataStruct) -> syn::Result<TokenStream> {
    let ident = &item.ident;

    let code = quote! {
        impl ::btmgmt_packet_helper::pack::Pack for #ident {
            fn pack<W>(&self, _: &mut W) -> ::btmgmt_packet_helper::pack::Result<()> where W: ::std::io::Write {
                Ok(())
            }
        }
    };
    Ok(code)
}

fn derive_tuple(item: &DeriveInput, data: &DataStruct) -> syn::Result<TokenStream> {
    let ident = &item.ident;
    let fields = (0..data.fields.len())
        .map(proc_macro2::Literal::usize_unsuffixed)
        .collect::<Vec<_>>();
    let (impl_generics, type_generics, where_clause) = item.generics.split_for_impl();

    let code = quote! {
        impl #impl_generics ::btmgmt_packet_helper::pack::Pack for #ident #type_generics #where_clause {
            fn pack<W>(&self, write: &mut W) -> ::btmgmt_packet_helper::pack::Result<()> where W: ::std::io::Write {
                #( self.#fields.pack(write)?; )*
                Ok(())
            }
        }
    };
    Ok(code)
}

fn derive_standard(item: &DeriveInput, data: &DataStruct) -> syn::Result<TokenStream> {
    let ident = &item.ident;
    let fields = data
        .fields
        .iter()
        .map(|f| f.ident.as_ref().unwrap())
        .collect::<Vec<_>>();
    let (impl_generics, type_generics, where_clause) = item.generics.split_for_impl();

    let code = quote! {
        impl #impl_generics ::btmgmt_packet_helper::pack::Pack for #ident #type_generics #where_clause {
            fn pack<W>(&self, write: &mut W) -> ::btmgmt_packet_helper::pack::Result<()> where W: ::std::io::Write {
                #( self.#fields.pack(write)?; )*
                Ok(())
            }
        }
    };
    Ok(code)
}

fn derive_enum(item: &DeriveInput, data: &DataEnum) -> syn::Result<TokenStream> {
    let mut ty = None;
    for attr in &item.attrs {
        if attr.path.is_ident("pack") {
            ty = Some(attr.parse_args::<Ident>()?);
        }
    }
    let ty = if let Some(ty) = ty {
        ty
    } else {
        return Err(syn::Error::new_spanned(item, "no `pack` specified."));
    };

    let ident = &item.ident;
    let fields = data
        .variants
        .iter()
        .map(|v| {
            if let Fields::Unit = v.fields {
                Ok(&v.ident)
            } else {
                Err(syn::Error::new_spanned(v, "supports unit variant only."))
            }
        })
        .collect::<syn::Result<Vec<_>>>()?;

    let code = quote! {
        impl ::btmgmt_packet_helper::pack::Pack for #ident {
            fn pack<W>(&self, write: &mut W) -> ::btmgmt_packet_helper::pack::Result<()> where W: ::std::io::Write {
                let v = match self {
                    #( Self::#fields => Self::#fields as #ty, )*
                };
                v.pack(write)
            }
        }
    };
    Ok(code)
}

fn derive(input: TokenStream) -> syn::Result<TokenStream> {
    let input = syn::parse2::<DeriveInput>(input)?;
    match &input.data {
        Data::Struct(
            data
            @ DataStruct {
                fields: Fields::Unit,
                ..
            },
        ) => derive_unit(&input, data),
        Data::Struct(
            data
            @
            DataStruct {
                fields: Fields::Unnamed(..),
                ..
            },
        ) => derive_tuple(&input, data),
        Data::Struct(
            data
            @
            DataStruct {
                fields: Fields::Named(..),
                ..
            },
        ) => derive_standard(&input, data),
        Data::Enum(data) => derive_enum(&input, data),
        _ => todo!(),
    }
}

pub fn pack(input: TokenStream) -> TokenStream {
    match derive(input) {
        Ok(tokens) => tokens,
        Err(err) => err.to_compile_error(),
    }
}