more efforts to automate rhai bindings
This commit is contained in:
		
							
								
								
									
										187
									
								
								rhai_autobind_macros/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								rhai_autobind_macros/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,187 @@
 | 
			
		||||
use proc_macro::TokenStream;
 | 
			
		||||
use quote::{quote, format_ident};
 | 
			
		||||
use syn::{parse_macro_input, DeriveInput, Ident, Type, Fields, Visibility, ItemStruct, LitStr};
 | 
			
		||||
 | 
			
		||||
/// A procedural macro to automatically generate Rhai bindings for a struct.
 | 
			
		||||
///
 | 
			
		||||
/// This macro will generate an `impl` block for the annotated struct containing
 | 
			
		||||
/// a static method, typically named `register_rhai_bindings_for_<struct_name_lowercase>`,
 | 
			
		||||
/// which registers the struct, its fields (as getters), its methods, and common
 | 
			
		||||
/// database operations with a Rhai `Engine`.
 | 
			
		||||
///
 | 
			
		||||
/// # Example Usage
 | 
			
		||||
///
 | 
			
		||||
/// ```rust,ignore
 | 
			
		||||
/// // Assuming MyDb is your database connection type
 | 
			
		||||
/// // and it's wrapped in an Arc for sharing with Rhai.
 | 
			
		||||
/// use std::sync::Arc;
 | 
			
		||||
/// use rhai::Engine;
 | 
			
		||||
/// use rhai_autobind_macros::rhai_model_export;
 | 
			
		||||
///
 | 
			
		||||
/// // Define your database type (placeholder)
 | 
			
		||||
/// struct MyDb;
 | 
			
		||||
/// impl MyDb {
 | 
			
		||||
///     fn new() -> Self { MyDb }
 | 
			
		||||
/// }
 | 
			
		||||
///
 | 
			
		||||
/// #[rhai_model_export(db_type = "Arc<MyDb>")]
 | 
			
		||||
/// pub struct User {
 | 
			
		||||
///     pub id: i64,
 | 
			
		||||
///     pub name: String,
 | 
			
		||||
/// }
 | 
			
		||||
///
 | 
			
		||||
/// impl User {
 | 
			
		||||
///     // A constructor or builder Rhai can use
 | 
			
		||||
///     pub fn new(id: i64, name: String) -> Self {
 | 
			
		||||
///         Self { id, name }
 | 
			
		||||
///     }
 | 
			
		||||
///
 | 
			
		||||
///     // An example method
 | 
			
		||||
///     pub fn greet(&self) -> String {
 | 
			
		||||
///         format!("Hello, {}!", self.name)
 | 
			
		||||
///     }
 | 
			
		||||
/// }
 | 
			
		||||
///
 | 
			
		||||
/// fn main() {
 | 
			
		||||
///     let mut engine = Engine::new();
 | 
			
		||||
///     let db_instance = Arc::new(MyDb::new());
 | 
			
		||||
///
 | 
			
		||||
///     // The generated function is called here
 | 
			
		||||
///     User::register_rhai_bindings_for_user(&mut engine, db_instance);
 | 
			
		||||
///
 | 
			
		||||
///     // Now you can use User in Rhai scripts
 | 
			
		||||
///     // let user = User::new(1, "Test User");
 | 
			
		||||
///     // print(user.name);
 | 
			
		||||
///     // print(user.greet());
 | 
			
		||||
/// }
 | 
			
		||||
/// ```
 | 
			
		||||
///
 | 
			
		||||
/// # Macro Attributes
 | 
			
		||||
///
 | 
			
		||||
/// - `db_type = "path::to::YourDbType"`: **Required**. Specifies the fully qualified
 | 
			
		||||
///   Rust type of your database connection/handler that will be passed to the
 | 
			
		||||
///   generated registration function and used for database operations.
 | 
			
		||||
///   The type should be enclosed in string quotes, e.g., `"Arc<crate::db::MyDatabase>"`.
 | 
			
		||||
///
 | 
			
		||||
/// - `collection_name = "your_collection_name"`: **Optional**. Specifies the name of the
 | 
			
		||||
///   database collection associated with this model. If not provided, it defaults
 | 
			
		||||
///   to the struct name converted to snake_case and pluralized (e.g., `User` -> `"users"`).
 | 
			
		||||
///   Enclose in string quotes, e.g., `"user_profiles"`.
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct MacroArgs {
 | 
			
		||||
    db_type: syn::Type,
 | 
			
		||||
    collection_name: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl syn::parse::Parse for MacroArgs {
 | 
			
		||||
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
 | 
			
		||||
        let mut db_type_str: Option<LitStr> = None;
 | 
			
		||||
        let mut collection_name_str: Option<LitStr> = None;
 | 
			
		||||
 | 
			
		||||
        while !input.is_empty() {
 | 
			
		||||
            let ident: Ident = input.parse()?;
 | 
			
		||||
            let _eq_token: syn::token::Eq = input.parse()?;
 | 
			
		||||
            if ident == "db_type" {
 | 
			
		||||
                db_type_str = Some(input.parse()?);
 | 
			
		||||
            }
 | 
			
		||||
            else if ident == "collection_name" {
 | 
			
		||||
                collection_name_str = Some(input.parse()?);
 | 
			
		||||
            }
 | 
			
		||||
             else {
 | 
			
		||||
                return Err(syn::Error::new(ident.span(), "Unknown attribute argument"));
 | 
			
		||||
            }
 | 
			
		||||
            if !input.is_empty() {
 | 
			
		||||
                let _comma: syn::token::Comma = input.parse()?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let db_type_str = db_type_str.ok_or_else(|| syn::Error::new(input.span(), "Missing required attribute `db_type`"))?;
 | 
			
		||||
        let db_type: syn::Type = syn::parse_str(&db_type_str.value())?;
 | 
			
		||||
        let collection_name = collection_name_str.map(|s| s.value());
 | 
			
		||||
 | 
			
		||||
        Ok(MacroArgs { db_type, collection_name })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[proc_macro_attribute]
 | 
			
		||||
pub fn rhai_model_export(attr: TokenStream, item: TokenStream) -> TokenStream {
 | 
			
		||||
    let macro_args = parse_macro_input!(attr as MacroArgs);
 | 
			
		||||
    let input_struct = parse_macro_input!(item as ItemStruct);
 | 
			
		||||
 | 
			
		||||
    let struct_name = &input_struct.ident;
 | 
			
		||||
    let struct_name_str = struct_name.to_string();
 | 
			
		||||
    let struct_name_lowercase = struct_name_str.to_lowercase();
 | 
			
		||||
 | 
			
		||||
    let db_type = macro_args.db_type;
 | 
			
		||||
    let collection_name = macro_args.collection_name.unwrap_or_else(|| {
 | 
			
		||||
        // Basic pluralization, could use `heck` or `inflector` for better results
 | 
			
		||||
        if struct_name_lowercase.ends_with('y') {
 | 
			
		||||
            format!("{}ies", &struct_name_lowercase[..struct_name_lowercase.len()-1])
 | 
			
		||||
        } else if struct_name_lowercase.ends_with('s') {
 | 
			
		||||
            format!("{}es", struct_name_lowercase)
 | 
			
		||||
        } else {
 | 
			
		||||
            format!("{}s", struct_name_lowercase)
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let generated_registration_fn_name = format_ident!("register_rhai_bindings_for_{}", struct_name_lowercase);
 | 
			
		||||
 | 
			
		||||
    // Placeholder for generated getters and method registrations
 | 
			
		||||
    let mut field_getters = Vec::new();
 | 
			
		||||
    if let Fields::Named(fields) = &input_struct.fields {
 | 
			
		||||
        for field in fields.named.iter() {
 | 
			
		||||
            if let (Some(field_name), Visibility::Public(_)) = (&field.ident, &field.vis) {
 | 
			
		||||
                let field_name_str = field_name.to_string();
 | 
			
		||||
                let getter_fn = quote! {
 | 
			
		||||
                    engine.register_get(#field_name_str, |s: &mut #struct_name| s.#field_name.clone());
 | 
			
		||||
                    // TODO: Handle non-cloneable types or provide options
 | 
			
		||||
                };
 | 
			
		||||
                field_getters.push(getter_fn);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO: Parse methods from the struct's impl blocks (this is complex)
 | 
			
		||||
    // TODO: Generate wrappers for DB operations using rhai_wrapper macros
 | 
			
		||||
 | 
			
		||||
    let output = quote! {
 | 
			
		||||
        #input_struct // Re-emit the original struct definition
 | 
			
		||||
 | 
			
		||||
        impl #struct_name {
 | 
			
		||||
            pub fn #generated_registration_fn_name(
 | 
			
		||||
                engine: &mut rhai::Engine,
 | 
			
		||||
                db: #db_type
 | 
			
		||||
            ) {
 | 
			
		||||
                // Bring rhai_wrapper into scope for generated DB functions later
 | 
			
		||||
                // Users might need to ensure rhai_wrapper is a dependency of their crate.
 | 
			
		||||
                // use rhai_wrapper::*;
 | 
			
		||||
 | 
			
		||||
                engine.build_type::<#struct_name>();
 | 
			
		||||
 | 
			
		||||
                // Register getters
 | 
			
		||||
                #(#field_getters)*
 | 
			
		||||
 | 
			
		||||
                // Placeholder for constructor registration
 | 
			
		||||
                // Example: engine.register_fn("new_user", User::new);
 | 
			
		||||
 | 
			
		||||
                // Placeholder for method registration
 | 
			
		||||
                // Example: engine.register_fn("greet", User::greet);
 | 
			
		||||
 | 
			
		||||
                // Placeholder for DB function registrations
 | 
			
		||||
                // e.g., get_by_id, get_all, save, delete
 | 
			
		||||
                // let get_by_id_fn_name = format!("get_{}_by_id", #struct_name_lowercase);
 | 
			
		||||
                // engine.register_fn(&get_by_id_fn_name, ...wrap_option_method_result!(...));
 | 
			
		||||
 | 
			
		||||
                println!("Registered {} with Rhai (collection: '{}') using DB type {}", 
 | 
			
		||||
                    #struct_name_str, 
 | 
			
		||||
                    #collection_name, 
 | 
			
		||||
                    stringify!(#db_type)
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    TokenStream::from(output)
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user