diff --git a/lua_macros/.gitignore b/language_macros/.gitignore similarity index 100% rename from lua_macros/.gitignore rename to language_macros/.gitignore diff --git a/lua_macros/Cargo.toml b/language_macros/Cargo.toml similarity index 90% rename from lua_macros/Cargo.toml rename to language_macros/Cargo.toml index cb754e2..89d759e 100644 --- a/lua_macros/Cargo.toml +++ b/language_macros/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "lua_macros" +name = "language_macros" version = "0.1.0" edition = "2021" diff --git a/language_macros/src/generate/command_enum/mod.rs b/language_macros/src/generate/command_enum/mod.rs new file mode 100644 index 0000000..72d5401 --- /dev/null +++ b/language_macros/src/generate/command_enum/mod.rs @@ -0,0 +1,50 @@ +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::{DeriveInput, Field, Type}; + +use super::{get_input_type_of_bare_fn_field, parse_derive_input_as_named_fields}; + +pub fn command_enum(input: &DeriveInput) -> TokenStream2 { + let named_fields = parse_derive_input_as_named_fields(input); + let fields: TokenStream2 = named_fields + .named + .iter() + .map(|field| turn_struct_fieled_to_enum(field)) + .collect(); + + quote! { + #[derive(Debug)] + pub enum Command { + #fields + } + } +} + +fn turn_struct_fieled_to_enum(field: &Field) -> TokenStream2 { + let field_name = format_ident!( + "{}", + field + .ident + .as_ref() + .expect("These are named fields, it should be Some()") + .to_string() + .from_case(Case::Snake) + .to_case(Case::Pascal) + ); + + let input_type: Option = get_input_type_of_bare_fn_field(field); + + match input_type { + Some(input_type) => { + quote! { + #field_name(#input_type), + } + } + None => { + quote! { + #field_name, + } + } + } +} diff --git a/language_macros/src/generate/lua_wrapper/lua_functions_to_globals/mod.rs b/language_macros/src/generate/lua_wrapper/lua_functions_to_globals/mod.rs new file mode 100644 index 0000000..194fb53 --- /dev/null +++ b/language_macros/src/generate/lua_wrapper/lua_functions_to_globals/mod.rs @@ -0,0 +1,54 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::{DeriveInput, Field}; + +use crate::generate::parse_derive_input_as_named_fields; + +pub fn generate_add_lua_functions_to_globals(input: &DeriveInput) -> TokenStream2 { + let named_fields = parse_derive_input_as_named_fields(input); + let function_adders: TokenStream2 = named_fields + .named + .iter() + .map(|field| generate_function_adder(field)) + .collect(); + + quote! { + pub fn add_lua_functions_to_globals( + lua: mlua::Lua, + tx: tokio::sync::mpsc::Sender, + ) -> mlua::Lua { + lua.set_app_data(tx); + let globals = lua.globals(); + + #function_adders + + drop(globals); + lua + } + } +} + +fn generate_function_adder(field: &Field) -> TokenStream2 { + let field_ident = field + .ident + .as_ref() + .expect("This is should be a named field"); + + let function_ident = format_ident!("wrapped_lua_function_{}", field_ident); + let function_name = field_ident.to_string(); + + quote! { + { + let #function_ident = lua.create_async_function(#field_ident).expect( + &format!( + "The function: `{}` should be defined", + #function_name + ) + ); + + globals.set(#function_name, #function_ident).expect( + "Setting a static global value should work" + ); + } + } +} diff --git a/language_macros/src/generate/lua_wrapper/mod.rs b/language_macros/src/generate/lua_wrapper/mod.rs new file mode 100644 index 0000000..8672d47 --- /dev/null +++ b/language_macros/src/generate/lua_wrapper/mod.rs @@ -0,0 +1,20 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::DeriveInput; + +use crate::generate::lua_wrapper::{ + lua_functions_to_globals::generate_add_lua_functions_to_globals, + rust_wrapper_functions::generate_rust_wrapper_functions, +}; + +mod lua_functions_to_globals; +mod rust_wrapper_functions; + +pub fn lua_wrapper(input: &DeriveInput) -> TokenStream2 { + let add_lua_functions_to_globals = generate_add_lua_functions_to_globals(input); + let rust_wrapper_functions = generate_rust_wrapper_functions(input); + quote! { + #add_lua_functions_to_globals + #rust_wrapper_functions + } +} diff --git a/language_macros/src/generate/lua_wrapper/rust_wrapper_functions/mod.rs b/language_macros/src/generate/lua_wrapper/rust_wrapper_functions/mod.rs new file mode 100644 index 0000000..706e463 --- /dev/null +++ b/language_macros/src/generate/lua_wrapper/rust_wrapper_functions/mod.rs @@ -0,0 +1,176 @@ +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::{ + punctuated::Punctuated, token::Comma, DeriveInput, Field, GenericArgument, Lifetime, Type, +}; + +use crate::generate::{ + get_input_type_of_bare_fn_field, get_return_type_of_bare_fn_field, + parse_derive_input_as_named_fields, +}; + +pub fn generate_rust_wrapper_functions(input: &DeriveInput) -> TokenStream2 { + let named_fields = parse_derive_input_as_named_fields(input); + let wrapped_functions: TokenStream2 = named_fields + .named + .iter() + .map(|field| wrap_lua_function(field)) + .collect(); + + quote! { + #wrapped_functions + } +} + +fn wrap_lua_function(field: &Field) -> TokenStream2 { + let input_type = get_input_type_of_bare_fn_field(field); + let return_type = get_return_type_of_bare_fn_field(field); + + let function_name = field.ident.as_ref().expect("This should be a named field"); + let function_body = get_function_body(field, input_type.is_some(), &return_type); + + let lifetime_args = + get_and_add_lifetimes_form_inputs_and_outputs(input_type.clone(), return_type); + + let input_type = input_type + .unwrap_or(syn::parse(quote! {()}.into()).expect("This is static, it always works")); + + quote! { + async fn #function_name <#lifetime_args>( + lua: &mlua::Lua, + input: #input_type + ) -> Result { + #function_body + } + } +} + +fn get_and_add_lifetimes_form_inputs_and_outputs<'a>( + input_type: Option, + return_type: Option, +) -> Punctuated { + fn get_lifetime_args_from_type<'a>(return_type: syn::Type) -> Option> { + match return_type { + syn::Type::Path(path) => { + let args_to_final_path_segment = &path + .path + .segments + .last() + .expect("The path should have a last segment") + .arguments; + match args_to_final_path_segment { + syn::PathArguments::None => + /* We ignore this case */ + { + None + } + syn::PathArguments::AngleBracketed(angle) => { + let lifetime_args: Vec<_> = angle + .args + .iter() + .filter_map(|arg| { + if let GenericArgument::Lifetime(lifetime) = arg { + Some(lifetime.to_owned()) + } else { + None + } + }) + .collect(); + return Some(lifetime_args); + } + syn::PathArguments::Parenthesized(_) => todo!(), + } + } + _ => todo!(), + } + } + + let mut output: Punctuated = Punctuated::new(); + if let Some(input_type) = input_type { + let lifetime_args = get_lifetime_args_from_type(input_type).unwrap_or(vec![]); + lifetime_args.into_iter().for_each(|arg| output.push(arg)); + } + if let Some(return_type) = return_type { + let lifetime_args = get_lifetime_args_from_type(return_type).unwrap_or(vec![]); + lifetime_args.into_iter().for_each(|arg| output.push(arg)); + } + output +} + +fn get_function_body(field: &Field, has_input: bool, output_type: &Option) -> TokenStream2 { + let command_name = field + .ident + .as_ref() + .expect("These are named fields, it should be Some()") + .to_string() + .from_case(Case::Snake) + .to_case(Case::Pascal); + let command_ident = format_ident!("{}", command_name); + + let send_output = if has_input { + quote! { + Event::CommandEvent( + Command::#command_ident(input.clone()), + Some(callback_tx), + ) + } + } else { + quote! { + Event::CommandEvent( + Command::#command_ident, + Some(callback_tx), + ) + } + }; + + let function_return = if let Some(_) = output_type { + quote! { + let output: mlua::Value = lua.to_value(output).expect("This conversion should (indirectely) be checked at compile time"); + return Ok(output); + } + } else { + quote! { + return Ok(()); + } + }; + let does_function_expect_output = if output_type.is_some() { + quote! { + // We didn't receive output but expected output. Raise an error to notify the lua code + // about it + return Err(mlua::Error::ExternalError(std::sync::Arc::new( + err + ))); + } + } else { + quote! { + // We didn't receive output and didn't expect output. Everything went well! + return Ok(()); + } + }; + + quote! { + let (callback_tx, callback_rx) = tokio::sync::oneshot::channel::(); + let tx: core::cell::Ref> = + lua.app_data_ref().expect("This should exist, it was set before"); + + (*tx) + .send(#send_output) + .await + .expect("This should work, as the receiver is not dropped"); + + cli_log::info!("Sent CommandEvent: `{}`", #command_name); + + match callback_rx.await { + Ok(output) => { + cli_log::info!( + "Lua function: `{}` returned output to lua: `{}`", #command_name, &output + ); + #function_return + }, + Err(err) => { + #does_function_expect_output + } + }; + } +} diff --git a/language_macros/src/generate/mod.rs b/language_macros/src/generate/mod.rs new file mode 100644 index 0000000..972bbf8 --- /dev/null +++ b/language_macros/src/generate/mod.rs @@ -0,0 +1,65 @@ +mod command_enum; +mod lua_wrapper; + +pub use command_enum::command_enum; +pub use lua_wrapper::lua_wrapper; +use syn::{DeriveInput, Field, FieldsNamed, ReturnType, Type, TypeBareFn}; + +pub fn parse_derive_input_as_named_fields(input: &DeriveInput) -> FieldsNamed { + match &input.data { + syn::Data::Struct(input) => match &input.fields { + syn::Fields::Named(named_fields) => named_fields, + _ => unimplemented!("The macro only works for named fields (e.g.: `Name: Type`)"), + }, + _ => unimplemented!("The macro only works for structs"), + } + .to_owned() +} + +pub fn get_bare_fn_input_type(function: &TypeBareFn) -> Option { + if function.inputs.len() == 1 { + Some( + function + .inputs + .first() + .expect("Only one element exists, we checked the length above") + .ty + .clone(), + ) + } else if function.inputs.len() == 0 { + // No inputs, so we can't return a type + None + } else { + unreachable!( + "The Function can only take one or zero arguments. + Use a tuple `(arg1, arg2)` if you want more" + ); + } +} + +pub fn get_input_type_of_bare_fn_field(field: &Field) -> Option { + match &field.ty { + syn::Type::BareFn(function) => get_bare_fn_input_type(&function), + _ => unimplemented!( + "Please specify the type as a bare fn type. + That is: `fn() -> `" + ), + } +} +pub fn get_return_type_of_bare_fn_field(field: &Field) -> Option { + match &field.ty { + syn::Type::BareFn(function) => get_bare_fn_return_type(&function), + _ => unimplemented!( + "Please specify the type as a bare fn type. + That is: `fn() -> `" + ), + } +} + +pub fn get_bare_fn_return_type(function: &TypeBareFn) -> Option { + let return_path: &ReturnType = &function.output; + match return_path { + ReturnType::Default => None, + ReturnType::Type(_, return_type) => Some(*return_type.to_owned()), + } +} diff --git a/language_macros/src/lib.rs b/language_macros/src/lib.rs new file mode 100644 index 0000000..93ac287 --- /dev/null +++ b/language_macros/src/lib.rs @@ -0,0 +1,93 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::DeriveInput; + +mod generate; + +/// This is the heart of the command api +/// It mainly does two things: +/// - Generate a command enum +/// - Wrap the enum in all supported languages (only lua for now) +/// - Generate wrapper lua function for each command +/// - Generate a `add_lua_functions_to_globals` function, which adds +/// the rust wrapper functions to the lua globals. +/// +/// The input and output values of the wrapped functions are derived from the values specified in +/// the `Commands` struct. +/// The returned values will be returned directly to the lua context, this allows to nest functions. +/// +/// For example this rust code: +/// ```rust +/// #[ci_command_enum] +/// struct Commands { +/// /// Greets the user +/// greet: fn(String) -> String, +/// } +/// ``` +/// results in this expanded code: +/// ```rust +/// #[derive(Debug)] +/// pub enum Command { +/// Greet(String), +/// } +/// pub fn add_lua_functions_to_globals( +/// lua: mlua::Lua, +/// tx: tokio::sync::mpsc::Sender, +/// ) -> mlua::Lua { +/// lua.set_app_data(tx); +/// let globals = lua.globals(); +/// { +/// let wrapped_lua_function_greet = lua +/// .create_async_function(greet) +/// .expect( +/// format!( +/// "The function: `{}` should be defined", +/// "greet", +/// ) +/// ); +/// globals +/// .set("greet", wrapped_lua_function_greet) +/// .expect("Setting a static global value should work"); +/// } +/// drop(globals); +/// lua +/// } +/// async fn greet(lua: &mlua::Lua, input: String) -> Result { +/// let (callback_tx, callback_rx) = tokio::sync::oneshot::channel::(); +/// let tx: core::cell::Ref> = lua +/// .app_data_ref() +/// .expect("This should exist, it was set before"); +/// (*tx) +/// .send(Event::CommandEvent(Command::Greet(input.clone()), Some(callback_tx))) +/// .await +/// .expect("This should work, as the receiver is not dropped"); +/// match callback_rx.await { +/// Ok(output) => { +/// return Ok(output); +/// } +/// Err(err) => { +/// return Err(mlua::Error::ExternalError(std::sync::Arc::new(err))); +/// } +/// }; +/// } +/// ``` +#[proc_macro_attribute] +pub fn ci_command_enum(_attrs: TokenStream, input: TokenStream) -> TokenStream { + // Construct a representation of Rust code as a syntax tree + // that we can manipulate + let input: DeriveInput = syn::parse(input) + .expect("This should always be valid rust code, as it's extracted from direct code"); + + // Build the language wrappers + let lua_wrapper: TokenStream2 = generate::lua_wrapper(&input); + + // Build the final enum + let command_enum = generate::command_enum(&input); + + quote! { + #command_enum + #lua_wrapper + } + .into() +} diff --git a/lua_macros/src/lib.rs b/lua_macros/src/lib.rs deleted file mode 100644 index 871dd3f..0000000 --- a/lua_macros/src/lib.rs +++ /dev/null @@ -1,69 +0,0 @@ -mod mark_as_ci_command; -mod struct_to_ci_enum; - -use mark_as_ci_command::generate_final_function; -use proc_macro::TokenStream; -use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::{format_ident, quote}; -use struct_to_ci_enum::{generate_command_enum, generate_generate_ci_function, generate_help_function}; -use syn::{self, parse_quote, parse_str, DeriveInput, FieldMutability, ItemFn, Token, Visibility}; - -#[proc_macro_attribute] -pub fn turn_struct_to_ci_command_enum(_attrs: TokenStream, input: TokenStream) -> TokenStream { - // Construct a representation of Rust code as a syntax tree - // that we can manipulate - let mut input: DeriveInput = syn::parse(input).expect("This should always be valid rust code, as it's extracted from direct code"); - - let mut named_fields = match &input.data { - syn::Data::Struct(input) => match &input.fields { - syn::Fields::Named(named_fields) => named_fields, - _ => unimplemented!("The macro only works for named fields (e.g.: `Name: Type`)"), - }, - _ => unimplemented!("The macro only works for structs"), - } - .to_owned(); - - let attr_parsed = parse_quote! { - /// This is a help function - }; - - named_fields.named.push(syn::Field { - attrs: vec![attr_parsed], - // attrs: attr_parser - // .parse("#[doc = r\"This is a help function\"]".to_token_stream().into()) - // .expect("See reason for other one"), - vis: Visibility::Inherited, - mutability: FieldMutability::None, - ident: Some(format_ident!("help")), - colon_token: Some(Token![:](Span::call_site())), - ty: parse_str("fn(Option) -> String").expect("This is static and valid rust code"), - }); - - match &mut input.data { - syn::Data::Struct(input) => input.fields = syn::Fields::Named(named_fields.clone()), - _ => unreachable!("This was a DataStruct before"), - }; - - // Build the trait implementation - let generate_ci_function: TokenStream2 = generate_generate_ci_function(&input); - - let command_enum = generate_command_enum(&named_fields); - - let help_function = generate_help_function(&named_fields); - - quote! { - #command_enum - - #generate_ci_function - - //#help_function - } - .into() -} - -#[proc_macro_attribute] -pub fn ci_command(_attrs: TokenStream, input: TokenStream) -> TokenStream { - let mut input: ItemFn = syn::parse(input).expect("This should always be valid rust code, as it's extracted from direct code"); - let output_function = generate_final_function(&mut input); - output_function.into() -} diff --git a/lua_macros/src/mark_as_ci_command.rs b/lua_macros/src/mark_as_ci_command.rs deleted file mode 100644 index 76e2fc1..0000000 --- a/lua_macros/src/mark_as_ci_command.rs +++ /dev/null @@ -1,173 +0,0 @@ -use convert_case::{Case, Casing}; -use proc_macro2::TokenStream as TokenStream2; -use quote::{format_ident, quote, ToTokens}; -use syn::{Block, Expr, ExprBlock, GenericArgument, ReturnType, Stmt, Type}; - -pub fn generate_final_function(input: &mut syn::ItemFn) -> TokenStream2 { - append_tx_send_code(input); - - let output: TokenStream2 = syn::parse(input.into_token_stream().into()) - .expect("This is generated from valid rust code, it should stay that way."); - - output -} - -fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn { - let function_name_pascal = format_ident!( - "{}", - input - .sig - .ident - .clone() - .to_string() - .from_case(Case::Snake) - .to_case(Case::Pascal) - ); - - let tx_send = match &input.sig.output { - syn::ReturnType::Default => { - unreachable!("All functions should have a output of (Result<$type, rlua::Error>)"); - } - syn::ReturnType::Type(_, ret_type) => { - let return_type = match *(ret_type.clone()) { - syn::Type::Path(path) => { - match path - .path - .segments - .first() - .expect("This is expected to be only one path segment") - .arguments - .to_owned() - { - syn::PathArguments::AngleBracketed(angled_path) => { - let angled_path = angled_path.args.to_owned(); - let filtered_paths: Vec<_> = angled_path - .into_iter() - .filter(|generic_arg| { - if let GenericArgument::Type(generic_type) = generic_arg { - if let Type::Path(_) = generic_type { - true - } else { - false - } - } else { - false - } - }) - .collect(); - - // There should only be two segments (the type is ) - if filtered_paths.len() > 2 { - unreachable!("There should be no more than two filtered_output, but got: {:#?}", filtered_paths) - } else if filtered_paths.len() <= 0 { - unreachable!("There should be more than zero filtered_output, but got: {:#?}", filtered_paths) - } - - if filtered_paths.len() == 2 { - // There is something else than mlua::Error - let gen_type = if let GenericArgument::Type(ret_type) = - filtered_paths - .first() - .expect("One path segment should exists") - .to_owned() - { - ret_type - } else { - unreachable!("These were filtered above."); - }; - let return_type_as_type_prepared = quote! {-> #gen_type}; - - let return_type_as_return_type: ReturnType = syn::parse( - return_type_as_type_prepared.to_token_stream().into(), - ) - .expect("This is valid."); - return_type_as_return_type - } else { - // There is only mlua::Error left - ReturnType::Default - } - } - _ => unimplemented!("Only for angled paths"), - } - } - _ => unimplemented!("Only for path types"), - }; - - let send_data = match return_type { - ReturnType::Default => { - quote! { - { - Event::CommandEvent(Command::#function_name_pascal, None) - } - } - } - ReturnType::Type(_, _) => { - quote! { - { - Event::CommandEvent(Command::#function_name_pascal(input.clone()), Some(callback_tx)) - } - } - } - }; - - let output_return = match return_type { - ReturnType::Default => { - quote! { - { - return Ok(()); - } - } - } - ReturnType::Type(_, _) => { - quote! { - { - if let Some(output) = callback_rx.recv().await { - callback_rx.close(); - return Ok(output); - } else { - return Err(mlua::Error::ExternalError(Arc::new(Error::new( - ErrorKind::Other, - "Callback reciever dropped", - )))); - } - } - } - } - }; - - quote! { - { - let (callback_tx, mut callback_rx) = tokio::sync::mpsc::channel::(256); - - let tx: - core::cell::Ref> = - lua - .app_data_ref() - .expect("This exists, it was set before"); - - (*tx) - .try_send(#send_data) - .expect("This should work, as the reciever is not dropped"); - - #output_return - } - } - } - }; - - let tx_send_block: Block = - syn::parse(tx_send.into()).expect("This is a static string, it will always parse"); - - let tx_send_expr_block = ExprBlock { - attrs: vec![], - label: None, - block: tx_send_block, - }; - let mut tx_send_stmt = vec![Stmt::Expr(Expr::Block(tx_send_expr_block), None)]; - - let mut new_stmts: Vec = Vec::with_capacity(input.block.stmts.len() + 1); - new_stmts.append(&mut tx_send_stmt); - new_stmts.append(&mut input.block.stmts); - input.block.stmts = new_stmts; - input -} diff --git a/lua_macros/src/struct_to_ci_enum/generate_command_enum.rs b/lua_macros/src/struct_to_ci_enum/generate_command_enum.rs deleted file mode 100644 index 8e5fd7e..0000000 --- a/lua_macros/src/struct_to_ci_enum/generate_command_enum.rs +++ /dev/null @@ -1,65 +0,0 @@ -use convert_case::{Case, Casing}; -use proc_macro2::TokenStream as TokenStream2; -use quote::{format_ident, quote}; -use syn::Type; - -pub fn generate_command_enum(input: &syn::FieldsNamed) -> TokenStream2 { - let input_tokens: TokenStream2 = input - .named - .iter() - .map(|field| -> TokenStream2 { - let field_ident = field - .ident - .as_ref() - .expect("These are only the named fields, thus they should all have a ident."); - - let enum_variant_type = match &field.ty { - syn::Type::BareFn(function) => { - let return_path = &function.inputs; - - let input_type: Option = if return_path.len() == 1 { - Some( - return_path - .last() - .expect("The last element exists") - .ty - .clone(), - ) - } else if return_path.len() == 0 { - None - } else { - panic!("The Function can only take on argument, or none"); - }; - input_type - } - _ => unimplemented!("This is only implemented for bare function types"), - }; - - let enum_variant_name = format_ident!( - "{}", - field_ident - .to_string() - .from_case(Case::Snake) - .to_case(Case::Pascal) - ); - if enum_variant_type.is_some() { - quote! { - #enum_variant_name (#enum_variant_type), - } - .into() - } else { - quote! { - #enum_variant_name, - } - } - }) - .collect(); - - let gen = quote! { - #[derive(Debug)] - pub enum Command { - #input_tokens - } - }; - gen.into() -} diff --git a/lua_macros/src/struct_to_ci_enum/generate_generate_ci_function.rs b/lua_macros/src/struct_to_ci_enum/generate_generate_ci_function.rs deleted file mode 100644 index 3c690ec..0000000 --- a/lua_macros/src/struct_to_ci_enum/generate_generate_ci_function.rs +++ /dev/null @@ -1,105 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::{format_ident, quote}; -use syn::{parse_quote, ReturnType, Type}; - -fn generate_ci_function_exposure(field: &syn::Field) -> TokenStream2 { - let field_ident = field - .ident - .as_ref() - .expect("These are only the named field, thus they all should have a name."); - - let function_name_ident = format_ident!("fun_{}", field_ident); - let function_name = format!("{}", field_ident); - quote! { - let #function_name_ident = lua.create_async_function(#field_ident).expect( - &format!( - "The function: `{}` should be defined", - #function_name - ) - ); - - globals.set(#function_name, #function_name_ident).expect( - &format!( - "Setting a static global value ({}, fun_{}) should work", - #function_name, - #function_name - ) - ); - } - .into() -} - -pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 { - let mut functions_to_generate: Vec = vec![]; - - let functions_to_export_in_lua: TokenStream2 = match &input.data { - syn::Data::Struct(input) => match &input.fields { - syn::Fields::Named(named_fields) => named_fields - .named - .iter() - .map(|field| -> TokenStream2 { - let input_type = match &field.ty { - syn::Type::BareFn(bare_fn) => { - if bare_fn.inputs.len() == 1 { - bare_fn.inputs.last().expect("The last element exists").ty.clone() - } else if bare_fn.inputs.len() == 0 { - let input_type: Type = parse_quote! {()}; - input_type - } else { - panic!("The Function can only take on argument, or none"); - } - } - _ => unimplemented!("This is only implemented for bare function types"), - }; - let return_type = match &field.ty { - syn::Type::BareFn(function) => { - let return_path: &ReturnType = &function.output; - match return_path { - ReturnType::Default => None, - ReturnType::Type(_, return_type) => Some(return_type.to_owned()) } - } - _ => unimplemented!("This is only implemented for bare function types"), - }; - - let function_name = field - .ident - .as_ref() - .expect("These are only the named field, thus they all should have a name."); - - if let Some(ret_type) = return_type { - functions_to_generate.push(quote! { - #[ci_command] - async fn #function_name(lua: &mlua::Lua, input: #input_type) -> Result<#ret_type, mlua::Error> { - } - }); - } else { - functions_to_generate.push(quote! { - #[ci_command] - async fn #function_name(lua: &mlua::Lua, input: #input_type) -> Result<(), mlua::Error> { - } - }); - } - generate_ci_function_exposure(field) - }) - .collect(), - - _ => unimplemented!("Only implemented for named fileds"), - }, - _ => unimplemented!("Only implemented for structs"), - }; - - let functions_to_generate: TokenStream2 = functions_to_generate.into_iter().collect(); - let gen = quote! { - pub fn generate_ci_functions( - lua: &mut mlua::Lua, - tx: tokio::sync::mpsc::Sender - ) - { - lua.set_app_data(tx); - let globals = lua.globals(); - #functions_to_export_in_lua - } - #functions_to_generate - }; - gen.into() -} diff --git a/lua_macros/src/struct_to_ci_enum/generate_help_function.rs b/lua_macros/src/struct_to_ci_enum/generate_help_function.rs deleted file mode 100644 index 2987272..0000000 --- a/lua_macros/src/struct_to_ci_enum/generate_help_function.rs +++ /dev/null @@ -1,53 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::{quote, ToTokens}; - -pub fn generate_help_function(input: &syn::FieldsNamed) -> TokenStream2 { - let input: Vec<_> = input.named.iter().collect(); - - let combined_help_text: TokenStream2 = input - .iter() - .map(|field| { - let attrs_with_doc: Vec = field - .attrs - .iter() - .filter_map(|attr| { - if attr.path().is_ident("doc") { - let help_text = attr - .meta - .require_name_value() - .expect("This is a named value type, because all doc comments work this way") - .value - .clone(); - Some(help_text.into_token_stream().into()) - } else { - return None; - } - }) - .collect(); - if attrs_with_doc.len() == 0 { - // TODO there should be a better panic function, than the generic one - panic!( - "The command named: `{}`, does not provide a help message", - field.ident.as_ref().expect("These are all named") - ); - } else { - let help_text_for_one_command_combined: TokenStream2 = attrs_with_doc.into_iter().collect(); - return help_text_for_one_command_combined; - } - }) - .collect(); - - quote! { - #[ci_command] - async fn help( - lua: &mlua::Lua, - input_str: Option - ) -> Result { - // TODO add a way to filter the help based on the input - - let output = "These functions exist:\n"; - output.push_str(#combined_help_text); - Ok(output) - } - } -} diff --git a/lua_macros/src/struct_to_ci_enum/mod.rs b/lua_macros/src/struct_to_ci_enum/mod.rs deleted file mode 100644 index 4d93f98..0000000 --- a/lua_macros/src/struct_to_ci_enum/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod generate_command_enum; -pub mod generate_generate_ci_function; -pub mod generate_help_function; - -pub use generate_command_enum::*; -pub use generate_generate_ci_function::*; -pub use generate_help_function::*;