use crate::error::{Error, Result}; use crate::{Layout, normalize_path_for_lua}; use mlua::{Lua, Table, Value}; use std::env; use tracing::{debug, warn}; const MIRROR_DIR: &str = "__MIRROR_DIR__"; // 采用形式 A:直接传引用的普通函数 pub fn register_basics(lua: &Lua, layout: &Layout) -> Result<()> { let base_dir = normalize_path_for_lua(&layout.base_dir); register_mirror_dir(lua, &base_dir)?; register_get_env(lua)?; setup_require(lua, &base_dir)?; Ok(()) } /// 注入全局锚点变量 pub fn register_mirror_dir(lua: &Lua, base_dir: &str) -> Result<()> { debug!("设置 {MIRROR_DIR} 环境变量"); lua.globals().set(MIRROR_DIR, base_dir)?; // .context(format!("设置 {MIRROR_DIR} 环境变量失败"))?; Ok(()) } ///注入 get_env 供配置读取环境变量 pub fn register_get_env(lua: &Lua) -> Result<()> { let globals = lua.globals(); let get_env = lua.create_function(|lua, key: String| -> mlua::Result { // 缺失变量视为空字符串,拆分后得到空表(不贡献任何路径段) let value = env::var_os(key).unwrap_or_default(); // 空输入返回空表;否则按平台分隔符拆分(split_paths 会剥离引号包裹) if value.is_empty() { return lua.create_table(); } let table = lua.create_table()?; for (i, p) in env::split_paths(&value).enumerate() { // 3. 跨平台提取原始字节并转为 LuaString,保证 100% 无损 #[cfg(unix)] let lua_str = { use std::os::unix::ffi::OsStrExt; lua.create_string(p.as_os_str().as_bytes())? }; #[cfg(windows)] let lua_str = { // Windows 路径是 UTF-16,转成字符串或保持其字节表达 let s = p.to_string_lossy(); lua.create_string(s.as_bytes())? }; table.set(i + 1, lua_str)?; } Ok(table) })?; // .context("注册 get_env 函数失败")?; globals.set("get_env", get_env)?; // .context("挂载 get_env 全局函数失败")?; Ok(()) } /// 配置 package.path 并包装 require,拦截加载失败以提高容错性 pub fn setup_require(lua: &Lua, base_dir: &str) -> Result<()> { let globals = lua.globals(); // 1. 安全加固并拓展 package 搜索路径 let package = globals.get::
("package")?; let _ = package.set("cpath", ""); let _ = package.set("loadlib", Value::Nil); let commands_dir = format!("{}/commands", base_dir); let vendor_dir = format!("{}/vendor", base_dir); let path = package.get::("path")?; let new_path = format!( "{};{}/?.lua;{}/?/init.lua;{}/?.lua;{}/?/init.lua;{}/?.lua;{}/?/init.lua;", path, base_dir, base_dir, commands_dir, commands_dir, vendor_dir, vendor_dir ); debug!("重新写入的 package 查找路径"); package.set("path", new_path)?; // 2. 获取原生 require 并通过闭包直接持有(无需向全局表注入备份变量) let original_require: mlua::Function = globals.get("require")?; // .context("获取内置 require 函数失败")?; let wrapped_require = lua.create_function(move |_lua, module: String| -> mlua::Result { match original_require.call::(module.as_str()) { Ok(value) => Ok(value), Err(e) => { warn!( module = %module, error = %e, "配置模块加载失败,已跳过该条目(可在独立配置文件中定义)" ); Ok(Value::Nil) } } })?; // .context("创建包装版 require 函数失败")?; // 3. 覆盖全局 require globals.set("require", wrapped_require)?; // .context("重载 require 函数失败")?; Ok(()) }