- 移除 anyhow,使用 thiserror 定义强类型 Error 枚举 - 支持 #[from] 自动转换 mlua::Error,关键路径保留源码与上下文 - LuaRuntimeBuilder 采用惰性错误收集,简化链式初始化调用 - MirrorRegistry 实现规范化 FromLuaConversionError 错误转换
112 lines
3.9 KiB
Rust
112 lines
3.9 KiB
Rust
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<Table> {
|
|
// 缺失变量视为空字符串,拆分后得到空表(不贡献任何路径段)
|
|
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::<Table>("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::<String>("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<Value> {
|
|
match original_require.call::<Value>(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(())
|
|
}
|