rlua-searcher/src/searcher.rs

308 lines
10 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use rlua::prelude::LuaError;
use rlua::{Context, Function, MetaMethod, RegistryKey, Table, UserData, UserDataMethods, Value};
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.
modules: HashMap<String, String>,
/// 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<String, String>, globals: RegistryKey) -> Self {
Self { modules, globals }
}
}
impl UserData for Searcher {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Call, |lua_ctx, this, name: String| {
match this.modules.get(&name) {
Some(content) => Ok(Value::Function(
lua_ctx
.load(content)
.set_name(&name)?
.set_environment(lua_ctx.registry_value::<Table>(&this.globals)?)?
.into_function()?,
)),
None => Ok(Value::Nil),
}
});
}
}
/// Like `Searcher`, but with `modules` keys and values encoded as `&'static str`
/// to facilitate compile-time includes of Lua source code.
struct StaticSearcher {
modules: HashMap<&'static str, &'static str>,
globals: RegistryKey,
}
impl StaticSearcher {
fn new(modules: HashMap<&'static str, &'static str>, globals: RegistryKey) -> Self {
Self { modules, globals }
}
}
impl UserData for StaticSearcher {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Call, |lua_ctx, this, name: String| {
match this.modules.get(name.as_str()) {
Some(content) => Ok(Value::Function(
lua_ctx
.load(content)
.set_name(&name)?
.set_environment(lua_ctx.registry_value::<Table>(&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<P>
where
P: 'static + AsRef<Path> + Send,
{
modules: HashMap<String, P>,
globals: RegistryKey,
/// Function to read file content as Lua source code.
transform: Box<dyn Fn(PathBuf) -> rlua::Result<String> + Send>,
}
impl<P> PathSearcherPoly<P>
where
P: 'static + AsRef<Path> + Send,
{
fn new(
modules: HashMap<String, P>,
globals: RegistryKey,
transform: Box<dyn Fn(PathBuf) -> rlua::Result<String> + Send>,
) -> Self {
Self {
modules,
globals,
transform,
}
}
}
impl<P> UserData for PathSearcherPoly<P>
where
P: 'static + AsRef<Path> + Send,
{
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Call, |lua_ctx, this, name: String| {
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)?
.set_environment(lua_ctx.registry_value::<Table>(&this.globals)?)?
.into_function()?,
))
}
None => Ok(Value::Nil),
}
});
}
}
/// 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`.
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. Luas `_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<
&'static str,
Box<
dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result<Function<'ctx>>
+ Send,
>,
>,
globals: RegistryKey,
}
impl ClosureSearcher {
pub fn new(
modules: HashMap<
&'static str,
Box<
dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result<Function<'ctx>>
+ 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 = name.as_str();
match this.modules.get(name) {
Some(ref closure) => Ok(Value::Function(closure(
lua_ctx,
lua_ctx.registry_value::<Table>(&this.globals)?,
name,
)?)),
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 Luas
/// `package.searchers` table in an `rlua::Context`, with lookup functionality
/// provided by the `rlua_searcher::Searcher` struct.
fn add_searcher(&self, modules: HashMap<String, String>) -> Result<()>;
/// Like `add_searcher`, but with Fennel source code encoded as `&'static str`
/// to facilitate compile-time includes.
fn add_static_searcher(&self, modules: HashMap<&'static str, &'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<P>(&self, modules: HashMap<String, P>) -> Result<()>
where
P: 'static + AsRef<Path> + Send;
/// Like `add_path_searcher`, but with user-provided closure for transforming
/// source code to Lua.
fn add_path_searcher_poly<P>(
&self,
modules: HashMap<String, P>,
transform: Box<dyn Fn(PathBuf) -> rlua::Result<String> + Send>,
) -> Result<()>
where
P: 'static + AsRef<Path> + Send;
/// Like `add_static_searcher`, but with user-provided closure for `rlua::Context`
/// setup.
fn add_closure_searcher(
&self,
modules: HashMap<
&'static str,
Box<
dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result<Function<'ctx>>
+ Send,
>,
>,
) -> Result<()>;
}
impl<'a> AddSearcher for Context<'a> {
fn add_searcher(&self, modules: HashMap<String, String>) -> 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_static_searcher(&self, modules: HashMap<&'static str, &'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 = StaticSearcher::new(modules, registry_key);
searchers
.set(searchers.len()? + 1, searcher)
.map_err(|e| e.into())
}
fn add_path_searcher<P>(&self, modules: HashMap<String, P>) -> Result<()>
where
P: 'static + AsRef<Path> + Send,
{
let transform = Box::new(|path| {
let mut content = String::new();
let mut file = File::open(path)
.map_err(|e| LuaError::RuntimeError(format!("io error: {:#?}", e)))?;
file.read_to_string(&mut content)
.map_err(|e| LuaError::RuntimeError(format!("io error: {:#?}", e)))?;
Ok(content)
});
self.add_path_searcher_poly(modules, transform)
}
fn add_path_searcher_poly<P>(
&self,
modules: HashMap<String, P>,
transform: Box<dyn Fn(PathBuf) -> rlua::Result<String> + Send>,
) -> Result<()>
where
P: 'static + AsRef<Path> + 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<
&'static str,
Box<
dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result<Function<'ctx>>
+ 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())
}
}