a3s-boot-macros 0.1.2

Attribute macros for a3s-boot
Documentation
use proc_macro2::TokenStream;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{parenthesized, Attribute, Path, Result, Token};

pub(crate) fn expand_apply_decorators_attrs(
    attrs: &[Attribute],
) -> (Vec<Attribute>, Vec<syn::Error>) {
    let mut expanded = Vec::new();
    let mut errors = Vec::new();

    for attr in attrs {
        if !is_apply_decorators_attribute(attr) {
            expanded.push(attr.clone());
            continue;
        }

        match attr.parse_args::<ApplyDecoratorsArgs>() {
            Ok(args) => expanded.extend(args.into_attributes()),
            Err(error) => errors.push(error),
        }
    }

    (expanded, errors)
}

fn is_apply_decorators_attribute(attr: &Attribute) -> bool {
    attr.path()
        .segments
        .last()
        .is_some_and(|segment| segment.ident == "apply_decorators")
}

struct ApplyDecoratorsArgs {
    decorators: Vec<AppliedDecorator>,
}

impl ApplyDecoratorsArgs {
    fn into_attributes(self) -> Vec<Attribute> {
        self.decorators
            .into_iter()
            .map(AppliedDecorator::into_attribute)
            .collect()
    }
}

impl Parse for ApplyDecoratorsArgs {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        let decorators = Punctuated::<AppliedDecorator, Token![,]>::parse_terminated(input)?
            .into_iter()
            .collect::<Vec<_>>();

        if decorators.is_empty() {
            return Err(input.error("expected at least one decorator"));
        }

        Ok(Self { decorators })
    }
}

struct AppliedDecorator {
    path: Path,
    args: Option<TokenStream>,
}

impl AppliedDecorator {
    fn into_attribute(self) -> Attribute {
        let path = self.path;
        match self.args {
            Some(args) => syn::parse_quote!(#[#path(#args)]),
            None => syn::parse_quote!(#[#path]),
        }
    }
}

impl Parse for AppliedDecorator {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        let path = input.parse::<Path>()?;
        let args = if input.peek(syn::token::Paren) {
            let content;
            parenthesized!(content in input);
            Some(content.parse::<TokenStream>()?)
        } else {
            None
        };

        Ok(Self { path, args })
    }
}