app-state-macros 0.1.0

Macros for the app-state crate
Documentation
use proc_macro2::Ident;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{parenthesized, Token};

#[derive(Default, Debug)]
pub(crate) struct PathAttr {
    pub(crate) init: Option<Vec<Ident>>,
    #[cfg(feature = "log")]
    pub(crate) log_member: Option<Ident>,
    #[cfg(feature = "log")]
    pub(crate) no_log: Option<Ident>,
}

impl Parse for PathAttr {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        const EXPECTED_ATTRIBUTE_MESSAGE: &str =
            "unexpected identifier, expected any of: init, log_member, no_log";
        let mut path_attr = PathAttr::default();

        while !input.is_empty() {
            let ident = input.parse::<Ident>().map_err(|error| {
                syn::Error::new(
                    error.span(),
                    format!("{EXPECTED_ATTRIBUTE_MESSAGE}, {error}"),
                )
            })?;
            let attribute_name = &*ident.to_string();

            match attribute_name {
                "init" => {
                    let default;
                    parenthesized!(default in input);

                    path_attr.init = Some(
                        Punctuated::<Ident, Token![,]>::parse_terminated(&default)
                            .map(|punctuated| punctuated.into_iter().collect::<Vec<Ident>>())?,
                    );

                    if path_attr.init.as_ref().unwrap().is_empty() {
                        return Err(syn::Error::new(
                            ident.span(),
                            "expected at least one state to initialize",
                        ));
                    }
                }
                #[cfg(feature = "log")]
                "log_member" => {
                    path_attr.log_member = Some(Ident::new("log_member", ident.span()));
                }
                #[cfg(feature = "log")]
                "no_log" => {
                    path_attr.no_log = Some(Ident::new("no_log", ident.span()));
                }
                _ => {
                    return Err(syn::Error::new(ident.span(), EXPECTED_ATTRIBUTE_MESSAGE));
                }
            }

            if !input.is_empty() {
                input.parse::<Token![,]>()?;
            }
        }

        #[cfg(feature = "log")]
        if path_attr.no_log.is_some() && path_attr.log_member.is_some() {
            return Err(syn::Error::new(
                path_attr.no_log.unwrap().span(),
                "cannot use both no_log and log_member",
            ));
        }

        Ok(path_attr)
    }
}