From ed5439eaa145ca82c0947ca090d79b74e6ae2f16 Mon Sep 17 00:00:00 2001 From: CNWei Date: Fri, 14 Aug 2026 10:36:09 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=8B=86=E5=88=86=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=BB=93=E6=9E=84=E4=B8=BA=20lib.rs=20=E4=B8=8E?= =?UTF-8?q?=E5=A4=9A=E6=A8=A1=E5=9D=97=E6=9E=B6=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将核心业务逻辑抽离至 lib.rs,精简 main.rs 为纯粹的 CLI 入口 - 将代码按职责拆分为 config、env、runtime、shim 等子模块 - 提高可测试性与代码复用度 --- src/config.rs | 92 ++++++++++++++++ src/env.rs | 57 ++++++++++ src/error.rs | 3 +- src/lib.rs | 11 ++ src/main.rs | 4 +- src/runtime.rs | 86 +++++++++++++++ src/shim.rs | 57 ++++++++++ src/shims.rs | 283 ------------------------------------------------- 8 files changed, 305 insertions(+), 288 deletions(-) create mode 100644 src/config.rs create mode 100644 src/env.rs create mode 100644 src/lib.rs create mode 100644 src/runtime.rs create mode 100644 src/shim.rs delete mode 100644 src/shims.rs diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..68dbb3d --- /dev/null +++ b/src/config.rs @@ -0,0 +1,92 @@ +use std::collections::HashMap; +use std::path::PathBuf; +use std::process::Command; +use mlua::{FromLua, Lua, Table, Value}; + +#[derive(Debug, Clone)] +pub struct ShimConfig { + pub target_path: PathBuf, + pub args: Option>, + pub envs: Option>, +} + +impl ShimConfig { + /// 根据配置快速构建准备执行的 Command 对象 + pub fn to_command(&self) -> Command { + let mut cmd = Command::new(&self.target_path); + + if let Some(args) = &self.args { + cmd.args(args); + } + + if let Some(envs) = &self.envs { + for (key, val) in envs { + // 直接应用环境变量(Lua 端已经处理好字符串拼接或列表合并) + cmd.env(key, val); + } + } + + cmd + } +} + +/// 实现 FromLua Trait,由 mlua 自动处理 Table 转换 +impl FromLua for ShimConfig { + fn from_lua(value: Value, _lua: &Lua) -> mlua::Result { + match value { + Value::Table(table) => { + let path_str: String = table.get("path")?; + let args: Option> = table.get("args")?; + // 解析 env Table + let mut envs_map = HashMap::new(); + if let Ok(env_table) = table.get::("env") { + // 获取当前系统的路径分隔符(Windows 为 ";",Unix 为 ":") + #[cfg(windows)] + let sep = ";"; + #[cfg(not(windows))] + let sep = ":"; + + for pair in env_table.pairs::() { + let (k, v) = pair?; + match v { + // 情况 1: 普通字符串,如 HOME = "C:/path" -> 直接覆盖 + Value::String(s) => { + envs_map.insert(k, s.to_str()?.to_string()); + } + // 情况 2: 数组 Table,如 PATH = { bin_dir, get_env("PATH") } + Value::Table(arr) => { + let paths: Vec = arr + // 将 arr 作为序列(数组)处理,每个元素转为 String + .sequence_values::() + .filter_map(|r| r.ok()) + .filter(|s| !s.is_empty()) // 过滤空串,防止生成不必要的连续 ;; + .collect(); + + let combined = paths.join(sep); + envs_map.insert(k, combined); + } + _ => {} + } + } + } + + let envs = if envs_map.is_empty() { + None + } else { + Some(envs_map) + }; + + Ok(ShimConfig { + target_path: PathBuf::from(path_str), + args, + envs, + }) + } + _ => Err(mlua::Error::FromLuaConversionError { + from: value.type_name(), + to: "ShimConfig".into(), + message: Some("Expected a Lua table".to_string()), + }), + } + } +} \ No newline at end of file diff --git a/src/env.rs b/src/env.rs new file mode 100644 index 0000000..579fc2c --- /dev/null +++ b/src/env.rs @@ -0,0 +1,57 @@ +use crate::error::ShimError; +use std::path::PathBuf; +pub struct ShimEnv { + pub bin_dir: PathBuf, + pub root_dir: PathBuf, + pub tools_dir: PathBuf, + pub target_name: String, +} + +impl ShimEnv { + /// 提取当前代理程序的运行环境信息 + pub fn new(current_exe: PathBuf) -> Result { + let bin_dir = current_exe + .parent() + .ok_or_else(|| { + ShimError::PathResolutionError(format!( + "无法获取程序 [{}] 的父级 bin 目录", + current_exe.display() + )) + })? + .to_path_buf(); + println!("bin_dir目录 {}", bin_dir.display()); + + let root_dir = bin_dir + .parent() + .ok_or_else(|| { + ShimError::PathResolutionError(format!( + "无法获取 bin 目录 [{}] 的父级 root 目录", + bin_dir.display() + )) + })? + .to_path_buf(); + println!("root_dir 目录 {}", root_dir.display()); + + let tools_dir = root_dir.join("tools"); + println!("tools_dir 目录 {}", tools_dir.display()); + + let target_name = current_exe + .file_stem() + .and_then(|s| s.to_str()) + .ok_or_else(|| { + ShimError::PathResolutionError(format!( + "无法从路径 [{}] 提取有效的程序名称", + current_exe.display() + )) + })? + .to_lowercase(); + println!("程序名称 {}", target_name); + + Ok(Self { + bin_dir, + root_dir, + tools_dir, + target_name, + }) + } +} diff --git a/src/error.rs b/src/error.rs index 1bb25c7..10f92af 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,3 @@ -use std::path::PathBuf; use thiserror::Error; // 推荐引入 thiserror 库,若不使用可手动实现 std::fmt::Display #[derive(Debug, Error)] @@ -24,4 +23,4 @@ pub enum ShimError { #[error("IO 错误: {0}")] Io(#[from] std::io::Error), -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b91bb3c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +mod config; +mod env; +mod error; +mod runtime; +mod shim; + +pub use config::ShimConfig; +pub use env::ShimEnv; +pub use error::ShimError; +pub use runtime::LuaRuntime; +pub use shim::Shim; diff --git a/src/main.rs b/src/main.rs index 0984d12..e72e009 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,10 +6,8 @@ use std::{ process::{Command, exit}, ptr::null_mut, }; -mod shims; -mod error; -use shims::Shim; +use rshim::Shim; use winapi::{ shared::minwindef::{BOOL, DWORD, FALSE, TRUE}, diff --git a/src/runtime.rs b/src/runtime.rs new file mode 100644 index 0000000..2c33d40 --- /dev/null +++ b/src/runtime.rs @@ -0,0 +1,86 @@ +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 { + // 只加载安全的标准库,剥离 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 { + 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::
("package") { + if let Ok(path) = package.get::("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 { + let code = fs::read_to_string(path)?; + + self.lua + .load(&code) + .set_name(path.to_string_lossy()) + .eval::
() + .map_err(|e| ShimError::LuaExecutionError { + file: path.display().to_string(), + source: e, + }) + } + + /// 将 Lua Value 解析转化为 ShimConfig 数据对象 + pub fn parse_config(&self, value: Value) -> Result { + ShimConfig::from_lua(value, &self.lua).map_err(|e| ShimError::InvalidConfig(e.to_string())) + } +} diff --git a/src/shim.rs b/src/shim.rs new file mode 100644 index 0000000..ac81ae3 --- /dev/null +++ b/src/shim.rs @@ -0,0 +1,57 @@ +use crate::ShimError; +use mlua::Value; +use std::{ + env, + io::{Error, ErrorKind}, +}; +use crate::{ShimConfig, ShimEnv, LuaRuntime}; + +pub struct Shim; + +impl Shim { + pub fn init() -> Result { + let current_exe = env::current_exe() + .map_err(|e| Error::new(ErrorKind::Other, format!("获取代理程序路径失败: {}", e)))?; + println!("当前目录 {}", current_exe.display()); + let shim_env = ShimEnv::new(current_exe)?; + let runtime = LuaRuntime::new(&shim_env)?; + + Self::resolve_config(&runtime, &shim_env) + } + + fn resolve_config(runtime: &LuaRuntime, env: &ShimEnv) -> Result { + // 策略 1: 尝试加载全局配置文件 shims.lua + let global_config = env.root_dir.join("shims.lua"); + if global_config.is_file() { + let root_table = runtime.evaluate_lua_script(&global_config)?; + + // 检查 shims.lua 中是否存在以 target_name 命名的 Table 节点 + if let Ok(target_val) = root_table.get::(env.target_name.as_str()) { + if matches!(target_val, Value::Table(_)) { + return runtime.parse_config(target_val); + } + } + // 穿透:若全局配置文件存在但未包含当前程序的 key,继续向下探查 + } + + // 策略 2: 降级寻找独立文件 ({exe}.lua),优先顺序:tools/ > root/ + let target_filename = format!("{}.lua", env.target_name); + let candidates = [ + env.tools_dir.join(&target_filename), + env.root_dir.join(&target_filename), + ]; + + for config_path in &candidates { + if config_path.is_file() { + let table = runtime.evaluate_lua_script(config_path)?; + return runtime.parse_config(Value::Table(table)); + } + } + + // 策略 3: 所有查找失败,抛出错误 + Err(ShimError::ConfigNotFound(format!( + "未找到关于 '{}' 的配置。请检查 shims.lua 或特定的 {}.lua 文件", + env.target_name, env.target_name + ))) + } +} diff --git a/src/shims.rs b/src/shims.rs deleted file mode 100644 index 38bd558..0000000 --- a/src/shims.rs +++ /dev/null @@ -1,283 +0,0 @@ -use crate::error::ShimError; -use mlua::{Error as LuaError, FromLua, Lua, StdLib, Table, Value}; -use std::process::Command; -use std::{ - collections::HashMap, - env, fs, - io::{Error, ErrorKind}, - path::{Path, PathBuf}, -}; - -/// 将 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 ShimEnv { - pub bin_dir: PathBuf, - pub root_dir: PathBuf, - pub tools_dir: PathBuf, - pub target_name: String, -} - -impl ShimEnv { - /// 提取当前代理程序的运行环境信息 - pub fn new(current_exe: PathBuf) -> Result { - let bin_dir = current_exe - .parent() - .ok_or_else(|| { - ShimError::PathResolutionError(format!( - "无法获取程序 [{}] 的父级 bin 目录", - current_exe.display() - )) - })? - .to_path_buf(); - println!("bin_dir目录 {}", bin_dir.display()); - - let root_dir = bin_dir - .parent() - .ok_or_else(|| { - ShimError::PathResolutionError(format!( - "无法获取 bin 目录 [{}] 的父级 root 目录", - bin_dir.display() - )) - })? - .to_path_buf(); - println!("root_dir 目录 {}", root_dir.display()); - - let tools_dir = root_dir.join("tools"); - println!("tools_dir 目录 {}", tools_dir.display()); - - let target_name = current_exe - .file_stem() - .and_then(|s| s.to_str()) - .ok_or_else(|| { - ShimError::PathResolutionError(format!( - "无法从路径 [{}] 提取有效的程序名称", - current_exe.display() - )) - })? - .to_lowercase(); - println!("程序名称 {}", target_name); - - Ok(Self { - bin_dir, - root_dir, - tools_dir, - target_name, - }) - } -} - -#[derive(Debug, Clone)] -pub struct ShimConfig { - pub target_path: PathBuf, - pub args: Option>, - pub envs: Option>, -} - -impl ShimConfig { - /// 根据配置快速构建准备执行的 Command 对象 - pub fn to_command(&self) -> Command { - let mut cmd = Command::new(&self.target_path); - - if let Some(args) = &self.args { - cmd.args(args); - } - - if let Some(envs) = &self.envs { - for (key, val) in envs { - // 直接应用环境变量(Lua 端已经处理好字符串拼接或列表合并) - cmd.env(key, val); - } - } - - cmd - } -} - -/// 实现 FromLua Trait,由 mlua 自动处理 Table 转换 -impl FromLua for ShimConfig { - fn from_lua(value: Value, _lua: &Lua) -> mlua::Result { - match value { - Value::Table(table) => { - let path_str: String = table.get("path")?; - let args: Option> = table.get("args")?; - // 解析 env Table - let mut envs_map = HashMap::new(); - if let Ok(env_table) = table.get::
("env") { - // 获取当前系统的路径分隔符(Windows 为 ";",Unix 为 ":") - #[cfg(windows)] - let sep = ";"; - #[cfg(not(windows))] - let sep = ":"; - - for pair in env_table.pairs::() { - let (k, v) = pair?; - match v { - // 情况 1: 普通字符串,如 HOME = "C:/path" -> 直接覆盖 - Value::String(s) => { - envs_map.insert(k, s.to_str()?.to_string()); - } - // 情况 2: 数组 Table,如 PATH = { bin_dir, get_env("PATH") } - Value::Table(arr) => { - let paths: Vec = arr - // 将 arr 作为序列(数组)处理,每个元素转为 String - .sequence_values::() - .filter_map(|r| r.ok()) - .filter(|s| !s.is_empty()) // 过滤空串,防止生成不必要的连续 ;; - .collect(); - - let combined = paths.join(sep); - envs_map.insert(k, combined); - } - _ => {} - } - } - } - - let envs = if envs_map.is_empty() { - None - } else { - Some(envs_map) - }; - - Ok(ShimConfig { - target_path: PathBuf::from(path_str), - args, - envs, - }) - } - _ => Err(mlua::Error::FromLuaConversionError { - from: value.type_name(), - to: "ShimConfig".into(), - message: Some("Expected a Lua table".to_string()), - }), - } - } -} -pub struct LuaRuntime { - lua: Lua, -} - -impl LuaRuntime { - /// 初始化限定权限的 Lua 沙箱环境 - pub fn new(shim_env: &ShimEnv) -> Result { - // 只加载安全的标准库,剥离 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 { - 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::
("package") { - if let Ok(path) = package.get::("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 { - let code = fs::read_to_string(path)?; - - self.lua - .load(&code) - .set_name(path.to_string_lossy()) - .eval::
() - .map_err(|e| ShimError::LuaExecutionError { - file: path.display().to_string(), - source: e, - }) - } - - /// 将 Lua Value 解析转化为 ShimConfig 数据对象 - pub fn parse_config(&self, value: Value) -> Result { - ShimConfig::from_lua(value, &self.lua).map_err(|e| ShimError::InvalidConfig(e.to_string())) - } -} -pub struct Shim; - -impl Shim { - pub fn init() -> Result { - let current_exe = env::current_exe() - .map_err(|e| Error::new(ErrorKind::Other, format!("获取代理程序路径失败: {}", e)))?; - println!("当前目录 {}", current_exe.display()); - let shim_env = ShimEnv::new(current_exe)?; - let runtime = LuaRuntime::new(&shim_env)?; - - Self::resolve_config(&runtime, &shim_env) - } - - fn resolve_config(engine: &LuaRuntime, env: &ShimEnv) -> Result { - // 策略 1: 尝试加载全局配置文件 shims.lua - let global_config = env.root_dir.join("shims.lua"); - if global_config.is_file() { - let root_table = engine.evaluate_lua_script(&global_config)?; - - // 检查 shims.lua 中是否存在以 target_name 命名的 Table 节点 - if let Ok(target_val) = root_table.get::(env.target_name.as_str()) { - if matches!(target_val, Value::Table(_)) { - return engine.parse_config(target_val); - } - } - // 穿透:若全局配置文件存在但未包含当前程序的 key,继续向下探查 - } - - // 策略 2: 降级寻找独立文件 ({exe}.lua),优先顺序:tools/ > root/ - let target_filename = format!("{}.lua", env.target_name); - let candidates = [ - env.tools_dir.join(&target_filename), - env.root_dir.join(&target_filename), - ]; - - for config_path in &candidates { - if config_path.is_file() { - let table = engine.evaluate_lua_script(config_path)?; - return engine.parse_config(Value::Table(table)); - } - } - - // 策略 3: 所有查找失败,抛出错误 - Err(ShimError::ConfigNotFound(format!( - "未找到关于 '{}' 的配置。请检查 shims.lua 或特定的 {}.lua 文件", - env.target_name, env.target_name - ))) - } -}