floem-css-macros 0.2.0

Macros for floem-css
Documentation
use proc_macro2::Span;
use quote::ToTokens;
use smallvec::SmallVec;
use syn::{punctuated::Punctuated, token::Comma, Ident, LitStr, Variant};

const SIZE: usize = 64;

pub struct ParsedVariants<'a> {
    pub(crate) idents: SmallVec<[&'a Ident; SIZE]>,
    pub(crate) properties: SmallVec<[LitStr; SIZE]>,
    pub(crate) parsers: SmallVec<[proc_macro2::Ident; SIZE]>,
    pub(crate) style_classes: SmallVec<[Ident; SIZE]>,
}

impl<'a> ParsedVariants<'a> {
    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            idents: SmallVec::with_capacity(capacity),
            properties: SmallVec::with_capacity(capacity),
            parsers: SmallVec::with_capacity(capacity),
            style_classes: SmallVec::with_capacity(capacity),
        }
    }

    pub fn add(&mut self, var: ParsedVariant<'a>) {
        self.idents.push(var.ident);
        self.properties.push(var.property);
        self.parsers.push(var.parser);
        self.style_classes.push(var.style_class);
    }
}

pub struct ParsedVariant<'a> {
    ident: &'a Ident,
    property: LitStr,
    parser: proc_macro2::Ident,
    style_class: Ident,
}

impl<'a> From<&'a Punctuated<Variant, Comma>> for ParsedVariants<'a> {
    fn from(value: &'a Punctuated<Variant, Comma>) -> Self {
        let mut variants = ParsedVariants::with_capacity(value.len());
        for variant in value.iter().map(parse_enum_variant) {
            variants.add(variant);
        }
        variants
    }
}

pub fn parse_enum_variant(variant: &Variant) -> ParsedVariant<'_> {
    let property = find_property(variant);
    let parser = find_parser(variant);
    let style_class = find_style_class(variant);
    ParsedVariant {
        ident: &variant.ident,
        property,
        parser,
        style_class,
    }
}

fn find_style_class(variant: &Variant) -> Ident {
    let style_class_attr = variant
        .attrs
        .iter()
        .find(|a| a.path().is_ident("style_class"))
        .unwrap_or_else(|| panic!("Missing style_class attribute for {}", variant.ident));
    style_class_attr.parse_args::<Ident>().unwrap_or_else(|e| {
        panic!(
            "style_class attribute must be ident: {}\n{e}",
            style_class_attr.to_token_stream()
        )
    })
}

fn find_property(variant: &Variant) -> LitStr {
    let property_attr = variant
        .attrs
        .iter()
        .find(|a| a.path().is_ident("property"))
        .unwrap_or_else(|| {
            panic!("Missing property attribute for {}", variant.ident);
        });
    property_attr.parse_args::<LitStr>().unwrap_or_else(|e| {
        panic!(
            "property attribute must be string literal: {}\n{e}",
            property_attr.to_token_stream()
        )
    })
}

fn find_parser(variant: &Variant) -> Ident {
    variant
        .attrs
        .iter()
        .find(|a| a.path().is_ident("parser"))
        .and_then(|a| {
            a.parse_args::<LitStr>()
                .ok()
                .map(|lit| Ident::new(&lit.value(), Span::call_site()))
        })
        .unwrap_or_else(|| panic!("Missing parser fn for {}", variant.ident))
}