iridis_file_ext_derive/
lib.rs1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{
6    parse::{Parse, ParseStream},
7    parse_macro_input,
8    punctuated::Punctuated,
9    DeriveInput, ImplItem, ItemImpl, ReturnType, Token,
10};
11
12#[proc_macro_derive(FileExtPlugin)]
13pub fn derive_file_ext_plugin(input: TokenStream) -> TokenStream {
14    let input = parse_macro_input!(input as DeriveInput);
15    let name = input.ident;
16
17    let expanded = quote! {
18        #[cfg(feature = "cdylib")]
19        #[doc(hidden)]
20        #[unsafe(no_mangle)]
21        pub static iridis_FILE_EXT_PLUGIN: DynamicallyLinkedFileExtPluginInstance =
22            || <#name>::new();
23
24        static DEFAULT_TOKIO_RUNTIME: std::sync::LazyLock<iridis_file_ext::prelude::thirdparty::tokio::runtime::Runtime> =
25            std::sync::LazyLock::new(|| {
26                iridis_file_ext::prelude::thirdparty::tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime")
27            });
28
29        fn default_runtime<T: Send + 'static>(
30            task: impl Future<Output = T> + Send + 'static,
31        ) -> iridis_file_ext::prelude::thirdparty::tokio::task::JoinHandle<T> {
32            match iridis_file_ext::prelude::thirdparty::tokio::runtime::Handle::try_current() {
33                Ok(handle) => handle.spawn(task),
34                Err(_) => DEFAULT_TOKIO_RUNTIME.spawn(task),
35            }
36        }
37    };
38
39    TokenStream::from(expanded)
40}
41
42struct MacroArgs {
43    runtime: String,
44}
45
46impl Parse for MacroArgs {
47    fn parse(input: ParseStream) -> syn::Result<Self> {
48        let mut runtime = String::new();
49
50        let vars = Punctuated::<syn::Meta, Token![,]>::parse_terminated(input)?;
51
52        for var in vars {
53            if let syn::Meta::NameValue(name_value) = var {
54                let name = name_value.path.get_ident().unwrap().to_string();
55
56                if name == "runtime" {
57                    if let syn::Expr::Lit(lit) = &name_value.value {
58                        if let syn::Lit::Str(lit_str) = &lit.lit {
59                            runtime = lit_str.value();
60                        }
61                    }
62                }
63            }
64        }
65
66        Ok(MacroArgs { runtime })
67    }
68}
69
70#[proc_macro_attribute]
71pub fn file_ext_plugin(attr: TokenStream, item: TokenStream) -> TokenStream {
72    let mut impl_block = parse_macro_input!(item as ItemImpl);
73
74    let args = parse_macro_input!(attr as MacroArgs);
75    let runtime_tokens = args.runtime.parse::<proc_macro2::TokenStream>().unwrap();
76
77    for item in &mut impl_block.items {
78        if let ImplItem::Fn(method) = item {
79            let was_async = method.sig.asyncness.is_some();
80            method.sig.asyncness = None;
81
82            let old_block = method.block.clone();
83
84            if was_async {
85                let old_return_type = match &method.sig.output {
86                    ReturnType::Default => quote! { () },
87                    ReturnType::Type(_, ty) => {
88                        if method.sig.ident == "new" {
89                            quote! { iridis_file_ext::prelude::thirdparty::eyre::Result<Box<dyn FileExtPlugin>> }
90                        } else {
91                            quote! { #ty }
92                        }
93                    }
94                };
95
96                method.sig.output = syn::parse_quote! {
97                    -> iridis_file_ext::prelude::thirdparty::tokio::task::JoinHandle<#old_return_type>
98                };
99
100                if method.sig.ident == "new" {
101                    method.block = syn::parse_quote! {
102                        {
103                            #runtime_tokens(async move {
104                                #old_block.map(|node| Box::new(node) as Box<dyn FileExtPlugin>)
105                            })
106                        }
107                    };
108                } else if method.sig.ident == "load" {
109                    method.block = syn::parse_quote! {
110                        {
111                            #runtime_tokens(async move {
112                                #old_block
113                            })
114                        }
115                    };
116                }
117            }
118        }
119    }
120
121    quote! {
122        #impl_block
123    }
124    .into()
125}