use rlua::{Context, Function, 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>(|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>(|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>(|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>(|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>(|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>(|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>(|lua_ctx| { Ok(lua_ctx.load(r#"return require("lume")"#).eval()?) }) .unwrap(); assert_eq!("hello again lume", hello); // Twice. let hello = lua .context::<_, Result>(|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>(|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< String, Box< dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result> + Send, >, > = HashMap::new(); let instrument_loader: Box< dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result> + Send, > = Box::new(|lua_ctx, env, name| { 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(lua_ctx .load("return instrument") .set_name(name)? .set_environment(env)? .into_function()?) }); modules.insert("instrument".to_string(), 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"); } #[test] fn add_static_closure_searcher_works() { let lua = Lua::new(); let mut modules: HashMap< &'static str, Box< dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result> + Send, >, > = HashMap::new(); let instrument_loader: Box< dyn for<'ctx> Fn(Context<'ctx>, Table<'ctx>, &str) -> rlua::Result> + Send, > = Box::new(|lua_ctx, env, name| { 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(lua_ctx .load("return instrument") .set_name(name)? .set_environment(env)? .into_function()?) }); modules.insert("instrument", instrument_loader); let sound = lua .context::<_, Result>(|lua_ctx| { lua_ctx.add_static_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""# }