robespierre_fw_macros/
lib.rs

1use proc_macro2::TokenStream;
2use quote::{format_ident, quote, ToTokens};
3use syn::{
4    parse::Parse, parse_macro_input, punctuated::Punctuated, token::Comma, Attribute, Expr, FnArg,
5    Type,
6};
7
8#[allow(clippy::borrowed_box)]
9struct ExtraArgs<'a>(&'a [&'a Box<Type>], &'a [Vec<Attribute>]);
10
11impl<'a> ToTokens for ExtraArgs<'a> {
12    fn to_tokens(&self, tokens: &mut TokenStream) {
13        for (ty, attrs) in self.0.iter().zip(self.1.iter()) {
14            let ty_from_message =
15                quote! {<#ty as ::robespierre::framework::standard::extractors::FromMessage>};
16            let config_ty = quote! {#ty_from_message ::Config};
17            let config_ty_ufcs_root = quote! {<#config_ty as ::robespierre::framework::standard::extractors::ExtractorConfigBuilder>};
18            let config_builder = {
19                let tks = quote! {<#config_ty as ::std::default::Default>::default()};
20
21                let tks = attrs.iter().fold(tks, |tks, attr| {
22                    if attr.path.is_ident("delimiter") {
23                        let expr = attr.parse_args::<Expr>().expect("parse as expr");
24                        quote! { #config_ty_ufcs_root :: delimiter(#tks, #expr)}
25                    } else if attr.path.is_ident("delimiters") {
26                        struct DelimiterList(Punctuated<Expr, Comma>);
27
28                        impl Parse for DelimiterList {
29                            fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
30                                Punctuated::<Expr, Comma>::parse_terminated(input).map(Self)
31                            }
32                        }
33
34                        let delimiters = attr
35                            .parse_args::<DelimiterList>()
36                            .expect("parse as delimiter list")
37                            .0;
38                        quote! { #config_ty_ufcs_root :: delimiters(#tks, ::std::vec![#delimiters])}
39                    } else {
40                        panic!("Unknown attribute: {:?}", attr.path);
41                    }
42                });
43
44                tks
45            };
46            tokens.extend(quote! {
47                #ty_from_message ::from_message(ctx.clone(), __message.clone(), #config_builder).await?,
48            });
49        }
50    }
51}
52
53// Input:
54//     async fn cmd(ctx: &FwContext, message: &Message, RawArgs(args): RawArgs) -> CommandResult {
55//         // ...
56//     }
57// Output:
58//     fn cmd<'a>(ctx: &'a FwContext, message: &'a Arc<Message>, args: &'a str) -> Pin<Box<dyn Future<Output = CommandResult> + Send + 'a>>> {
59//         async fn __cmd_impl(ctx: &FwContext, message: &Message, RawArgs(args): RawArgs) -> CommandResult {
60//             // ...
61//         }
62//         Box::pin(async move {
63//             let __message = Msg {message: Arc::clone(message), args: Arc::new(args.to_string())};
64//             __cmd_impl(ctx, message, <RawArgs as FromMessage>::from_message(ctx.clone(), __message.clone()).await?, /* other extractors if present */).await
65//         })
66//     }
67#[proc_macro_attribute]
68pub fn command(
69    _attr: proc_macro::TokenStream,
70    item: proc_macro::TokenStream,
71) -> proc_macro::TokenStream {
72    let mut command_func = parse_macro_input!(item as syn::ItemFn);
73
74    let impl_name = format_ident!("__{}_impl", &command_func.sig.ident);
75    let old_name = std::mem::replace(&mut command_func.sig.ident, impl_name.clone());
76
77    let visibility = std::mem::replace(&mut command_func.vis, syn::Visibility::Inherited);
78
79    let attrs = command_func
80        .sig
81        .inputs
82        .iter_mut()
83        .skip(2)
84        .filter_map(|it| match it {
85            FnArg::Typed(t) => {
86                let (my_attrs, other_attrs): (Vec<_>, _) =
87                    std::mem::take(&mut t.attrs).into_iter().partition(|attr| {
88                        attr.path.is_ident("delimiter") || attr.path.is_ident("delimiters")
89                    });
90                t.attrs = other_attrs;
91
92                Some(my_attrs)
93            }
94            FnArg::Receiver(_) => None,
95        })
96        .collect::<Vec<_>>();
97
98    let extra_args = command_func
99        .sig
100        .inputs
101        .iter()
102        .skip(2)
103        .filter_map(|it| match it {
104            FnArg::Typed(a) => Some(&a.ty),
105            FnArg::Receiver(_) => None,
106        })
107        .collect::<Vec<_>>();
108
109    let extra_args = ExtraArgs(&extra_args, &attrs);
110
111    let result = quote! {
112        #visibility fn #old_name <'a> (
113            ctx: &'a ::robespierre::framework::standard::FwContext,
114            message: &'a ::std::sync::Arc<::robespierre_models::channels::Message>,
115            args: &'a ::std::primitive::str,
116        ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::robespierre::framework::standard::CommandResult> + ::std::marker::Send + 'a>> {
117            #command_func
118
119            ::std::boxed::Box::pin(async move {
120                let __message = ::robespierre::framework::standard::extractors::Msg {message: ::std::sync::Arc::clone(message), args: ::std::sync::Arc::new(args.to_string())};
121                #impl_name (
122                    ctx,
123                    message,
124                    #extra_args
125                ).await
126            })
127        }
128    };
129    proc_macro::TokenStream::from(result)
130}