801a04802a
expose `UserData` created from Rust manipulate the `rlua::Context` in Rust and return string to load as chunk primarily intended to enable constructing Lua modules in Rust - particularly Lua modules involving `UserData`
237 lines
6.4 KiB
Rust
237 lines
6.4 KiB
Rust
use rlua::{Context, Lua, Table, UserData, UserDataMethods, Value};
|
||
use rlua_searcher::{AddSearcher, Result};
|
||
use std::collections::HashMap;
|
||
use std::fs::File;
|
||
use std::io::Write;
|
||
use std::path::PathBuf;
|
||
|
||
#[test]
|
||
fn add_searcher_works() {
|
||
let lume = read_lume_to_string();
|
||
let name = "lume".to_string();
|
||
let mut map = HashMap::new();
|
||
map.insert(name, lume);
|
||
|
||
let lua = Lua::new();
|
||
|
||
let hello = lua
|
||
.context::<_, Result<String>>(|lua_ctx| {
|
||
lua_ctx.add_searcher(map)?;
|
||
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
|
||
})
|
||
.unwrap();
|
||
|
||
assert_eq!("hello lume", hello);
|
||
}
|
||
|
||
#[test]
|
||
fn add_static_searcher_works() {
|
||
let lume = read_lume_to_str();
|
||
let name = "lume";
|
||
let mut map = HashMap::new();
|
||
map.insert(name, lume);
|
||
|
||
let lua = Lua::new();
|
||
|
||
let hello = lua
|
||
.context::<_, Result<String>>(|lua_ctx| {
|
||
lua_ctx.add_static_searcher(map)?;
|
||
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
|
||
})
|
||
.unwrap();
|
||
|
||
assert_eq!("hello lume", hello);
|
||
}
|
||
|
||
#[test]
|
||
fn add_path_searcher_works() {
|
||
let name = "lume".to_string();
|
||
let path = PathBuf::new()
|
||
.join(std::env::var("CARGO_MANIFEST_DIR").unwrap())
|
||
.join("tests")
|
||
.join("data")
|
||
.join("lume.lua");
|
||
let mut map = HashMap::new();
|
||
map.insert(name, path);
|
||
|
||
let lua = Lua::new();
|
||
|
||
let hello = lua
|
||
.context::<_, Result<String>>(|lua_ctx| {
|
||
lua_ctx.add_path_searcher(map)?;
|
||
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
|
||
})
|
||
.unwrap();
|
||
|
||
assert_eq!("hello lume", hello);
|
||
}
|
||
|
||
#[test]
|
||
fn module_reloading_works() {
|
||
let name = "lume".to_string();
|
||
let path = PathBuf::new()
|
||
.join(std::env::var("CARGO_MANIFEST_DIR").unwrap())
|
||
.join("tests")
|
||
.join("data")
|
||
.join("lume.lua");
|
||
let mut map = HashMap::new();
|
||
map.insert(name.clone(), path.clone());
|
||
|
||
let lua = Lua::new();
|
||
|
||
// Add searcher for lume module on disk, and read from it.
|
||
let hello = lua
|
||
.context::<_, Result<String>>(|lua_ctx| {
|
||
lua_ctx.add_path_searcher(map)?;
|
||
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
|
||
})
|
||
.unwrap();
|
||
|
||
assert_eq!("hello lume", hello);
|
||
|
||
// Twice.
|
||
let hello = lua
|
||
.context::<_, Result<String>>(|lua_ctx| {
|
||
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
|
||
})
|
||
.unwrap();
|
||
|
||
assert_eq!("hello lume", hello);
|
||
|
||
// Modify lume module on disk.
|
||
let mut out = File::create(path.clone()).expect("Could not create Lume module on disk");
|
||
write!(out, "{}\n", r#"return "hello again lume""#)
|
||
.expect("Could not modify Lume module on disk");
|
||
|
||
// Thrice. Should still be unchanged due to caching.
|
||
let hello = lua
|
||
.context::<_, Result<String>>(|lua_ctx| {
|
||
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
|
||
})
|
||
.unwrap();
|
||
|
||
assert_eq!("hello lume", hello);
|
||
|
||
// Remove lume module from Lua’s `package.loaded` cache to facilitate reload.
|
||
lua.context::<_, rlua::Result<()>>(|lua_ctx| {
|
||
let globals = lua_ctx.globals();
|
||
let loaded: Table = globals.get::<_, Table>("package")?.get("loaded")?;
|
||
loaded.set(name.clone(), Value::Nil)
|
||
})
|
||
.unwrap();
|
||
|
||
// Re-read from lume module on disk.
|
||
let hello = lua
|
||
.context::<_, Result<String>>(|lua_ctx| {
|
||
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
|
||
})
|
||
.unwrap();
|
||
|
||
assert_eq!("hello again lume", hello);
|
||
|
||
// Twice.
|
||
let hello = lua
|
||
.context::<_, Result<String>>(|lua_ctx| {
|
||
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
|
||
})
|
||
.unwrap();
|
||
|
||
assert_eq!("hello again lume", hello);
|
||
|
||
// Revert changes to lume module on disk.
|
||
let mut out = File::create(path).expect("Could not create Lume module on disk");
|
||
write!(out, "{}\n", r#"return "hello lume""#).expect("Could not modify Lume module on disk");
|
||
|
||
// Clear cache again.
|
||
lua.context::<_, rlua::Result<()>>(|lua_ctx| {
|
||
let globals = lua_ctx.globals();
|
||
let loaded: Table = globals.get::<_, Table>("package")?.get("loaded")?;
|
||
loaded.set(name, Value::Nil)
|
||
})
|
||
.unwrap();
|
||
|
||
// Ensure changes have been successfully reverted.
|
||
let hello = lua
|
||
.context::<_, Result<String>>(|lua_ctx| {
|
||
Ok(lua_ctx.load(r#"return require("lume")"#).eval()?)
|
||
})
|
||
.unwrap();
|
||
|
||
assert_eq!("hello lume", hello);
|
||
}
|
||
|
||
#[test]
|
||
fn add_closure_searcher_works() {
|
||
let lua = Lua::new();
|
||
|
||
let mut modules: HashMap<
|
||
&'static str,
|
||
Box<dyn Fn(Context) -> 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<String>>(|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()
|
||
}
|
||
|
||
fn read_lume_to_str() -> &'static str {
|
||
r#"return "hello lume""#
|
||
}
|