use std::fs::DirEntry; use log::{debug, Record}; use mlua::{Function, FromLua, Lua, Table, Value}; pub struct Plugin<'lua> { pub table: Table<'lua>, pub id: String, } impl<'lua> FromLua<'lua> for Plugin<'lua> { fn from_lua(lua_value: Value<'lua>, lua: &'lua Lua) -> mlua::Result { let table = Table::from_lua(lua_value, lua)?; Ok(Plugin { id: table.get("id")?, table, }) } } fn lua_locinfo(lua: &Lua) -> Option<(u32, String)> { let debug = lua.globals().get::<_, Table>("debug").ok()?; let getinfo = debug.get::<_, Function>("getinfo").ok()?; let data = getinfo.call::<_, Table>((2,)).ok()?; let line = data.get("currentline").ok()?; let file = data.get("short_src").ok()?; Some((line, file)) } fn lua_log(level: log::Level, path: &'static str, msg: String, lua: &Lua) { let mut record = Record::builder(); record.level(level); record.target(path); record.module_path_static(Some(path)); let locinfo = lua_locinfo(lua); if let Some((line, file)) = &locinfo { record.line(Some(*line)); record.file(Some(file)); } log::logger().log(&record.args(format_args!("{}", msg)).build()); } const LEVELS: [(&str, log::Level); 5] = [ ("trace", log::Level::Trace), ("debug", log::Level::Debug), ("info", log::Level::Info), ("warn", log::Level::Warn), ("error", log::Level::Error), ]; impl<'lua> Plugin<'lua> { pub fn load(entry: &DirEntry, lua: &'lua Lua) -> anyhow::Result { let ty = entry.file_type()?; let mut path = entry.path(); if ty.is_dir() { path.push("main.lua"); } let chunk = lua.load(&path); debug!("evaluating plugin"); let plugin = Self::from_lua(chunk.eval()?, lua)?; // leak: plugins are only loaded at the beginning of the program // and the path needs to live forever anyway, so this is fine let path: &'static str = Box::leak(format!("qcplugin::{}", plugin.id).into_boxed_str()); for (name, level) in LEVELS { plugin.table.set(name, lua.create_function(move |lua, (msg,): (String,)| { lua_log(level, path, msg, lua); Ok(()) })?)?; } Ok(plugin) } }