use mlua::LuaString; use std::ffi::OsString; use std::path::Path; /// 将 Path 转换为适合 Lua 使用的安全字符串路径 pub fn normalize_path_for_lua(path: &Path) -> String { // 自动将 Windows UNC 规范路径转回传统路径 let simplified = dunce::simplified(path); simplified.to_string_lossy().replace('\\', "/") } /// 将 Lua 字符串转为 Rust 字符串;非 UTF-8 按系统默认的 ANSI/OEM (如 GBK) 进行安全解码 pub fn lua_string_2_os_string(s: &LuaString) -> mlua::Result { let raw_bytes = &s.as_bytes(); // 1. Unix 平台:直接零拷贝透传原始字节(无损支持任意编码) #[cfg(unix)] { use std::os::unix::ffi::OsStrExt; Ok(OsStr::from_bytes(raw_bytes).to_os_string()) } // 2. Windows 平台:优先按 UTF-8 解码,失败则按当前系统本地代码页 (ANSI/GBK) 转换 #[cfg(windows)] { // 先尝试标准的 UTF-8 if let Ok(utf8_str) = std::str::from_utf8(raw_bytes) { return Ok(OsString::from(utf8_str)); } unsafe { use std::os::windows::ffi::OsStringExt; use windows_sys::Win32::Globalization::{ CP_ACP, MB_ERR_INVALID_CHARS, MultiByteToWideChar, }; if raw_bytes.is_empty() { return Ok(OsString::new()); } let len = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, raw_bytes.as_ptr(), raw_bytes.len() as i32, std::ptr::null_mut(), 0, ); if len <= 0 { // 构造一个带上下文的 FromLua 转换错误,便于定位配置问题 return Err(mlua::Error::FromLuaConversionError { from: "LuaString", to: "OsString".to_string(), message: Some( format!("字符串{:?}包含无效或当前系统无法识别的编码字节", s).to_string(), ), }); } let mut buf = vec![0u16; len as usize]; MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, raw_bytes.as_ptr(), raw_bytes.len() as i32, buf.as_mut_ptr(), len, ); Ok(OsString::from_wide(&buf)) } } } /// 将输入的字符串按 Shell 规则切分为独立的 CLI 参数 Token /// - 自动过滤连续空格 /// - 支持单引号 `'...'` 和双引号 `"..."` 包裹包含空格的参数 pub fn parse_tokens(input: &str) -> Vec { let mut tokens = Vec::new(); let mut current_token = String::new(); let mut in_quote: Option = None; let mut chars = input.chars().peekable(); while let Some(ch) = chars.next() { match (ch, in_quote) { // 处理转义字符 (例如 \") ('\\', _quote) => { if let Some(&next_ch) = chars.peek() { let should_escape = if cfg!(windows) { // Windows 策略:只有在转义引号、反斜杠本身时才剥离 \ // (如果在双引号内部,空格也不应该被 \ 转义) next_ch == '"' || next_ch == '\'' || next_ch == '\\' } else { // Unix 策略:标准 Shell 转义(引号、反斜杠、空格等) next_ch == '"' || next_ch == '\'' || next_ch == '\\' || next_ch.is_whitespace() }; if should_escape { chars.next(); // 消耗掉下一个字符 current_token.push(next_ch); } else { // 保留 Windows 路径分隔符或未知转义中的 \ current_token.push('\\'); } } else { // 结尾孤立的 \ current_token.push('\\'); } } // 遇到引号:开启或关闭引号包裹 ('"' | '\'', None) => { in_quote = Some(ch); } ('"' | '\'', Some(q)) if q == ch => { in_quote = None; } // 引号外部遇到空白字符:切分出一个完整的 Token (ch, None) if ch.is_whitespace() => { if !current_token.is_empty() { tokens.push(OsString::from(std::mem::take(&mut current_token))); } } // 其他字符或引号内部字符:直接追加 (ch, _) => { current_token.push(ch); } } } // 收尾最后一个 Token if !current_token.is_empty() { tokens.push(OsString::from(current_token)); } tokens } #[cfg(test)] mod tests { use super::*; use std::ffi::OsString; /// 辅助宏:简化声明与断言对比 macro_rules! assert_tokens { ($input:expr, $expected:expr) => { let actual = parse_tokens($input); let expected_os: Vec = $expected.into_iter().map(OsString::from).collect(); assert_eq!( actual, expected_os, "\n测试输入: {:?}\n期望输出: {:?}\n实际输出: {:?}", $input, expected_os, actual ); }; } #[test] fn test_parse_tokens_basic_split() { // 场景 1:基础多参数拆分(空格分隔) assert_tokens!("cargo run --verbose", vec!["cargo", "run", "--verbose"]); assert_tokens!("git status", vec!["git", "status"]); } #[test] fn test_parse_tokens_multi_alias_ref() { // 场景 2:多别名混合与组合引用 assert_tokens!( "mr:run --bin mr:base_flags", vec!["mr:run", "--bin", "mr:base_flags"] ); assert_tokens!( "mr:app1 mr:app2 --flag", vec!["mr:app1", "mr:app2", "--flag"] ); } #[test] fn test_parse_tokens_continuous_whitespaces() { // 场景 3:连续多空格与制表符过滤 assert_tokens!( "mr:run --bin \t my_app", vec!["mr:run", "--bin", "my_app"] ); assert_tokens!(" cargo build ", vec!["cargo", "build"]); } #[test] fn test_parse_tokens_double_quotes() { // 场景 4:双引号包裹包含空格的参数 assert_tokens!( "git commit -m \"fix a bug\"", vec!["git", "commit", "-m", "fix a bug"] ); assert_tokens!("echo \"hello world\"", vec!["echo", "hello world"]); } #[test] fn test_parse_tokens_single_quotes() { // 场景 5:单引号包裹包含空格的参数 assert_tokens!( "gcc -O2 'my file.c' -o app", vec!["gcc", "-O2", "my file.c", "-o", "app"] ); assert_tokens!( "python 'script with space.py'", vec!["python", "script with space.py"] ); } #[test] fn test_parse_tokens_escaped_characters() { // 场景 6:反斜杠转义字符 assert_tokens!("echo hello\\ world", vec!["echo", "hello\\", "world"]); assert_tokens!( "echo \"hello \\\"world\\\"\"", vec!["echo", "hello \"world\""] ); } #[test] fn test_parse_tokens_single_scalar_and_edge_cases() { // 场景 7:单标量参数与边界情况 assert_tokens!("git", vec!["git"]); assert_tokens!("8000", vec!["8000"]); assert_tokens!("", Vec::<&str>::new()); assert_tokens!(" ", Vec::<&str>::new()); } #[test] fn test_parse_tokens_unclosed_quotes() { // 场景 8:未闭合引号的容错处理(会尽量追加到当前 Token 中) assert_tokens!("echo \"hello world", vec!["echo", "hello world"]); } }