use rlua::{Context, Function, MetaMethod, RegistryKey, Table, UserData, UserDataMethods, Value}; use std::borrow::Cow; use std::collections::HashMap; use std::fs::File; use std::io::Read; use std::path::{Path, PathBuf}; use crate::types::Result; /// Stores Lua modules indexed by module name, and provides an `rlua::MetaMethod` /// to enable `require`ing the stored modules by name in an `rlua::Context`. struct Searcher { /// A `HashMap` of Lua modules in string representation, indexed by module name. /// /// Uses `Cow<'static, str>` types to allow both `&'static str` and owned `String`. modules: HashMap, Cow<'static, str>>, /// An `rlua::RegistryKey` whose value is the Lua environment within which the /// user made the request to instantiate a `Searcher` for `modules`. globals: RegistryKey, } impl Searcher { fn new(modules: HashMap, Cow<'static, str>>, globals: RegistryKey) -> Self { Self { modules, globals } } } impl UserData for Searcher { 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 = Cow::from(name); match this.modules.get(&name) { Some(content) => { let content = match content { Cow::Borrowed(content) => content, Cow::Owned(content) => content.as_str(), }; Ok(Value::Function( lua_ctx .load(content) .set_name(name.as_ref())? .set_environment(lua_ctx.registry_value::(&this.globals)?)? .into_function()?, )) } None => Ok(Value::Nil), } }); } } /// Like `Searcher`, but with `modules` values given as paths to files the content of /// which can be read as Lua source code. /// /// Facilitates Lua module reloading, and module reloading of any other programming /// language whose source code can be compiled to Lua. struct PathSearcherPoly

where P: 'static + AsRef + Send, { modules: HashMap, P>, globals: RegistryKey, /// Function to read file content as Lua source code. transform: Box rlua::Result + Send>, } impl

PathSearcherPoly

where P: 'static + AsRef + Send, { fn new( modules: HashMap, P>, globals: RegistryKey, transform: Box rlua::Result + Send>, ) -> Self { Self { modules, globals, transform, } } } impl

UserData for PathSearcherPoly

where P: 'static + AsRef + Send, { 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 = Cow::from(name); match this.modules.get(&name) { Some(ref path) => { let path = path.as_ref().to_path_buf(); let content = (this.transform)(path)?; Ok(Value::Function( lua_ctx .load(&content) .set_name(name.as_ref())? .set_environment(lua_ctx.registry_value::

(&this.globals)?)? .into_function()?, )) } None => Ok(Value::Nil), } }); } } /// Like `Searcher`, but with closures as `modules` values, to facilitate setting up an /// `rlua::Context` with Rust code. /// /// Enables exposing `UserData` types to an `rlua::Context`. pub struct ClosureSearcher { /// Closures must accept three parameters: /// /// 1. An `rlua::Context`, which the closure can do what it wants with. /// /// 2. An `rlua::Table` containing globals (i.e. Lua’s `_G`), which can be passed /// to `Chunk.set_environment()`. /// /// 3. The name of the module to be loaded (`&str`). /// /// Closures must return an `rlua::Result`-wrapped `Function`. This `Function` /// acts as the module loader. modules: HashMap< Cow<'static, str>, Box< dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result> + Send, >, >, globals: RegistryKey, } impl ClosureSearcher { pub fn new( modules: HashMap< Cow<'static, str>, Box< dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result> + 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: Context<'lua>, this, name: String| { let name = Cow::from(name); match this.modules.get(&name) { Some(ref closure) => Ok(Value::Function(closure( lua_ctx, lua_ctx.registry_value::
(&this.globals)?, name.as_ref(), )?)), None => Ok(Value::Nil), } }, ); } } /// Like `Searcher`, but with function pointers as `modules` values, to facilitate /// setting up an `rlua::Context` with Rust code. /// /// Enables exposing `UserData` types to an `rlua::Context`. pub struct FunctionSearcher { /// Functions must accept three parameters: /// /// 1. An `rlua::Context`, which the function body can do what it wants with. /// /// 2. An `rlua::Table` containing globals (i.e. Lua’s `_G`), which can be passed /// to `Chunk.set_environment()`. /// /// 3. The name of the module to be loaded (`&str`). /// /// Functions must return an `rlua::Result`-wrapped `Function`. This `Function` /// acts as the module loader. modules: HashMap< Cow<'static, str>, for<'ctx> fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result>, >, globals: RegistryKey, } impl FunctionSearcher { pub fn new( modules: HashMap< Cow<'static, str>, for<'ctx> fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result>, >, globals: RegistryKey, ) -> Self { Self { modules, globals } } } impl UserData for FunctionSearcher { fn add_methods<'lua, M>(methods: &mut M) where M: UserDataMethods<'lua, Self>, { methods.add_meta_method( MetaMethod::Call, |lua_ctx: Context<'lua>, this, name: String| { let name = Cow::from(name); match this.modules.get(&name) { Some(ref function) => Ok(Value::Function(function( lua_ctx, lua_ctx.registry_value::
(&this.globals)?, name.as_ref(), )?)), 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 /// `package.searchers` table in an `rlua::Context`, with lookup functionality /// provided by the `rlua_searcher::Searcher` struct. fn add_searcher(&self, modules: HashMap, Cow<'static, str>>) -> Result<()>; /// Like `add_searcher`, but with `modules` values given as paths to files containing /// Lua source code to facilitate module reloading. fn add_path_searcher

(&self, modules: HashMap, P>) -> Result<()> where P: 'static + AsRef + Send; /// Like `add_path_searcher`, but with user-provided closure for transforming /// source code to Lua. fn add_path_searcher_poly

( &self, modules: HashMap, P>, transform: Box rlua::Result + Send>, ) -> Result<()> where P: 'static + AsRef + Send; /// Like `add_searcher`, but with user-provided closure for `rlua::Context` setup. fn add_closure_searcher( &self, modules: HashMap< Cow<'static, str>, Box< dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result> + Send, >, >, ) -> Result<()>; /// Like `add_searcher`, but with user-provided function for `rlua::Context` setup. fn add_function_searcher( &self, modules: HashMap< Cow<'static, str>, for<'ctx> fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result>, >, ) -> Result<()>; } impl<'a> AddSearcher for Context<'a> { fn add_searcher(&self, modules: HashMap, Cow<'static, str>>) -> Result<()> { let globals = self.globals(); let searchers: Table = globals.get::<_, Table>("package")?.get("searchers")?; let registry_key = self.create_registry_value(globals)?; let searcher = Searcher::new(modules, registry_key); searchers .set(searchers.len()? + 1, searcher) .map_err(|e| e.into()) } fn add_path_searcher

(&self, modules: HashMap, P>) -> Result<()> where P: 'static + AsRef + Send, { let transform = Box::new(|path| { let mut content = String::new(); let mut file = File::open(path) .map_err(|e| rlua::Error::RuntimeError(format!("io error: {:#?}", e)))?; file.read_to_string(&mut content) .map_err(|e| rlua::Error::RuntimeError(format!("io error: {:#?}", e)))?; Ok(content) }); self.add_path_searcher_poly(modules, transform) } fn add_path_searcher_poly

( &self, modules: HashMap, P>, transform: Box rlua::Result + Send>, ) -> Result<()> where P: 'static + AsRef + Send, { let globals = self.globals(); let searchers: Table = globals.get::<_, Table>("package")?.get("searchers")?; let registry_key = self.create_registry_value(globals)?; let searcher = PathSearcherPoly::new(modules, registry_key, transform); searchers .set(searchers.len()? + 1, searcher) .map_err(|e| e.into()) } fn add_closure_searcher( &self, modules: HashMap< Cow<'static, str>, Box< dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result> + 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()) } fn add_function_searcher( &self, modules: HashMap< Cow<'static, str>, for<'ctx> fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result>, >, ) -> Result<()> { let globals = self.globals(); let searchers: Table = globals.get::<_, Table>("package")?.get("searchers")?; let registry_key = self.create_registry_value(globals)?; let searcher = FunctionSearcher::new(modules, registry_key); searchers .set(searchers.len()? + 1, searcher) .map_err(|e| e.into()) } }