moxy-derive 0.0.4

derive macros for moxy crate
Documentation
use quote::quote;
use syn::punctuated::Punctuated;

use crate::{Error, core::Attr};

#[derive(Clone)]
pub enum Arg {
    Flag(syn::Path),
    Literal(syn::Path, syn::Lit),
    Ident(syn::Path, syn::Ident),
    Attr(Attr),
    Expr(syn::Path, syn::Expr),
    Signature(syn::Path, syn::Signature),
}

impl PartialEq for Arg {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Flag(a), Self::Flag(b)) => a == b,
            (Self::Literal(pa, la), Self::Literal(pb, lb)) => pa == pb && la == lb,
            (Self::Ident(pa, ia), Self::Ident(pb, ib)) => pa == pb && ia == ib,
            (Self::Attr(a), Self::Attr(b)) => a == b,
            (Self::Expr(pa, ea), Self::Expr(pb, eb)) => pa == pb && ea == eb,
            (Self::Signature(pa, _), Self::Signature(pb, _)) => pa == pb,
            _ => false,
        }
    }
}

impl Eq for Arg {}

impl Arg {
    pub fn from_flag(path: syn::Path) -> Self {
        Self::Flag(path)
    }

    pub fn from_lit(path: syn::Path, value: syn::Lit) -> Self {
        Self::Literal(path, value)
    }

    pub fn from_ident(path: syn::Path, value: syn::Ident) -> Self {
        Self::Ident(path, value)
    }

    pub fn from_attr(attr: Attr) -> Self {
        Self::Attr(attr)
    }

    pub fn from_expr(path: syn::Path, expr: syn::Expr) -> Self {
        Self::Expr(path, expr)
    }

    pub fn from_signature(path: syn::Path, sig: syn::Signature) -> Self {
        Self::Signature(path, sig)
    }

    #[allow(unused)]
    pub fn is_flag(&self) -> bool {
        matches!(self, Self::Flag(_))
    }

    #[allow(unused)]
    pub fn is_lit(&self) -> bool {
        matches!(self, Self::Literal(_, _))
    }

    #[allow(unused)]
    pub fn is_ident(&self) -> bool {
        matches!(self, Self::Ident(_, _))
    }

    #[allow(unused)]
    pub fn is_attr(&self) -> bool {
        matches!(self, Self::Attr(_))
    }

    #[allow(unused)]
    pub fn is_expr(&self) -> bool {
        matches!(self, Self::Expr(_, _))
    }

    #[allow(unused)]
    pub fn is_signature(&self) -> bool {
        matches!(self, Self::Signature(_, _))
    }

    pub fn path(&self) -> &syn::Path {
        match self {
            Self::Flag(path) => path,
            Self::Literal(path, _) => path,
            Self::Ident(path, _) => path,
            Self::Attr(attr) => attr.path(),
            Self::Expr(path, _) => path,
            Self::Signature(path, _) => path,
        }
    }

    #[allow(unused)]
    pub fn as_lit(&self) -> Option<&syn::Lit> {
        match self {
            Self::Literal(_, v) => Some(v),
            _ => None,
        }
    }

    #[allow(unused)]
    pub fn as_ident(&self) -> Option<&syn::Ident> {
        match self {
            Self::Ident(_, v) => Some(v),
            _ => None,
        }
    }

    #[allow(unused)]
    pub fn as_attr(&self) -> Option<&Attr> {
        match self {
            Self::Attr(attr) => Some(attr),
            _ => None,
        }
    }

    #[allow(unused)]
    pub fn to_lit(&self) -> Option<syn::Lit> {
        match self {
            Self::Literal(_, v) => Some(v.clone()),
            _ => None,
        }
    }

    #[allow(unused)]
    pub fn to_ident(&self) -> Option<syn::Ident> {
        match self {
            Self::Ident(_, v) => Some(v.clone()),
            _ => None,
        }
    }

    #[allow(unused)]
    pub fn to_attr(&self) -> Option<Attr> {
        match self {
            Self::Attr(attr) => Some(attr.clone()),
            _ => None,
        }
    }

    #[allow(unused)]
    pub fn as_expr(&self) -> Option<&syn::Expr> {
        match self {
            Self::Expr(_, expr) => Some(expr),
            _ => None,
        }
    }

    #[allow(unused)]
    pub fn as_signature(&self) -> Option<&syn::Signature> {
        match self {
            Self::Signature(_, sig) => Some(sig),
            _ => None,
        }
    }

    /// Returns tokens for the value side of any `path = value` arg.
    #[allow(unused)]
    pub fn as_value_tokens(&self) -> Option<proc_macro2::TokenStream> {
        match self {
            Self::Literal(_, lit) => Some(quote::quote!(#lit)),
            Self::Ident(_, ident) => Some(quote::quote!(#ident)),
            Self::Expr(_, expr) => Some(quote::quote!(#expr)),
            _ => None,
        }
    }

    #[allow(unused)]
    pub fn error(&self, message: &str) -> proc_macro2::TokenStream {
        self.path().error(message).to_compile_error()
    }
}

impl quote::ToTokens for Arg {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        tokens.extend(match self {
            Self::Flag(path) => quote!(#path),
            Self::Literal(path, value) => quote!(#path = #value),
            Self::Ident(path, ident) => quote!(#path = #ident),
            Self::Attr(attr) => quote!(#attr),
            Self::Expr(_, expr) => quote!(#expr),
            Self::Signature(_, sig) => quote!(#sig),
        });
    }
}

impl syn::parse::Parse for Arg {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        if input.peek(syn::Token![fn]) {
            let sig: syn::Signature = input.parse()?;
            let path: syn::Path = syn::parse_quote!(__signature);
            return Ok(Arg::from_signature(path, sig));
        }

        if input.peek(syn::LitStr) {
            let lit: syn::Lit = input.parse()?;
            let path: syn::Path = syn::parse_quote!(__value);
            return Ok(Arg::from_lit(path, lit));
        }

        let fork = input.fork();

        if fork.parse::<syn::Path>().is_ok() {
            if fork.peek(syn::Token![=]) {
                let path: syn::Path = input.parse()?;
                input.parse::<syn::Token![=]>()?;
                let expr: syn::Expr = input.parse()?;

                return Ok(match expr {
                    syn::Expr::Lit(expr_lit) => Arg::from_lit(path, expr_lit.lit),
                    syn::Expr::Path(expr_path) if expr_path.path.get_ident().is_some() => {
                        Arg::from_ident(path, expr_path.path.get_ident().unwrap().clone())
                    }
                    other => Arg::from_expr(path, other),
                });
            }

            if fork.peek(syn::token::Paren) {
                let path: syn::Path = input.parse()?;
                let list;
                let _ = syn::parenthesized!(list in input);
                let items = Punctuated::<Arg, syn::Token![,]>::parse_terminated(&list)?;
                return Ok(Arg::from_attr(Attr::new(path, items)));
            }

            if fork.is_empty() || fork.peek(syn::Token![,]) {
                let path: syn::Path = input.parse()?;
                return Ok(Arg::from_flag(path));
            }
        }

        let expr: syn::Expr = input.parse()?;
        let path: syn::Path = syn::parse_quote!(__expr);
        Ok(Arg::from_expr(path, expr))
    }
}