fix(config): 优化 layout
- 新增 从环境变量解析目录结构
This commit is contained in:
331
Cargo.lock
generated
331
Cargo.lock
generated
@@ -1,331 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de"
|
||||
|
||||
[[package]]
|
||||
name = "fs-err"
|
||||
version = "3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lua-src"
|
||||
version = "550.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75c110c2fa33f34e0de05448e1f3eb2e0631e7a69e2d8ae1586cffc9fc9f9949"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "luajit-src"
|
||||
version = "210.7.2+b925b3e"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "920cf654b23d217c550ceea57c32cd2a413ea27b6d47ed77b5ee0cf655adefa6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "mlua"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad72ffa037cf5970c9860674f32f703fda25d86cf217475fe7a79c5f9961bcaa"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"either",
|
||||
"libc",
|
||||
"mlua-sys",
|
||||
"num-traits",
|
||||
"parking_lot",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mlua-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92136787b906d4e55cfe96cd6c62e010bb1a56889d0d6cf83eb016dbad07576b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"lua-src",
|
||||
"luajit-src",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rshim"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fs-err",
|
||||
"mlua",
|
||||
"thiserror",
|
||||
"unicode-bom",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bom"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "8.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f3ef584124b911bcc3875c2f1472e80f24361ceb789bd1c62b3e9a3df9ff43c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
8
build.rs
8
build.rs
@@ -34,13 +34,13 @@ fn main() {
|
||||
}
|
||||
|
||||
// 复制文件
|
||||
let source = PathBuf::from(&manifest_dir).join("shims.lua");
|
||||
let source = PathBuf::from(&manifest_dir).join("mimic.lua");
|
||||
let destination = output_dir.join("rshim.lua");
|
||||
|
||||
if source.exists() {
|
||||
fs::copy(&source, &destination).expect("Failed to copy shims.lua");
|
||||
println!("Copied shims.lua to: {:?}", destination);
|
||||
fs::copy(&source, &destination).expect("Failed to copy mimic.lua");
|
||||
println!("Copied mimic.lua to: {:?}", destination);
|
||||
} else {
|
||||
panic!("shims.lua not found at: {:?}", source);
|
||||
panic!("mimic.lua not found at: {:?}", source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- shims.lua (总控制台)
|
||||
-- mimic.lua (总控制台)
|
||||
-- __SHIM_DIR__ 已经在 Rust 中注入完毕,并且是正斜杠格式 (例如 C:/Tools)
|
||||
local base_dir = __SHIM_DIR__
|
||||
|
||||
194
src/config.rs
194
src/config.rs
@@ -1,4 +1,4 @@
|
||||
use mlua::{FromLua, Lua, LuaString, Table, Value};
|
||||
use mlua::{FromLua, Lua, LuaString, ObjectLike, Table, Value};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt;
|
||||
@@ -35,7 +35,7 @@ fn lua_string_2_os_string(s: LuaString) -> mlua::Result<OsString> {
|
||||
let (cow, _, had_errors) = encoding_rs::GBK.decode(raw_bytes);
|
||||
if had_errors {
|
||||
return Err(mlua::Error::RuntimeError(
|
||||
"Path contains invalid/unsupported byte encoding".into(),
|
||||
"路径包含无效/不支持的字节编码".into(),
|
||||
));
|
||||
}
|
||||
Ok(OsString::from(cow.as_ref()))
|
||||
@@ -43,58 +43,71 @@ fn lua_string_2_os_string(s: LuaString) -> mlua::Result<OsString> {
|
||||
}
|
||||
|
||||
/// 按连续整数下标遍历表的序列部分;存在空洞或非序列键时返回错误,避免静默截断
|
||||
fn validate_each_sequence_item(
|
||||
tbl: &Table,
|
||||
mut f: impl FnMut(Value) -> mlua::Result<()>,
|
||||
) -> mlua::Result<()> {
|
||||
let mut index = 1i64;
|
||||
loop {
|
||||
let item: Value = tbl.raw_get(index)?;
|
||||
if matches!(item, Value::Nil) {
|
||||
break;
|
||||
}
|
||||
f(item)?;
|
||||
index += 1;
|
||||
}
|
||||
// 校验剩余键:只允许已经被遍历的连续整数键
|
||||
for pair in tbl.pairs::<Value, Value>() {
|
||||
let (key, _) = pair?;
|
||||
match key {
|
||||
Value::Integer(i) if i >= 1 && i < index => {}
|
||||
_ => {
|
||||
return Err(conversion_error(format!(
|
||||
"数组只能包含连续的整数下标 [1..{}],发现非序列键或空洞",
|
||||
index - 1
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 值分流与上下文分发枚举
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ValueShunt {
|
||||
pub enum ValueShunt<'a> {
|
||||
/// 命令行参数元素:仅支持一维基础标量,禁止嵌套 Table
|
||||
Args,
|
||||
/// 环境变量值:支持基础标量及多维嵌套 Table(递归展平)
|
||||
Env,
|
||||
Env(&'a str),
|
||||
}
|
||||
impl Display for ValueShunt {
|
||||
impl<'a> Display for ValueShunt<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Args => write!(f, "命令行参数 (args)"),
|
||||
Self::Env => write!(f, "环境变量 (env)"),
|
||||
Self::Env(name) => write!(f, "环境变量 ({name})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueShunt {
|
||||
fn expected_types_desc(&self) -> &str {
|
||||
match self {
|
||||
Self::Args => "string/number/boolean(不支持嵌套数组)",
|
||||
Self::Env => "string/number/boolean 或包含这些类型的数组/嵌套数组",
|
||||
}
|
||||
impl<'a> ValueShunt<'a> {
|
||||
fn parse_sequence(&self, tbl: &Table, out: &mut Vec<OsString>) -> mlua::Result<()> {
|
||||
self.validate_each_sequence_item(tbl, |item| self.collect_into(item, out))
|
||||
}
|
||||
|
||||
fn validate_each_sequence_item(
|
||||
&self,
|
||||
tbl: &Table,
|
||||
mut f: impl FnMut(Value) -> mlua::Result<()>,
|
||||
) -> mlua::Result<()> {
|
||||
let mut index = 1i64;
|
||||
loop {
|
||||
let item: Value = tbl.raw_get(index)?;
|
||||
if matches!(item, Value::Nil) {
|
||||
break;
|
||||
}
|
||||
f(item)?;
|
||||
index += 1;
|
||||
}
|
||||
// 校验剩余键:只允许已经被遍历的连续整数键
|
||||
for pair in tbl.pairs::<Value, Value>() {
|
||||
let (key, _) = pair?;
|
||||
match key {
|
||||
Value::Integer(i) if i >= 1 && i < index => {}
|
||||
Value::Integer(_) => {
|
||||
return Err(conversion_error(format!(
|
||||
"{} 第 {} 个元素为 nil 或不存在(请检查是否漏写引号或变量未定义)",
|
||||
self, index
|
||||
)));
|
||||
}
|
||||
_ => {
|
||||
// let label = match self {
|
||||
// ValueShunt::Args => "命令行参数 (args)".to_string(),
|
||||
// ValueShunt::Env(n) => format!("环境变量 [{n}]"),
|
||||
// };
|
||||
return Err(conversion_error(format!(
|
||||
"{} 必须是纯列表,不能包含键值对/字典结构",
|
||||
self
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ValueShunt<'a> {
|
||||
/// 递归/标量收集实现
|
||||
pub fn collect_into(&self, value: Value, out: &mut Vec<OsString>) -> mlua::Result<()> {
|
||||
match value {
|
||||
@@ -110,15 +123,32 @@ impl ValueShunt {
|
||||
out.push(OsString::from(b.to_string()));
|
||||
}
|
||||
// 嵌套 Table 仅在 EnvValue 下允许递归展开(支持 get_env("PATH") 等返回的子表)
|
||||
Value::Table(tbl) if *self == Self::Env => {
|
||||
validate_each_sequence_item(&tbl, |item| self.collect_into(item, out))?;
|
||||
// Value::Table(tbl) if *self == Self::Env(_) => {
|
||||
// self.validate_each_sequence_item(&tbl, |item| self.collect_into(item, out))?;
|
||||
// }
|
||||
Value::Table(tbl) => {
|
||||
match self {
|
||||
Self::Args => {
|
||||
return Err(conversion_error(format!(
|
||||
"{} 不支持 {} 类型, 仅支持 string/number/boolean",
|
||||
self,
|
||||
tbl.to_string()?
|
||||
)));
|
||||
}
|
||||
Self::Env(_) => {
|
||||
self.validate_each_sequence_item(&tbl, |item| {
|
||||
self.collect_into(item, out)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
// self.validate_each_sequence_item(&tbl, |item| self.collect_into(item, out))?;
|
||||
}
|
||||
|
||||
other => {
|
||||
return Err(conversion_error(format!(
|
||||
"{} 不支持类型 {}(仅支持 {})",
|
||||
"{} 不支持 {} 类型, 仅支持 string/number/boolean 或包含这些类型的数组/嵌套数组",
|
||||
self,
|
||||
other.type_name(),
|
||||
self.expected_types_desc()
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -127,7 +157,19 @@ impl ValueShunt {
|
||||
}
|
||||
|
||||
/// 校验环境变量名的合法性(Windows 约束:非空、不含 '='、不含 NUL)
|
||||
fn validate_env_var_name(key: &str) -> mlua::Result<()> {
|
||||
fn validate_env_var_name(key_val: &Value, val: &Value) -> mlua::Result<String> {
|
||||
let key = match key_val {
|
||||
Value::String(s) => s.to_str()?.to_string(),
|
||||
|
||||
_ => {
|
||||
return Err(conversion_error(format!(
|
||||
r#"env 配置解析失败:值: {} (类型: {}) 无效的键值!
|
||||
示例 HOME="/home" 或 PATH = {{ "C:/tools/", "D:/Windows"}})"#,
|
||||
val.to_string()?,
|
||||
val.type_name()
|
||||
)));
|
||||
}
|
||||
};
|
||||
if key.is_empty() {
|
||||
return Err(conversion_error("环境变量名不能为空"));
|
||||
}
|
||||
@@ -143,42 +185,11 @@ fn validate_env_var_name(key: &str) -> mlua::Result<()> {
|
||||
key
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
/// 递归将任意 Lua Value 展开为扁平的字符串片段列表
|
||||
fn collect_env_segments(value: Value, out: &mut Vec<OsString>) -> mlua::Result<()> {
|
||||
match value {
|
||||
// 1. 字符串
|
||||
Value::String(s) => out.push(lua_string_2_os_string(s)?),
|
||||
// 2. 整数与浮点数
|
||||
Value::Integer(i) => out.push(OsString::from(i.to_string())),
|
||||
Value::Number(n) => {
|
||||
tracing::warn!(value = %n, "环境变量中的浮点数将按十进制格式转换为字符串");
|
||||
out.push(OsString::from(n.to_string()));
|
||||
}
|
||||
// 3. 布尔值
|
||||
Value::Boolean(b) => {
|
||||
tracing::warn!(value = %b, "环境变量中的布尔值将转换为字符串");
|
||||
out.push(OsString::from(b.to_string()));
|
||||
}
|
||||
// 4. 表/数组:必须是连续整数下标的纯序列,递归解包(支持任意深度的嵌套数组)
|
||||
Value::Table(tbl) => {
|
||||
validate_each_sequence_item(&tbl, |item| collect_env_segments(item, out))?
|
||||
}
|
||||
// 5. 安全忽
|
||||
// 略 nil
|
||||
Value::Nil => {}
|
||||
// 6. 无法转为环境变量的非法类型(函数、协程、UserData 等):报错而非静默忽略
|
||||
other => {
|
||||
return Err(conversion_error(format!(
|
||||
"环境变量值不支持类型 {}(仅支持 string/number/boolean/数组)",
|
||||
other.type_name()
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
// fn collect_env_segments(value: Value, out: &mut Vec<OsString>) -> mlua::Result<()>{OK(())}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ShimConfig {
|
||||
@@ -243,11 +254,7 @@ impl FromLua for ShimConfig {
|
||||
// 可选字段: args(缺失或 nil 时默认空列表;保留空字符串参数,与提权路径的 "" 语义一致)
|
||||
match table.get::<Option<Value>>("args")? {
|
||||
None | Some(Value::Nil) => {}
|
||||
Some(Value::Table(tbl)) => {
|
||||
validate_each_sequence_item(&tbl, |item| {
|
||||
ValueShunt::Args.collect_into(item, &mut args)
|
||||
})?;
|
||||
}
|
||||
Some(Value::Table(tbl)) => ValueShunt::Args.parse_sequence(&tbl, &mut args)?,
|
||||
Some(other) => {
|
||||
return Err(conversion_error(format!(
|
||||
"args 必须是数组列表,实际类型是 {}",
|
||||
@@ -262,12 +269,13 @@ impl FromLua for ShimConfig {
|
||||
match env_table {
|
||||
None | Some(Value::Nil) => {}
|
||||
Some(Value::Table(tbl)) => {
|
||||
for pair in tbl.pairs::<String, Value>() {
|
||||
for pair in tbl.pairs::<Value, Value>() {
|
||||
let (name, value) = pair?;
|
||||
validate_env_var_name(&name)?;
|
||||
|
||||
let name = validate_env_var_name(&name, &value)?;
|
||||
println!("==========={name}:{:?}", value);
|
||||
let mut parts = Vec::new();
|
||||
ValueShunt::Env.collect_into(value, &mut parts)?;
|
||||
ValueShunt::Env(&name).collect_into(value, &mut parts)?;
|
||||
|
||||
// 空数组/空串也显式设置(空值表示清空该变量),
|
||||
// 与“未配置该变量(继承宿主环境)”相区分
|
||||
@@ -277,7 +285,7 @@ impl FromLua for ShimConfig {
|
||||
name, e
|
||||
))
|
||||
})?;
|
||||
println!("环境变量拼接结果:{:?}", joined_os_str);
|
||||
// println!("环境变量拼接结果:{:?}", joined_os_str);
|
||||
// 检查是否包含非法 NUL 字符
|
||||
#[cfg(unix)]
|
||||
{
|
||||
@@ -355,14 +363,10 @@ mod tests {
|
||||
target = "C:/tools/git.exe",
|
||||
args = { "--no-pager" },
|
||||
env = {
|
||||
PATH = { "C:/tools/git/bin", "C:/Windows",get_env("PATH") },
|
||||
PATH = { "C:/tools/git/bin", "C:/Windows", get_env("PATH") },
|
||||
HOME = "C:/tools/home",
|
||||
CONST = 3,
|
||||
BOOL = true,
|
||||
MUT ={
|
||||
A=3,
|
||||
B=true
|
||||
}
|
||||
BOOL = true
|
||||
}
|
||||
}
|
||||
"#,
|
||||
@@ -470,7 +474,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn rejects_non_string_args_element() {
|
||||
assert!(parse(r#"return { target = "t.exe", args = { 1 } }"#).is_err());
|
||||
// ValueShunt::Args 允许基础标量(数字/布尔)转字符串,仅禁止嵌套表
|
||||
let cfg = parse(r#"return { target = "t.exe", args = { 1, true } }"#).unwrap();
|
||||
assert_eq!(cfg.args, vec!["1", "true"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,36 +1,67 @@
|
||||
use anyhow::{Context, Result, bail};
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct ShimLayout {
|
||||
pub root_dir: PathBuf,
|
||||
use tracing::debug;
|
||||
pub struct Layout {
|
||||
pub base_dir: PathBuf,
|
||||
pub bin_dir: PathBuf,
|
||||
pub tools_dir: PathBuf,
|
||||
pub lua_file: PathBuf,
|
||||
}
|
||||
|
||||
impl ShimLayout {
|
||||
impl Layout {
|
||||
/// 自动解析目录布局:
|
||||
/// 1. 优先使用环境变量 RSHIM_HOME
|
||||
/// 2. 兜底回退到当前垫片可执行文件所在目录推断 (exe -> bin -> root)
|
||||
pub fn discover(current_exe: &Path) -> Result<Self> {
|
||||
// 策略 1: 环境变量优先
|
||||
if let Ok(home_val) = env::var("MIMIC_HOME") {
|
||||
let trimmed = home_val.trim();
|
||||
if !trimmed.is_empty() {
|
||||
debug!(home = %trimmed, "检测到 MIMIC_HOME,采用环境变量配置");
|
||||
return Self::from_base_dir(PathBuf::from(trimmed));
|
||||
}
|
||||
}
|
||||
|
||||
// 策略 2: 相对路径自动推断兜底
|
||||
debug!("未配置 MIMIC_HOME,尝试从当前可执行文件路径推断根目录");
|
||||
Self::from_executable(current_exe)
|
||||
}
|
||||
|
||||
/// 基于确定的根目录构建完整布局
|
||||
fn from_base_dir(base_dir: PathBuf) -> Result<Self> {
|
||||
if !base_dir.is_dir() {
|
||||
bail!("指定的根目录不存在或不是有效目录: [{}]", base_dir.display());
|
||||
}
|
||||
|
||||
let bin_dir = base_dir.join("bin");
|
||||
let tools_dir = base_dir.join("tools");
|
||||
let lua_file = base_dir.join("mimic.lua");
|
||||
|
||||
Ok(Self {
|
||||
base_dir,
|
||||
bin_dir,
|
||||
tools_dir,
|
||||
lua_file,
|
||||
})
|
||||
}
|
||||
|
||||
/// 从当前可执行文件解析 shim 安装目录布局
|
||||
pub fn from_executable(exe_path: impl AsRef<Path>) -> Result<Self> {
|
||||
let exe_path = exe_path.as_ref();
|
||||
fn from_executable(exe_path: &Path) -> Result<Self> {
|
||||
// let exe_path = exe_path.as_ref();
|
||||
let bin_dir = exe_path
|
||||
.parent()
|
||||
.with_context(|| format!("无法获取程序 [{}] 的父级 bin 目录", exe_path.display()))?
|
||||
.to_path_buf();
|
||||
|
||||
eprintln!("bin_dir目录 {}", bin_dir.display());
|
||||
debug!("bin_dir目录 {}", bin_dir.display());
|
||||
|
||||
let root_dir = bin_dir
|
||||
let base_dir = bin_dir
|
||||
.parent()
|
||||
.with_context(|| format!("无法获取 bin 目录 [{}] 的父级 root 目录", bin_dir.display()))?
|
||||
.to_path_buf();
|
||||
eprintln!("root_dir 目录 {}", root_dir.display());
|
||||
debug!("base_dir 目录 {}", base_dir.display());
|
||||
|
||||
let tools_dir = root_dir.join("tools");
|
||||
eprintln!("tools_dir 目录 {}", tools_dir.display());
|
||||
|
||||
Ok(Self {
|
||||
root_dir,
|
||||
bin_dir,
|
||||
tools_dir,
|
||||
})
|
||||
Self::from_base_dir(base_dir)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ mod layout;
|
||||
mod logger;
|
||||
mod runtime;
|
||||
mod shim;
|
||||
mod loader;
|
||||
|
||||
pub use config::ShimConfig;
|
||||
pub use layout::ShimLayout;
|
||||
pub use layout::Layout;
|
||||
pub use runtime::LuaRuntime;
|
||||
pub use shim::Shim;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{ShimConfig, ShimLayout};
|
||||
use crate::{ShimConfig, Layout};
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use mlua::{FromLua, Lua, StdLib, Table, Value};
|
||||
use std::ffi::OsStr;
|
||||
@@ -17,7 +17,7 @@ pub struct LuaRuntime {
|
||||
|
||||
impl LuaRuntime {
|
||||
/// 初始化限定权限的 Lua 沙箱环境
|
||||
pub fn new(layout: &ShimLayout) -> Result<Self> {
|
||||
pub fn new(layout: &Layout) -> Result<Self> {
|
||||
// 只加载安全的标准库,剥离 os / io 等风险模块
|
||||
let lua = Lua::new_with(
|
||||
StdLib::TABLE | StdLib::STRING | StdLib::MATH | StdLib::PACKAGE,
|
||||
@@ -28,12 +28,12 @@ impl LuaRuntime {
|
||||
let globals = lua.globals();
|
||||
|
||||
// 统一使用 POSIX 风格路径规范化路径字符串
|
||||
let root_dir = normalize_path_for_lua(&layout.root_dir);
|
||||
let base_dir = normalize_path_for_lua(&layout.base_dir);
|
||||
let tools_dir = normalize_path_for_lua(&layout.tools_dir);
|
||||
|
||||
// 1. 注入锚点变量 __SHIM_DIR__(shim 安装根目录)
|
||||
globals
|
||||
.set("__SHIM_DIR__", root_dir.clone())
|
||||
.set("__SHIM_DIR__", base_dir.clone())
|
||||
.context("设置 __SHIM_DIR__ 环境变量失败")?;
|
||||
|
||||
// 2. 安全暴露 get_env 供配置读取环境变量
|
||||
@@ -82,14 +82,14 @@ impl LuaRuntime {
|
||||
if let Ok(path) = package.get::<String>("path") {
|
||||
let new_path = format!(
|
||||
"{};{}/?.lua;{}/?/init.lua;{}/?.lua;{}/?/init.lua",
|
||||
path, root_dir, root_dir, tools_dir, tools_dir
|
||||
path, base_dir, base_dir, tools_dir, tools_dir
|
||||
);
|
||||
let _ = package.set("path", new_path);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 包装 require:配置模块缺失/加载失败时记录日志并跳过该条目,
|
||||
// 而不是让整个 shims.lua 解析失败(排查问题时日志可见)
|
||||
// 而不是让整个 mimic.lua 解析失败(排查问题时日志可见)
|
||||
let original_require: mlua::Function = globals
|
||||
.get("require")
|
||||
.context("获取内置 require 函数失败")?;
|
||||
@@ -145,23 +145,20 @@ impl LuaRuntime {
|
||||
.with_context(|| format!("执行 Lua 配置文件失败: {}", path.display()))
|
||||
}
|
||||
|
||||
// /// 将 Lua Value 解析转化为 ShimConfig 数据对象
|
||||
// pub fn parse_config(&self, value: Value) -> Result<ShimConfig, Error> {
|
||||
// ShimConfig::from_lua(value, &self.lua).map_err(|e| Error::InvalidConfig(e.to_string()))
|
||||
// }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ShimLayout;
|
||||
use crate::Layout;
|
||||
|
||||
fn test_layout() -> ShimLayout {
|
||||
fn test_layout() -> Layout {
|
||||
let root = std::env::temp_dir().join("rshim-test-layout");
|
||||
ShimLayout {
|
||||
root_dir: root.clone(),
|
||||
Layout {
|
||||
base_dir: root.clone(),
|
||||
bin_dir: root.join("bin"),
|
||||
tools_dir: root.join("tools"),
|
||||
lua_file: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
27
src/shim.rs
27
src/shim.rs
@@ -1,4 +1,4 @@
|
||||
use crate::{LuaRuntime, ShimConfig, ShimLayout};
|
||||
use crate::{LuaRuntime, ShimConfig, Layout};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use mlua::{Table, Value};
|
||||
use std::env;
|
||||
@@ -20,10 +20,10 @@ impl Shim {
|
||||
current_exe = %current_exe.display(),
|
||||
"开始加载 Shim 配置"
|
||||
);
|
||||
let layout = Layout::discover(¤t_exe)?;
|
||||
|
||||
let layout = ShimLayout::from_executable(current_exe)?;
|
||||
debug!(
|
||||
root_dir = %layout.root_dir.display(),
|
||||
root_dir = %layout.base_dir.display(),
|
||||
bin_dir = %layout.bin_dir.display(),
|
||||
tools_dir = %layout.tools_dir.display(),
|
||||
"Shim 目录布局解析完成"
|
||||
@@ -36,16 +36,25 @@ impl Shim {
|
||||
|
||||
fn resolve_config(
|
||||
runtime: &LuaRuntime,
|
||||
paths: &ShimLayout,
|
||||
paths: &Layout,
|
||||
target_name: &str,
|
||||
) -> Result<ShimConfig> {
|
||||
// 策略 1: 尝试加载全局配置文件 shims.lua
|
||||
let global_config = paths.root_dir.join("rshim.lua");
|
||||
// 策略 1: 尝试加载全局配置文件 mimic.lua
|
||||
let global_config = paths.base_dir.join("rshim.lua");
|
||||
|
||||
// if !global_config.is_file() {
|
||||
// bail!(
|
||||
// "未找到主配置文件: [{}],请确保在安装根目录创建 rshim.lua",
|
||||
// global_config.display()
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
if global_config.is_file() {
|
||||
trace!(path = %global_config.display(), "发现全局配置文件,尝试解析");
|
||||
let root_table: Table = runtime.eval_script(&global_config)?;
|
||||
|
||||
// 检查 shims.lua 中是否存在以 target_name 命名的 Table 节点
|
||||
// 检查 mimic.lua 中是否存在以 target_name 命名的 Table 节点
|
||||
if root_table
|
||||
.contains_key(target_name)
|
||||
.context("检查全局配置失败")?
|
||||
@@ -69,7 +78,7 @@ impl Shim {
|
||||
let target_filename = format!("{}.lua", target_name);
|
||||
let candidates = [
|
||||
paths.tools_dir.join(&target_filename),
|
||||
paths.root_dir.join(&target_filename),
|
||||
paths.base_dir.join(&target_filename),
|
||||
];
|
||||
|
||||
for config_path in &candidates {
|
||||
@@ -84,7 +93,7 @@ impl Shim {
|
||||
// 策略 3: 所有查找失败,抛出错误
|
||||
warn!(target = %target_name, "未找到任何匹配的配置文件");
|
||||
bail!(
|
||||
"未找到关于 '{}' 的配置。请检查 shims.lua 或特定的 {}.lua 文件",
|
||||
"未找到关于 '{}' 的配置。请检查 mimic.lua 或特定的 {}.lua 文件",
|
||||
target_name,
|
||||
target_name
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user