bauer-macros 0.5.0

Macro implementation for the `bauer` crate. This should only be accessed through the `bauer` crate.
Documentation
use quote::format_ident;
use strum::{AsRefStr, IntoStaticStr, VariantArray};
use syn::{Ident, LitStr, Token, ext::IdentExt, parse::ParseStream};

use crate::util::parse::{parethesised_or_braced, parse_attributes, parse_docs};

macro_rules! bail {
    ($span: expr => $message: literal $(, $args: expr)*$(,)?) => {
        return Err(syn::Error::new(
            $span,
            format!($message, $($args),*),
        ))
    }
}

#[derive(Clone, Copy, VariantArray, IntoStaticStr, AsRefStr, Debug, PartialEq, Eq)]
#[strum(serialize_all = "snake_case")]
enum Attribute {
    #[allow(clippy::enum_variant_names)]
    Attributes,
    Doc,
    Rename,
    Force,
}

impl Attribute {
    fn matches(self, ident: &Ident) -> bool {
        if ident == self.as_ref() {
            return true;
        }

        match self {
            Self::Attributes => ident == "attribute",
            Self::Doc => ident == "docs",
            _ => false,
        }
    }

    fn parse(ident: &Ident) -> syn::Result<Self> {
        Self::VARIANTS
            .iter()
            .copied()
            .find(|e| e.matches(ident))
            .ok_or_else(|| {
                syn::Error::new(
                    ident.span(),
                    format!(
                        "Unknown attribute '{}'.  Valid attribute are: '{}'",
                        ident,
                        Self::VARIANTS
                            .iter()
                            .map(<&str>::from)
                            .collect::<Vec<_>>()
                            .join(", ")
                    ),
                )
            })
    }
}

#[derive(Debug, Clone, Default)]
pub struct ErrorAttr {
    pub attributes: Vec<syn::Attribute>,
    pub rename: Option<Ident>,
    pub force: bool,
}

impl ErrorAttr {
    pub fn name(&self, struct_name: &Ident) -> Ident {
        if let Some(rename) = &self.rename {
            rename.clone()
        } else {
            format_ident!("{}BuildError", struct_name)
        }
    }

    pub fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut out = Self::default();

        while input.peek(Ident::peek_any) {
            let ident = Ident::parse_any(input)?;
            match Attribute::parse(&ident)? {
                Attribute::Attributes => {
                    let attrs = parethesised_or_braced(input)?;

                    if !attrs.is_empty() {
                        parse_attributes(&attrs, &mut out.attributes)?;
                    }
                }
                Attribute::Doc => {
                    let attrs = parethesised_or_braced(input)?;

                    if !attrs.is_empty() {
                        parse_docs(&attrs, ident.span(), &mut out.attributes)?;
                    }
                }
                Attribute::Rename => {
                    if out.rename.is_some() {
                        bail!(ident.span() => "`rename` may only be used once");
                    }

                    let _: Token![=] = input.parse()?;
                    let s: LitStr = input.parse()?;

                    out.rename = Some(s.parse()?);
                }
                Attribute::Force => {
                    if out.force {
                        bail!(ident.span() => "`force` may only be used once");
                    }

                    out.force = true;
                }
            }

            if input.peek(Token![,]) {
                let _: Token![,] = input.parse()?;
            } else {
                break;
            }
        }

        Ok(out)
    }
}