diff --git a/src/searcher.rs b/src/searcher.rs index fcdc31f..bf2d8b2 100644 --- a/src/searcher.rs +++ b/src/searcher.rs @@ -128,6 +128,54 @@ where } } +/// Like `StaticSearcher`, but with closures as `modules` values, to facilitate setting +/// up an `rlua::Context` with Rust code. +/// +/// Enables exposing `UserData` types to an `rlua::Context`. +/// +/// Closures must return an `rlua::Result`-wrapped `&'static str`. This string is +/// subsequently passed into `rlua::Context.load()` and evaluated. +pub struct ClosureSearcher { + /// Closures must accept an `rlua::Context` as their only parameter, and can do with + /// it what they wish. Closures must return an `rlua::Result`-wrapped `&'static str`. + modules: HashMap<&'static str, Box rlua::Result<&'static str> + Send>>, + + globals: RegistryKey, +} + +impl ClosureSearcher { + pub fn new( + modules: HashMap<&'static str, Box rlua::Result<&'static str> + Send>>, + globals: RegistryKey, + ) -> Self { + Self { modules, globals } + } +} + +impl UserData for ClosureSearcher { + fn add_methods<'lua, M>(methods: &mut M) + where + M: UserDataMethods<'lua, Self>, + { + methods.add_meta_method(MetaMethod::Call, |lua_ctx, this, name: String| { + let name = name.as_str(); + match this.modules.get(name) { + Some(ref loader) => { + let content = loader(lua_ctx)?; + Ok(Value::Function( + lua_ctx + .load(content) + .set_name(name)? + .set_environment(lua_ctx.registry_value::(&this.globals)?)? + .into_function()?, + )) + } + None => Ok(Value::Nil), + } + }); + } +} + /// Extend `rlua::Context` to support `require`ing Lua modules by name. pub trait AddSearcher { /// Add a `HashMap` of Lua modules indexed by module name to Lua’s @@ -154,6 +202,13 @@ pub trait AddSearcher { ) -> Result<()> where P: 'static + AsRef + Send; + + /// Like `add_static_searcher`, but with user-provided closure for `rlua::Context` + /// setup. + fn add_closure_searcher( + &self, + modules: HashMap<&'static str, Box rlua::Result<&'static str> + Send>>, + ) -> Result<()>; } impl<'a> AddSearcher for Context<'a> { @@ -208,4 +263,17 @@ impl<'a> AddSearcher for Context<'a> { .set(searchers.len()? + 1, searcher) .map_err(|e| e.into()) } + + fn add_closure_searcher( + &self, + modules: HashMap<&'static str, Box rlua::Result<&'static str> + Send>>, + ) -> Result<()> { + let globals = self.globals(); + let searchers: Table = globals.get::<_, Table>("package")?.get("searchers")?; + let registry_key = self.create_registry_value(globals)?; + let searcher = ClosureSearcher::new(modules, registry_key); + searchers + .set(searchers.len()? + 1, searcher) + .map_err(|e| e.into()) + } } diff --git a/tests/tests.rs b/tests/tests.rs index 5ad705b..51e9bff 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,4 +1,4 @@ -use rlua::{Lua, Table, Value}; +use rlua::{Context, Lua, Table, UserData, UserDataMethods, Value}; use rlua_searcher::{AddSearcher, Result}; use std::collections::HashMap; use std::fs::File; @@ -160,6 +160,73 @@ fn module_reloading_works() { assert_eq!("hello lume", hello); } +#[test] +fn add_closure_searcher_works() { + let lua = Lua::new(); + + let mut modules: HashMap< + &'static str, + Box rlua::Result<&'static str> + Send>, + > = HashMap::new(); + + let instrument_loader = Box::new(|lua_ctx: Context| { + let globals = lua_ctx.globals(); + let new = lua_ctx.create_function(|_, (name, sound): (String, String)| { + Ok(Instrument::new(name, sound)) + })?; + let tbl = lua_ctx.create_table()?; + tbl.set("new", new)?; + globals.set("instrument", tbl)?; + Ok("return instrument") + }); + + modules.insert("instrument", instrument_loader); + + let sound = lua + .context::<_, Result>(|lua_ctx| { + lua_ctx.add_closure_searcher(modules)?; + + // Ensure global variable `instrument` is unset. + let nil: String = lua_ctx.load("return type(instrument)").eval()?; + assert_eq!(nil, "nil"); + + Ok(lua_ctx + .load( + r#"local instrument = require("instrument") + local ukulele = instrument.new("ukulele", "twang") + return ukulele:play()"#, + ) + .eval()?) + }) + .unwrap(); + + assert_eq!(sound, "The ukulele goes twang"); +} + +struct Instrument { + name: String, + sound: String, +} + +impl Instrument { + pub fn new(name: String, sound: String) -> Self { + Self { name, sound } + } + + pub fn play(&self) -> String { + format!("The {} goes {}", self.name, self.sound) + } +} + +impl UserData for Instrument { + fn add_methods<'lua, M>(methods: &mut M) + where + M: UserDataMethods<'lua, Self>, + { + methods.add_method("play", |_, instrument, ()| Ok(instrument.play())); + } +} + fn read_lume_to_string() -> String { r#"return "hello lume""#.to_string() }