diff --git a/plugin_macros/Cargo.toml b/plugin_macros/Cargo.toml index 58ca022..b3bd038 100644 --- a/plugin_macros/Cargo.toml +++ b/plugin_macros/Cargo.toml @@ -14,13 +14,11 @@ readme = "README.md" proc-macro = true [dependencies] -syn = "1.0" -quote = "1.0" -darling = "0.14.1" convert_case = "0.5.0" serde_json = "1.0" clightningrpc-plugin = { path = "../plugin"} -#clightningrpc-plugin = "0.3.0-beta.3" +kproc-parser = { version = "0.0.1-beta.3" } +#clightningrpc-plugin = "0.3.0-beta.7" [dev-dependencies] rstest = "0.10.0" diff --git a/plugin_macros/examples/macros_ex.rs b/plugin_macros/examples/macros_ex.rs index 380863a..29574b3 100644 --- a/plugin_macros/examples/macros_ex.rs +++ b/plugin_macros/examples/macros_ex.rs @@ -1,47 +1,58 @@ //! plugin macros usage example. extern crate clightningrpc_plugin_macros; -use clightningrpc_plugin_macros::{ - add_plugin_rpc, notification, plugin_register_notification, rpc_method, -}; -use serde_json::{json, Value}; +use clightningrpc_plugin_macros::*; + +use serde_json::json; +use serde_json::Value; use clightningrpc_plugin::commands::RPCCommand; use clightningrpc_plugin::errors::PluginError; use clightningrpc_plugin::plugin::Plugin; -use clightningrpc_plugin::types::LogLevel; -use clightningrpc_plugin::{add_rpc, register_notification}; + +#[derive(Clone)] +struct State; #[rpc_method( rpc_name = "foo_macro", description = "This is a simple and short description" )] -pub fn foo_rpc(_plugin: Plugin<()>, _request: Value) -> Result { - /// The name of the parameters can be used only if used, otherwise can be omitted - /// the only rules that the macros require is to have a propriety with the following rules: - /// - Plugin as _plugin - /// - CLN JSON request as _request - /// The function parameter can be specified in any order. - let response = json!({"is_dynamic": _plugin.dynamic, "rpc_request": _request}); +pub fn foo_rpc(plugin: &mut Plugin, request: Value) -> Result { + let response = json!({"is_dynamic": plugin.dynamic, "rpc_request": request}); Ok(response) } #[notification(on = "rpc_command")] -fn on_rpc(_plugin: Plugin<()>, _request: Value) { - _plugin.log(LogLevel::Info, "received an RPC notification"); +fn on_rpc(plugin: &mut Plugin, request: &Value) { + use clightningrpc_plugin::types::LogLevel; + plugin.log(LogLevel::Info, "received an RPC notification"); } fn main() { // as fist step you need to make a new plugin instance // more docs about Plugin struct is provided under the clightning_plugin crate - let mut plugin = Plugin::new((), true); - - // The macros helper that help to register a RPC method with the name - // without worry about all the rules of the library - add_plugin_rpc!(plugin, "foo_macro"); - - // the macros helper that help to register a notification with the - // event name without worry about the rules of the library :) - plugin_register_notification!(plugin, "rpc_command"); + let mut plugin = Plugin::new(State, true); + // FIXME: this is just for now, we will write a plugin macros + // that define the definition like in the linux kernel a module is + // defined. + // + // ``` + // module! { + // type: RustMinimal, + // name: "rust_minimal", + // author: "Rust for Linux Contributors", + // description: "Rust minimal sample", + // license: "GPL", + // } + // ``` + let call = on_rpc(); + plugin.register_notification(&call.on_event.clone(), call); + let call = foo_rpc(); + plugin.add_rpc_method( + &call.name.clone(), + &call.usage.clone(), + &call.description.clone(), + call, + ); plugin.start(); } diff --git a/plugin_macros/src/attr_parser.rs b/plugin_macros/src/attr_parser.rs new file mode 100644 index 0000000..3f2a0f6 --- /dev/null +++ b/plugin_macros/src/attr_parser.rs @@ -0,0 +1,45 @@ +//! A custom parser for the attributes + +use std::collections::HashMap; + +use kproc_parser::{ + check, + kparser::{KParserError, KParserTracer}, + kproc_macros::KTokenStream, + trace, +}; + +pub(crate) struct AttributeParser {} + +impl AttributeParser { + pub fn parse( + stream: &mut KTokenStream, + tracer: &dyn KParserTracer, + ) -> Result, KParserError> { + parse_key_values(stream, tracer) + } +} + +fn parse_key_values( + stream: &mut KTokenStream, + tracer: &dyn KParserTracer, +) -> Result, KParserError> { + let mut hash_map = HashMap::new(); + trace!(tracer, "start parsing key values"); + trace!(tracer, "start with tok {}", stream.peek()); + while !stream.is_end() { + let key = stream.advance(); + check!("=", stream.peek())?; + let _ = stream.advance(); + let value = stream.advance(); + if !stream.is_end() && stream.match_tok(",") { + trace!(tracer, "removing the `,` tok"); + check!(",", stream.advance())?; + } + let value = value.to_string().replace("\"", ""); + trace!(tracer, "key {key} = value {value}"); + hash_map.insert(key.to_string(), value.to_string()); + trace!(tracer, "map is {:?}", hash_map); + } + Ok(hash_map) +} diff --git a/plugin_macros/src/lib.rs b/plugin_macros/src/lib.rs index 342892b..829e07f 100644 --- a/plugin_macros/src/lib.rs +++ b/plugin_macros/src/lib.rs @@ -3,69 +3,38 @@ //! with less code. //! //! author: https://github.com/vincenzopalazzo -use convert_case::{Case, Casing}; -use darling::FromMeta; -use proc_macro::TokenStream; -use quote::ToTokens; -use std::fmt; -use syn::{parse, parse_macro_input, AttributeArgs, Item, ItemFn}; +use kproc_parser::kparser::KParserTracer; +use kproc_parser::proc_macro::TokenStream; -/// The struct where the tools darling store the information -/// the macros, so we implement the struct where we want to unmarshall -/// the user information. -#[derive(FromMeta)] -struct RPCMethodMacro { - /// rpc_name is the name of the plugin that the user want to use - /// regarding the RPC method registered by the plugin. - rpc_name: String, - /// description is the short description that the user want to add - /// to the rpc method. - description: String, - /// usage is some tips to give the user on how t use the rpc method - #[darling(default)] - usage: String, - // FIXME: add the long description -} +mod notification; +mod rpc_method; -/// Method to generate the RPC call in a string -/// format -struct RPCCall { - /// original name of the rpc method specified - /// by the user - original_name: String, - /// the name of the struct that will be created by the - /// macros by write in the camel case the original_name - struct_name: String, - /// the function body of the method specified by the user - /// in the function. - fn_body: String, - /// the description of the rpc method - description: String, - /// the usage tips that is use by core lightning to give tips to the user - usage: String, -} +mod attr_parser; + +struct Tracer; -/// implementation fo the Display method of the rpc method that give -/// that convert the struct in the function call in a valid rust syntax. -impl fmt::Display for RPCCall { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(formatter, "{}", self.fn_body) +impl KParserTracer for Tracer { + fn log(&self, msg: &str) { + eprintln!("\x1b[93mkproc-tracing\x1b[1;97m {msg}"); } } /// procedural macros that can be used wit the following code /// ```no_run /// use serde_json::{json, Value}; -/// use clightningrpc_plugin_macros::{add_plugin_rpc, rpc_method}; +/// use clightningrpc_plugin_macros::rpc_method; /// use clightningrpc_plugin::commands::RPCCommand; /// use clightningrpc_plugin::plugin::Plugin; /// use clightningrpc_plugin::errors::PluginError; /// +/// #[derive(Clone)] +/// struct State; +/// /// #[rpc_method( /// rpc_name = "foo", /// description = "This is a simple and short description" /// )] -/// pub fn foo_rpc(_plugin: Plugin<()>, _request: Value) -> Result { +/// pub fn foo_rpc(_plugin: &mut Plugin, _request: Value) -> Result { /// /// The name of the parameters can be used only if used, otherwise can be omitted /// /// the only rules that the macros require is to have a propriety with the following rules: /// /// - Plugin as _plugin @@ -76,301 +45,27 @@ impl fmt::Display for RPCCall { /// ``` #[proc_macro_attribute] pub fn rpc_method(attr: TokenStream, item: TokenStream) -> TokenStream { - // parse the macros attributes - let attr_list = parse_macro_input!(attr as AttributeArgs); - let args = RPCMethodMacro::from_list(&attr_list); - let macro_args = match args { - Ok(args) => args, - Err(err) => panic!("{}", err), - }; - - // parse the item, that in this case need to be a function - // declaration. - let ast_item: Item = parse(item.clone()).unwrap(); - let fn_dec = match ast_item { - Item::Fn(decl) => decl, - _ => panic!("The macros is applied over a not function declaration"), - }; - let rpc_call = generate_method_call(¯o_args, fn_dec); - generate_rpc_method(&item, &rpc_call).parse().unwrap() -} - -// helper method to generator the RPCCall struct and make the code more readable and cleaner. -fn generate_method_call(rpc: &RPCMethodMacro, fun_dec: ItemFn) -> RPCCall { - RPCCall { - original_name: rpc.rpc_name.to_owned(), - struct_name: rpc.rpc_name.as_str().to_case(Case::Pascal), - fn_body: fun_dec.block.into_token_stream().to_string(), - description: rpc.description.to_string(), - usage: rpc.usage.to_string(), - } -} - -// helper function to generate the RPC Generator over a generic type -// to make sure that the user can use the plugin state to build the RPC method. -fn generate_rpc_method(item: &TokenStream, method_call: &RPCCall) -> String { - format!( - " - use std::marker::PhantomData; - - #[derive(Clone, Default)] - struct {} {{ - // keep the information added in the macros to - // help future macros to register the plugin. - name: String, - description: String, - long_description: String, - usage: String, - _phantom: Option> - }} - - impl {} {{ - pub fn new() -> Self {{ - {}::{{ - name: \"{}\".to_string(), - description: \"{}\".to_string(), - long_description: \"{}\".to_string(), - usage: \"{}\".to_string(), - _phantom: None, - }} - }} - - {} - - }} - - - impl RPCCommand for {} {{ - fn call<'c>(&self, _plugin: &mut Plugin, _request: Value) -> Result {{ - {} - }} - }} -", - method_call.struct_name, - method_call.struct_name, - method_call.struct_name, - method_call.original_name, - method_call.description, - method_call.description, - method_call.usage, - item, - method_call.struct_name, - method_call, - ) + rpc_method::parse(attr, item) } -/// procedural macros to generate the code to register a RPC method created with the -/// `rpc_method` procedural macro. -/// -/// this procedural macro hide the complicity that the user need to learn to register -/// a rpc method with `rpc_method` and continue to be under magic. -/// -/// the macros take in input as first parameter the plugin, and as second the name of the -/// rpc function specified by the user. -/// +/// procedural macros that can be used wit the following code /// ```no_run -/// use clightningrpc_plugin_macros::{add_plugin_rpc, rpc_method}; /// use serde_json::{json, Value}; -/// -/// use clightningrpc_plugin::add_rpc; +/// use clightningrpc_plugin_macros::notification; /// use clightningrpc_plugin::commands::RPCCommand; /// use clightningrpc_plugin::plugin::Plugin; +/// use clightningrpc_plugin::types::LogLevel; /// use clightningrpc_plugin::errors::PluginError; /// -/// #[rpc_method( -/// rpc_name = "foo", -/// description = "This is a simple and short description" -/// )] -/// pub fn foo_rpc(_plugin: Plugin<()>, _request: Value) -> Result { -/// Ok(json!({"is_dynamic": _plugin.dynamic, "rpc_request": _request})) -/// } -/// -/// fn main() { -/// // as fist step you need to make a new plugin instance -/// // more docs about Plugin struct is provided under the clightning_plugin crate -/// let mut plugin = Plugin::new((), true); +/// #[derive(Clone)] +/// struct State; /// -/// // The macros helper that help to register a RPC method with the name -/// // without worry about all the rules of the library -/// add_plugin_rpc!(plugin, "foo"); -/// -/// plugin.start(); +/// #[notification(on = "rpc_command")] +/// fn on_rpc(plugin: &mut Plugin, request: &Value) { +/// plugin.log(LogLevel::Info, "received an RPC notification"); /// } /// ``` -#[proc_macro] -pub fn add_plugin_rpc(items: TokenStream) -> TokenStream { - let input = items.into_iter().collect::>(); - // FIXME: improve parsing - assert_eq!(input.len(), 3); - format!( - "use clightningrpc_plugin::add_rpc; - add_rpc!({}, {});", - input[0], - input[2] - .to_string() - .replace('"', "") - .as_str() - .to_case(Case::Pascal) - ) - .parse() - .unwrap() -} - -/// The struct where the tools darling store the information -/// the macros regardng the notification, so we implement the struct where we want to unmarshall -/// the user information. -#[derive(FromMeta)] -struct NotificationMethodMacro { - /// rpc_name is the name of the plugin that the user want to use - /// regarding the RPC method registered by the plugin. - on: String, -} - -/// RPC notification struct contains all the information to generate a struct -struct RPCNotification { - /// The name of the notification specified by the user - original_name: String, - /// struct name for the command struct - struct_name: String, - /// function body defined by the user - fn_body: String, -} - -/// implementation fo the Display method of the rpc method that give -/// that convert the struct in the function call in a valid rust syntax. -impl fmt::Display for RPCNotification { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(formatter, "{}", self.fn_body) - } -} - #[proc_macro_attribute] pub fn notification(attr: TokenStream, item: TokenStream) -> TokenStream { - // parse the macros attributes - let attr_list = parse_macro_input!(attr as AttributeArgs); - let args = NotificationMethodMacro::from_list(&attr_list); - let macro_args = match args { - Ok(args) => args, - Err(err) => panic!("{}", err), - }; - - // parse the item, that in this case need to be a function - // declaration. - let ast_item: Item = parse(item.clone()).unwrap(); - let fn_dec = match ast_item { - Item::Fn(decl) => decl, - _ => panic!("The macros is applied over a not function declaration"), - }; - let notification = generate_notification_call(¯o_args, fn_dec); - generate_notification_method(&item, ¬ification) - .parse() - .unwrap() -} - -// helper method to generator the RPCCall struct and make the code more readable and cleaner. -fn generate_notification_call( - notification: &NotificationMethodMacro, - fun_dec: ItemFn, -) -> RPCNotification { - RPCNotification { - original_name: notification.on.to_owned(), - // FIXMEL append some suffix - struct_name: notification.on.as_str().to_case(Case::Pascal), - fn_body: fun_dec.block.into_token_stream().to_string(), - } -} - -/// helper method to generate the necessary Rust code to implement -fn generate_notification_method(item: &TokenStream, method_call: &RPCNotification) -> String { - format!( - " - #[derive(Clone, Default)] - struct {} {{ - // keep the information added in the macros to - // help future macros to register the plugin. - on_event: String, - _phantom: Option> - }} - - impl {} {{ - pub fn new() -> Self {{ - {}::{{ - on_event: \"{}\".to_string(), - _phantom: None, - }} - }} - - {} - - }} - - - impl RPCCommand for {} {{ - fn call_void<'c>(&self, _plugin: &mut Plugin, _request: &'c Value) {{ - {} - }} - }} -", - method_call.struct_name, - method_call.struct_name, - method_call.struct_name, - method_call.original_name, - item, - method_call.struct_name, - method_call, - ) -} - -/// procedural macros to generate the code to register a RPC method created with the -/// `rpc_method` procedural macro. -/// -/// this procedural macro hide the complicity that the user need to learn to register -/// a rpc method with `rpc_method` and continue to be under magic. -/// -/// the macros take in input as first parameter the plugin, and as second the name of the -/// rpc function specified by the user. -/// -/// ```no_run -/// use std::marker::PhantomData; -/// use serde_json::{json, Value}; -/// -/// use clightningrpc_plugin::commands::RPCCommand; -/// use clightningrpc_plugin::plugin::Plugin; -/// use clightningrpc_plugin::types::LogLevel; -/// use clightningrpc_plugin_macros::{add_plugin_rpc, notification, rpc_method, plugin_register_notification}; -/// -/// #[notification(on = "rpc_command")] -/// fn on_rpc(_plugin: Plugin<()>, _request: Value) { -/// _plugin.log(LogLevel::Info, "received an RPC notification"); -/// } -/// -/// fn main() { -/// // as fist step you need to make a new plugin instance -/// // more docs about Plugin struct is provided under the clightning_plugin crate -/// let mut plugin = Plugin::new((), true); -/// -/// // plugin_register_notification macros helper that help to register a notification with the -/// // event name without worry about the rules of the library :) -/// plugin_register_notification!(plugin, "rpc_command"); -/// -/// plugin.start(); -/// } -/// ``` -#[proc_macro] -pub fn plugin_register_notification(items: TokenStream) -> TokenStream { - let input = items.into_iter().collect::>(); - // FIXME: improve parsing - assert_eq!(input.len(), 3); - format!( - "use clightningrpc_plugin::register_notification; - register_notification!({}, {});", - input[0], - input[2] - .to_string() - .replace('"', "") - .as_str() - .to_case(Case::Pascal) - ) - .parse() - .unwrap() + notification::parse(attr, item) } diff --git a/plugin_macros/src/notification.rs b/plugin_macros/src/notification.rs new file mode 100644 index 0000000..0804465 --- /dev/null +++ b/plugin_macros/src/notification.rs @@ -0,0 +1,143 @@ +//! Crate that to implement the notification proc macro. +use std::fmt::{Display, Error, Formatter}; +use std::process::abort; + +use convert_case::{Case, Casing}; + +use kproc_parser::kparser::{DummyTracer, KParserTracer}; +use kproc_parser::kproc_macros::KTokenStream; +use kproc_parser::proc_macro::TokenStream; +use kproc_parser::rust::ast_nodes::MethodDeclToken; +use kproc_parser::rust::kparser::RustParser; +use kproc_parser::trace; + +use crate::attr_parser::AttributeParser; + +/// The struct where the tools darling store the information +/// the macros regardng the notification, so we implement the struct where we want to unmarshall +/// the user information. +struct NotificationMethodMacro { + /// rpc_name is the name of the plugin that the user want to use + /// regarding the RPC method registered by the plugin. + on: String, +} + +/// RPC notification struct contains all the information to generate a struct +struct RPCNotification { + /// The name of the notification specified by the user + original_name: String, + /// the original function name + /// where the macro is operating on. + fn_name: String, + /// struct name for the command struct + struct_name: String, + /// function parameters defined in the user function + fn_params: TokenStream, + /// function body defined by the user + fn_body: TokenStream, + /// Plugin state defined by the user and pass + /// as first method parameter. + state_ty: String, +} + +/// implementation fo the Display method of the rpc method that give +/// that convert the struct in the function call in a valid rust syntax. +impl Display for RPCNotification { + fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), Error> { + write!(formatter, "{}", self.fn_body) + } +} + +/// Core function to parse token stream and return +/// the generate token stream +pub(crate) fn parse(attr: TokenStream, item: TokenStream) -> TokenStream { + let tracer = DummyTracer; + let parser = RustParser::with_tracer(&tracer); + let fn_ast = parser.parse_fn(&item); + + let mut attr = KTokenStream::new(&attr); + let parser = AttributeParser::parse(&mut attr, &tracer); + if let Err(err) = parser { + err.emit(); + abort(); + } + let parser = parser.unwrap(); + let attr = NotificationMethodMacro { + on: parser.get("on").unwrap().to_owned(), + }; + let meta = generate_notification_call(&attr, fn_ast); + generate_notification_method(meta, &tracer) +} + +// helper method to generator the RPCCall struct and make the code more readable and cleaner. +fn generate_notification_call( + notification: &NotificationMethodMacro, + fun_dec: MethodDeclToken, +) -> RPCNotification { + let struct_name = format!("On{}", notification.on.as_str().to_case(Case::Pascal)); + let Some((_, ty)) = fun_dec.params.first() else { + panic!("TODO: we need to return an error, but for now the list of params is empty"); + }; + let Some(ty) = ty.generics.clone().and_then(|gen| gen.first().cloned()) else { + panic!("TODO: we need to return an error , but or now the inner generics is None") + }; + RPCNotification { + original_name: notification.on.to_owned(), + // FIXME: append some suffix + struct_name, + fn_name: fun_dec.ident.to_string(), + fn_params: fun_dec.raw_params, + fn_body: fun_dec.raw_body.unwrap(), + state_ty: ty.to_string(), + } +} + +/// helper method to generate the necessary Rust code to implement +fn generate_notification_method( + method_call: RPCNotification, + tracer: &dyn KParserTracer, +) -> TokenStream { + let struct_name = method_call.struct_name; + let event_name = method_call.original_name; + let fn_params = method_call.fn_params; + let fn_body = method_call.fn_body; + let state_ty = method_call.state_ty; + let fn_name = method_call.fn_name; + let result = format!( + " + #[derive(Clone, Default)] + struct {struct_name} {{ + // keep the information added in the macros to + // help future macros to register the plugin. + pub on_event: String, + }} + + impl {struct_name} {{ + pub fn new() -> Self {{ + Self{{ + on_event: \"{event_name}\".to_string(), + }} + }} + }} + + // FIXME: we should have the possibility to take the + // rag type and put in the `RPCCommand` + impl RPCCommand<{state_ty}> for {struct_name} {{ + fn call_void<'c>(&self,{fn_params}) {{ + {fn_body} + }} + }} + + /// now the original function will e the builder function + /// than under the hook call the `new` method of the + /// struct just defined. + fn {fn_name}() -> {struct_name} {{ + {struct_name}::new() + }} +" + ); + + trace!(tracer, "notification method callback {result}"); + + result.parse().unwrap() +} diff --git a/plugin_macros/src/rpc_method.rs b/plugin_macros/src/rpc_method.rs new file mode 100644 index 0000000..32d1a19 --- /dev/null +++ b/plugin_macros/src/rpc_method.rs @@ -0,0 +1,169 @@ +//! Crate to keep the code of the +//! rpc_method proc macro +use std::fmt::{Display, Error, Formatter}; + +use convert_case::{Case, Casing}; + +use kproc_parser::kparser::{DummyTracer, KParserTracer}; +use kproc_parser::kproc_macros::KTokenStream; +use kproc_parser::proc_macro::TokenStream; +use kproc_parser::rust::ast_nodes::{MethodDeclToken, TyToken}; +use kproc_parser::rust::kparser::RustParser; +use kproc_parser::trace; + +use crate::attr_parser::AttributeParser; + +/// Method to generate the RPC call in a string format +struct RPCCall { + /// original name of the rpc method specified + /// by the user + original_name: String, + /// the description of the rpc method + description: String, + /// the usage tips that is use by core lightning to give tips to the user + usage: String, + /// the name of the struct that will be created by the + /// macros by write in the camel case the original_name + struct_name: String, + /// the function name where the macro + /// is operating on + fn_name: String, + /// the function params of the method specified by the user + fn_params: TokenStream, + /// the return type of the function defined. + return_ty: TyToken, + /// the function body of the method specified by the user + /// in the function. + fn_body: TokenStream, + /// plugin state defined by the user + state_ty: String, +} + +/// implementation fo the Display method of the rpc method that give +/// that convert the struct in the function call in a valid rust syntax. +impl Display for RPCCall { + fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), Error> { + write!(formatter, "{}", self.fn_body) + } +} + +/// The struct where the tools darling store the information +/// the macros, so we implement the struct where we want to unmarshall +/// the user information. +struct RPCMethodMacro { + /// rpc_name is the name of the plugin that the user want to use + /// regarding the RPC method registered by the plugin. + rpc_name: String, + /// description is the short description that the user want to add + /// to the rpc method. + description: String, + /// usage is some tips to give the user on how t use the rpc method + usage: String, + // FIXME: add the long description +} + +/// core parse function that take in input the TokenStream and return +/// in output the generated token stream. +pub(crate) fn parse(attr: TokenStream, item: TokenStream) -> TokenStream { + let tracer = DummyTracer {}; + let parser = RustParser::new(); + let fun_ast = parser.parse_fn(&item); + + trace!(tracer, "attrs: {:?}", attr); + let mut attr = KTokenStream::new(&attr); + let parser = AttributeParser::parse(&mut attr, &tracer); + if let Err(err) = parser { + err.emit(); + panic!(); + } + + let attrs = parser.unwrap(); + + let attr = RPCMethodMacro { + rpc_name: attrs.get("rpc_name").unwrap().to_owned(), + description: attrs.get("description").unwrap().to_owned(), + usage: "".to_owned(), + }; + let meta = generate_method_call(attr, fun_ast); + generate_rpc_method(meta, &tracer) +} + +/// helper method to generator the RPCCall struct and make the code more readable and cleaner. +fn generate_method_call(rpc: RPCMethodMacro, fun_dec: MethodDeclToken) -> RPCCall { + let Some((_, ty)) = fun_dec.params.first() else { + panic!("TODO: we need to return an error, but for now the list of params is empity"); + }; + let Some(ty) = ty.generics.clone().and_then(|gen| gen.first().cloned()) else { + panic!("TODO: we should return an error, but for now the inner ty has no generics"); + }; + RPCCall { + original_name: rpc.rpc_name.to_owned(), + description: rpc.description.to_string(), + usage: rpc.usage.to_string(), + // FIXME: I can use the function name istead? + struct_name: rpc.rpc_name.as_str().to_case(Case::Pascal), + fn_name: fun_dec.ident.to_string(), + fn_params: fun_dec.raw_params, + return_ty: fun_dec.return_ty.unwrap(), + fn_body: fun_dec.raw_body.unwrap(), + state_ty: ty.to_string(), + } +} + +/// helper function to generate the RPC Generator over a generic type +/// to make sure that the user can use the plugin state to build the RPC method. +fn generate_rpc_method(method_call: RPCCall, tracer: &dyn KParserTracer) -> TokenStream { + let struct_name = method_call.struct_name; + let description = method_call.description; + let usage = method_call.usage; + let rpc_name = method_call.original_name; + let fn_params = method_call.fn_params; + let fn_body = method_call.fn_body; + let return_ty = method_call.return_ty; + let state_ty = method_call.state_ty; + let fn_name = method_call.fn_name; + + let code = format!( + " + #[derive(Clone, Default)] + struct {struct_name} {{ + // keep the information added in the macros to + // help future macros to register the plugin. + pub name: String, + pub description: String, + pub long_description: String, + pub usage: String, + }} + + impl {struct_name} {{ + pub fn new() -> Self {{ + Self{{ + name: \"{rpc_name}\".to_string(), + description: \"{description}\".to_string(), + long_description: \"{description}\".to_string(), + usage: \"{usage}\".to_string(), + }} + }} + }} + + + impl RPCCommand<{state_ty}> for {struct_name} {{ + fn call<'c>(&self, {fn_params}) -> {return_ty} {{ + {fn_body} + }} + }} + + + /// now the original function will e the builder function + /// than under the hook call the `new` method of the + /// struct just defined. + fn {fn_name}() -> {struct_name} {{ + {struct_name}::new() + }} +" + ); + + trace!(tracer, "rpc_method: {code}"); + + code.parse().unwrap() +}