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()?;
let _eq_token: Token![=] = input.parse()?;
match &keyword {
k if k == "apply" => {
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 {
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
}
}
}