clappen 0.1.5

Clap flatten prefix macro
Documentation
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::parse::{Parse, ParseStream, Result};
use syn::spanned::Spanned;
use syn::{Ident, LitStr, Path, Token, Type};

use crate::helper;

#[derive(Clone)]
pub(crate) enum NestedAttributes {
    Apply(TokenStream),
    Prefix(Option<String>),
}

impl Parse for NestedAttributes {
    fn parse(input: ParseStream) -> Result<Self> {
        let keyword: Ident = input.parse()?;
        // Advance the iterator so that we can skip the '=' token
        let _eq_token: Token![=] = input.parse()?;

        match &keyword {
            k if k == "apply" => {
                // parsed as a path, kept as tokens: `$crate::child` needs the `$`
                let dollar: Option<Token![$]> = input.parse()?;
                let path: Path = input.parse()?;
                Ok(NestedAttributes::Apply(quote! { #dollar #path }))
            }
            k if k == "prefix" => {
                let val: LitStr = input.parse()?;
                let val = val.value();

                if val.is_empty() {
                    return Err(syn::Error::new(
                        keyword.span(),
                        "'prefix' attribute field  must not be empty when provided",
                    ));
                }

                Ok(NestedAttributes::Prefix(Some(val)))
            }
            e => Err(syn::Error::new(
                keyword.span(),
                format!("unknown attribute field '{e}'"),
            )),
        }
    }
}

#[derive(Clone)]
pub(crate) struct Attributes {
    pub apply: TokenStream,
    pub prefix: String,
}

impl TryFrom<Vec<NestedAttributes>> for Attributes {
    type Error = ();

    fn try_from(fields: Vec<NestedAttributes>) -> std::result::Result<Self, Self::Error> {
        let macro_uses: Vec<_> = fields
            .iter()
            .flat_map(|e| match e {
                NestedAttributes::Apply(e) => Some(e),
                _ => None,
            })
            .collect();

        let macro_use = match macro_uses.first() {
            Some(e) => e,
            None => return Err(()),
        };

        let field_prefix: Vec<_> = fields
            .iter()
            .flat_map(|ref e| match e {
                NestedAttributes::Prefix(e) => e,
                _ => &None,
            })
            .collect();

        Ok(Attributes {
            apply: macro_use.to_owned().clone(),
            prefix: field_prefix
                .first()
                .map(|e| (*e).to_owned())
                .unwrap_or_default(),
        })
    }
}

impl Attributes {
    pub(crate) fn nested_macro_call(
        &self,
        default_prefix: &str,
        struct_prefix: &str,
        field_ident: &Ident,
        field_type: &Type,
    ) -> (TokenStream, TokenStream) {
        let apply = &self.apply;
        let nested_prefix = helper::nested_step_prefix(&self.prefix, default_prefix, struct_prefix);
        let module_name = helper::macro_module_name(&field_ident.to_string());
        let new_type_full_ref =
            Self::new_full_type_definition(&module_name, &nested_prefix, field_type);

        (
            quote! {
                    pub(crate) mod #module_name {
                        #apply!(#nested_prefix);
                    }
            },
            new_type_full_ref,
        )
    }

    fn new_full_type_definition(
        module_name: &Ident,
        nested_prefix: &str,
        field_type: &Type,
    ) -> TokenStream {
        // allow for fully qualified type notation, needed for $crate::something
        let field_type: Ident = match &field_type {
            Type::Path(e) => match e.path.segments.last() {
                Some(e) => e.ident.to_owned().clone(),
                None => {
                    return syn::Error::new(
                        field_type.span(),
                        format!("cannot get ident out of {}", field_type.to_token_stream()),
                    )
                    .into_compile_error();
                }
            },
            _ => {
                return syn::Error::new(field_type.span(), "unknown attribute")
                    .into_compile_error();
            }
        };

        let field_type = helper::prefixed_ident(nested_prefix, &field_type.to_string());

        quote! {
            #module_name::#field_type
        }
    }
}