thalo-macros 0.5.0

Macros for crates.io/thalo
Documentation
use std::str;

use better_bae::{FromAttributes, TryFromAttributes};
use heck::{
    ToKebabCase, ToLowerCamelCase, ToPascalCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase,
};
use proc_macro2::TokenStream;
use quote::quote;

use crate::traits::DeriveMacro;

const UNKNOWN_RENAME_ALL_MESSAGE: &str = r#"Unknown rename type. Expected one of: "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE", "kebab-case", "SCREAMING-KEBAB-CASE""#;

pub(crate) struct EventType {
    ident: syn::Ident,
    variant_types: Vec<(syn::Ident, String)>,
}

#[derive(Default, FromAttributes)]
#[bae("thalo")]
struct Attrs {
    rename_all: Option<syn::LitStr>,
}

#[derive(Default, FromAttributes)]
#[bae("thalo")]
struct VariantAttrs {
    rename: Option<syn::LitStr>,
}

#[derive(Clone, Copy)]
#[allow(clippy::enum_variant_names)]
enum RenameAll {
    LowerCase,
    UpperCase,
    PascalCase,
    CamelCase,
    SnakeCase,
    ScreamingSnakeCase,
    KebabCase,
    ScreamingKebabCase,
}

impl str::FromStr for RenameAll {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        use RenameAll::*;

        match s {
            "lowercase" => Ok(LowerCase),
            "UPPERCASE" => Ok(UpperCase),
            "PascalCase" => Ok(PascalCase),
            "camelCase" => Ok(CamelCase),
            "snake_case" => Ok(SnakeCase),
            "SCREAMING_SNAKE_CASE" => Ok(ScreamingSnakeCase),
            "kebab-case" => Ok(KebabCase),
            "SCREAMING-KEBAB-CASE" => Ok(ScreamingKebabCase),
            _ => Err(()),
        }
    }
}

impl DeriveMacro for EventType {
    fn new(input: syn::DeriveInput) -> syn::Result<Self> {
        let attrs = Attrs::try_from_attributes(&input.attrs)?.unwrap_or_default();
        let rename_all: Option<RenameAll> = attrs
            .rename_all
            .as_ref()
            .map(|rename_all| rename_all.value().parse())
            .transpose()
            .map_err(|_| {
                syn::Error::new(attrs.rename_all.unwrap().span(), UNKNOWN_RENAME_ALL_MESSAGE)
            })?;
        let ident = input.ident;

        let variants = match input.data {
            syn::Data::Enum(data) => data.variants,
            _ => {
                return Err(syn::Error::new(
                    ident.span(),
                    "EventType can only be applied to enums",
                ))
            }
        };

        let variant_types: Vec<_> = variants
            .into_iter()
            .map(|variant| {
                let event_type = if let Some(rename) =
                    VariantAttrs::try_from_attributes(&variant.attrs)?
                        .unwrap_or_default()
                        .rename
                {
                    rename.value()
                } else if let Some(rename_all) = &rename_all {
                    match rename_all {
                        RenameAll::LowerCase => variant.ident.to_string().to_lowercase(),
                        RenameAll::UpperCase => variant.ident.to_string().to_uppercase(),
                        RenameAll::PascalCase => variant.ident.to_string().to_pascal_case(),
                        RenameAll::CamelCase => variant.ident.to_string().to_lower_camel_case(),
                        RenameAll::SnakeCase => variant.ident.to_string().to_snake_case(),
                        RenameAll::ScreamingSnakeCase => {
                            variant.ident.to_string().to_shouty_snake_case()
                        }
                        RenameAll::KebabCase => variant.ident.to_string().to_kebab_case(),
                        RenameAll::ScreamingKebabCase => {
                            variant.ident.to_string().to_shouty_kebab_case()
                        }
                    }
                } else {
                    variant.ident.to_string()
                };

                Result::<_, syn::Error>::Ok((variant.ident, event_type))
            })
            .collect::<Result<_, _>>()?;

        Ok(EventType {
            ident,
            variant_types,
        })
    }

    fn expand(self) -> syn::Result<proc_macro2::TokenStream> {
        let expanded_impl_event_type = self.expand_impl_event_type();

        Ok(expanded_impl_event_type)
    }
}

impl EventType {
    fn expand_impl_event_type(&self) -> TokenStream {
        let Self {
            ident,
            variant_types,
        } = self;

        let expanded_variants = variant_types.iter().map(
            |(variant_ident, event_type)| quote!(#ident::#variant_ident { .. } => #event_type),
        );

        quote! {
            #[automatically_derived]
            impl thalo::event::EventType for #ident {
                fn event_type(&self) -> &'static str {
                    match self {
                        #( #expanded_variants ),*
                    }
                }
            }
        }
    }
}