nade-macro 0.2.1

Macros for nade
Documentation
use proc_macro2::{Punct, TokenStream};
use quote::ToTokens;
use syn::{
    parse::{Parse, ParseStream},
    spanned::Spanned,
    Token,
};

#[derive(Clone)]
pub(crate) enum MaybeStartsWithDollar<T> {
    StartsWithDollar(StartsWithDollar<T>),
    Normal(Normal<T>),
}

impl<T: Parse> Parse for MaybeStartsWithDollar<T> {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let dollar_input = input.fork();
        if let Some(punct) = dollar_input.parse::<Option<Punct>>()? {
            if '$' == punct.as_char() {
                return Ok(Self::StartsWithDollar(StartsWithDollar::parse(
                    &dollar_input,
                )?));
            }
        }

        Ok(Self::Normal(Normal::parse(input)?))
    }
}

impl<T: ToTokens> ToTokens for MaybeStartsWithDollar<T> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            MaybeStartsWithDollar::StartsWithDollar(e) => e.to_tokens(tokens),
            MaybeStartsWithDollar::Normal(e) => e.to_tokens(tokens),
        }
    }
}

impl<T: Parse> TryFrom<proc_macro::TokenStream> for MaybeStartsWithDollar<T> {
    type Error = syn::Error;

    fn try_from(value: proc_macro::TokenStream) -> Result<Self, Self::Error> {
        let mut iter = value.into_iter().peekable();

        if let Some(proc_macro::TokenTree::Punct(p)) = iter.peek() {
            if p.as_char() == '$' {
                let s = syn::parse::<StartsWithDollar<T>>(iter.skip(1).collect())?;
                return Ok(MaybeStartsWithDollar::StartsWithDollar(s));
            }
        }

        let n = syn::parse::<Normal<T>>(iter.collect())?;
        Ok(MaybeStartsWithDollar::Normal(n))
    }
}

impl<T> MaybeStartsWithDollar<T> {
    pub(crate) fn inner(&self) -> &T {
        match self {
            MaybeStartsWithDollar::StartsWithDollar(s) => &s.inner,
            MaybeStartsWithDollar::Normal(n) => &n.inner,
        }
    }
}

impl<T: ToTokens> MaybeStartsWithDollar<T> {
    pub(crate) fn starts_with_dollar(self) -> syn::Result<StartsWithDollar<T>> {
        match self {
            MaybeStartsWithDollar::StartsWithDollar(s) => Ok(s),
            MaybeStartsWithDollar::Normal(n) => Err(syn::Error::new(
                n.inner.span(),
                "expected starting with `$crate`",
            )),
        }
    }
}

#[derive(Clone)]
pub(crate) struct StartsWithDollar<T> {
    pub(crate) inner: T,
}

impl<T: Parse> Parse for StartsWithDollar<T> {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        if input.peek(Token![crate]) {
            Ok(Self {
                inner: input.parse()?,
            })
        } else {
            Err(input.error("expected starting with `$crate`"))
        }
    }
}

impl<T: ToTokens> ToTokens for StartsWithDollar<T> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        <Token![$]>::default().to_tokens(tokens);
        self.inner.to_tokens(tokens);
    }
}

#[derive(Clone)]
pub(crate) struct Normal<T> {
    pub(crate) inner: T,
}

impl<T: Parse> Parse for Normal<T> {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        Ok(Self {
            inner: input.parse()?,
        })
    }
}

impl<T: ToTokens> ToTokens for Normal<T> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        self.inner.to_tokens(tokens);
    }
}