use std::ffi::OsString; use mlua::{FromLua, Lua, LuaString, Value}; use std::path::PathBuf; use crate::utils::lua_string_2_os_string; use crate::validation_error; use crate::validators::ensure_no_nul; /// 目标可执行文件的定位配置(支持静态路径与多版本 Table) #[derive(Debug, Clone)] pub enum TargetConfig{ /// 静态单版本工具 Static(PathBuf), /// 多版本管理 Versioned{ /// 工具主根目录(供 mr use 扫描候选版本) home:PathBuf, /// 版本真相源文件名(如 ".version",可选,默认 ".version") version_file:Option, /// 当前激活版本的可执行文件完整路径(若未激活或不存在则为 None) current:Option, } } impl Default for TargetConfig { fn default() -> Self { TargetConfig::Static(PathBuf::default()) } } impl TargetConfig { /// 垫片程序(mirror-shim)专用快速通道:提取最终可执行路径 /// /// - Static: 返回自身路径 /// - Versioned: 返回 current 路径(若未激活则返回 None) pub fn executable_path(&self) -> Option<&PathBuf> { match self { TargetConfig::Static(path) => Some(path), TargetConfig::Versioned { current, .. } => current.as_ref(), } } /// 管理程序(mr-cli)专用辅助方法:判断是否支持多版本 pub fn is_versioned(&self) -> bool { matches!(self, TargetConfig::Versioned { .. }) } } impl FromLua for TargetConfig { fn from_lua(value: Value, _lua: &Lua) -> mlua::Result { match value { // 分支 1:静态单版本工具 target = "..." Value::String(s) => { let os_str = lua_string_2_os_string(&s)?; ensure_no_nul(&"target", &os_str)?; Ok(TargetConfig::Static(PathBuf::from(os_str))) } // 分支 2:多版本管理工具 target = { home = "...", ... } Value::Table(table) => { // 1. home (必填字段) let home = match table.get::>("home")? { Some(Value::String(s)) => { let os_str = lua_string_2_os_string(&s)?; ensure_no_nul(&"target.home", &os_str)?; PathBuf::from(os_str) } None | Some(Value::Nil) => { return Err(validation_error("多版本 target 表缺少必填字段 'home'")); } Some(other) => { return Err(validation_error(format!( "target.home 需为路径字符串,实际类型是 {}", other.type_name() ))); } }; // 2. version_file (选填字段,未填或 nil 时为 None) let version_file = match table.get::>("version_file")? { Some(Value::String(s)) => { let os_str = lua_string_2_os_string(&s)?; ensure_no_nul(&"target.version_file", &os_str)?; Some(os_str.to_string_lossy().into_owned()) } None | Some(Value::Nil) => None, Some(other) => { return Err(validation_error(format!( "target.version_file 需为文件名字符串,实际类型是 {}", other.type_name() ))); } }; // 3. current (选填/可为 nil,表示未激活或当前未指定版本) let current = match table.get::>("current")? { Some(Value::String(s)) => { let os_str = lua_string_2_os_string(&s)?; ensure_no_nul(&"target.current", &os_str)?; Some(PathBuf::from(os_str)) } // Lua 侧 ver 为 nil 时,current 拼出的也是 nil,此时合法解析为 None None | Some(Value::Nil) => None, Some(other) => { return Err(validation_error(format!( "target.current 需为有效路径字符串或 nil,实际类型是 {}", other.type_name() ))); } }; Ok(TargetConfig::Versioned { home, version_file, current, }) } Value::Nil => Err(validation_error( "缺少必填字段 target(应为字符串路径或多版本配置表)", )), other => Err(validation_error(format!( "target 需为有效路径字符串或多版本配置表,实际类型是 {}", other.type_name() ))), } } }