miwa-macros 0.1.0

Miwa macros
Documentation
use darling::{ast::NestedMeta, FromMeta};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{FnArg, ItemFn, Meta, Path};

use crate::utils::get_crate_name;

#[derive(FromMeta, Default)]
pub struct Extension {
    #[darling(default)]
    internal: bool,
    name: Option<String>,
    #[darling(default)]
    provides: PathList,
}

#[derive(Default, Debug)]
pub struct PathList(pub Vec<Path>);

impl FromMeta for PathList {
    fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
        let mut res = Vec::new();
        for item in items {
            if let NestedMeta::Meta(Meta::Path(p)) = item {
                res.push(p.clone());
            } else {
                return Err(darling::Error::custom("Invalid path list"));
            }
        }
        Ok(PathList(res))
    }
}

pub fn generate(extension: &Extension, item_fn: &ItemFn) -> proc_macro::TokenStream {
    let crate_name = get_crate_name(extension.internal);
    let vis = &item_fn.vis;
    let ident = &item_fn.sig.ident;
    let def_struct = quote! {
        #vis struct #ident;
    };

    let extension_name = match extension.name {
        Some(ref n) => n.clone(),
        None => ident.clone().to_string(),
    };
    let mut extractors = Vec::new();
    let mut args = Vec::new();
    let mut requires = Vec::new();

    for (idx, input) in item_fn.sig.inputs.clone().into_iter().enumerate() {
        if let FnArg::Typed(pat) = input {
            let ty = &pat.ty;
            let id = quote::format_ident!("p{}", idx);
            args.push(id.clone());
            // requires.push(quote! { std::any::TypeId::of::<#ty>()});
            requires.push(quote! { <#ty as #crate_name::core::MiwaId>::component_id()});
            extractors.push(quote! {
                let #id = <#ty as #crate_name::core::FromMiwaContext>::from_context(&context)?;
            });
        }
    }

    let provides = provides(extension);

    quote! {
        #[allow(non_camel_case_types)]
        #def_struct


        #[async_trait::async_trait]
        impl #crate_name::core::ExtensionFactory for #ident {

            fn name(&self) -> &str {
                #extension_name
            }

            async fn init(&self, context : &#crate_name::core::MiwaContext) -> #crate_name::core::MiwaResult<Box<dyn #crate_name::core::Extension>> {
               #(#extractors)*
               #item_fn
               let extension = #ident(#(#args),*).await?;

               Ok(Box::new(extension))
            }

            fn requires(&self) -> Vec<std::any::TypeId> {
                vec![#(#requires),*]
            }

            fn exposes(&self) -> Vec<std::any::TypeId> {
                vec![#(#provides),*]
            }
        }
    }.into()
}

fn provides(extension: &Extension) -> Vec<TokenStream> {
    extension
        .provides
        .0
        .iter()
        .flat_map(|path| path.segments.last())
        .map(|segment| segment.ident.clone())
        .map(|inner_ident| quote! { std::any::TypeId::of::<#inner_ident>()})
        .collect()
}