- 将核心业务逻辑抽离至 lib.rs,精简 main.rs 为纯粹的 CLI 入口 - 将代码按职责拆分为 config、env、runtime、shim 等子模块 - 提高可测试性与代码复用度
87 lines
3.1 KiB
Rust
87 lines
3.1 KiB
Rust
use crate::error::ShimError;
|
||
use crate::{ShimConfig, ShimEnv};
|
||
use mlua::{FromLua, Lua, StdLib, Table, Value};
|
||
use std::path::Path;
|
||
use std::{env, fs};
|
||
/// 将 Path 转换为适合 Lua 使用的安全字符串路径
|
||
fn normalize_path_for_lua(path: &Path) -> String {
|
||
let path_str = path.to_string_lossy();
|
||
|
||
// 1. 剥离 Windows UNC 规范路径前缀 (\\?\)
|
||
let clean_str = path_str.strip_prefix(r"\\?\").unwrap_or(&path_str);
|
||
|
||
// 2. 将反斜杠转换成正斜杠(在非 UNC 路径下,Windows 和 Lua 均完美支持 /)
|
||
// 这样既避免了 Lua 字符串转义隐患,又不会破坏 Windows 路径
|
||
clean_str.replace('\\', "/")
|
||
}
|
||
|
||
pub struct LuaRuntime {
|
||
lua: Lua,
|
||
}
|
||
|
||
impl LuaRuntime {
|
||
/// 初始化限定权限的 Lua 沙箱环境
|
||
pub fn new(shim_env: &ShimEnv) -> Result<Self, ShimError> {
|
||
// 只加载安全的标准库,剥离 os / io 等风险模块
|
||
let lua = Lua::new_with(
|
||
StdLib::TABLE | StdLib::STRING | StdLib::MATH | StdLib::PACKAGE,
|
||
mlua::LuaOptions::default(),
|
||
)
|
||
.map_err(|e| ShimError::EnvError(format!("初始化 Lua 失败: {}", e)))?;
|
||
|
||
let globals = lua.globals();
|
||
|
||
// 统一使用 POSIX 风格路径规范化路径字符串
|
||
let root_dir_str = normalize_path_for_lua(&shim_env.root_dir);
|
||
let tools_dir_str = normalize_path_for_lua(&shim_env.tools_dir);
|
||
|
||
// 1. 注入锚点变量
|
||
globals
|
||
.set("__SHIM_DIR__", root_dir_str.clone())
|
||
.map_err(|e| ShimError::EnvError(e.to_string()))?;
|
||
|
||
// 2. 安全暴露 get_env 供配置读取环境变量
|
||
let get_env = lua
|
||
.create_function(|_, key: String| -> mlua::Result<String> {
|
||
Ok(env::var(key).unwrap_or_default())
|
||
})
|
||
.map_err(|e| ShimError::EnvError(e.to_string()))?;
|
||
|
||
globals
|
||
.set("get_env", get_env)
|
||
.map_err(|e| ShimError::EnvError(e.to_string()))?;
|
||
|
||
// 3. 配置 package.path,确保 require 行为正常
|
||
if let Ok(package) = globals.get::<Table>("package") {
|
||
if let Ok(path) = package.get::<String>("path") {
|
||
let new_path = format!(
|
||
"{};{}/?.lua;{}/?/init.lua;{}/?.lua;{}/?/init.lua",
|
||
path, root_dir_str, root_dir_str, tools_dir_str, tools_dir_str
|
||
);
|
||
let _ = package.set("path", new_path);
|
||
}
|
||
}
|
||
|
||
Ok(Self { lua })
|
||
}
|
||
|
||
/// 执行指定脚本文件,直接返回完整的 Lua Table
|
||
pub fn evaluate_lua_script(&self, path: &Path) -> Result<Table, ShimError> {
|
||
let code = fs::read_to_string(path)?;
|
||
|
||
self.lua
|
||
.load(&code)
|
||
.set_name(path.to_string_lossy())
|
||
.eval::<Table>()
|
||
.map_err(|e| ShimError::LuaExecutionError {
|
||
file: path.display().to_string(),
|
||
source: e,
|
||
})
|
||
}
|
||
|
||
/// 将 Lua Value 解析转化为 ShimConfig 数据对象
|
||
pub fn parse_config(&self, value: Value) -> Result<ShimConfig, ShimError> {
|
||
ShimConfig::from_lua(value, &self.lua).map_err(|e| ShimError::InvalidConfig(e.to_string()))
|
||
}
|
||
}
|