feat(cli): 优化 log 子命令并引入 LogLevel 强类型解析,重构 commands.lua 注册表解析与校验逻辑
- 调整 LogLevel 的 FromStr 错误类型为 String,适配 clap 的 value_parser - log 子命令直接绑定 LogLevel 枚举,消除硬编码校验与类型转换 - 规范化日志级别更新与配置读取逻辑 - handler 类型有 Function 改为 String
This commit is contained in:
35
mirror-cli/src/commands/builtin.rs
Normal file
35
mirror-cli/src/commands/builtin.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use anyhow::{Context, Result};
|
||||
use mirror_core::Layout;
|
||||
use std::fs;
|
||||
|
||||
// Rust 原生处理 log 命令逻辑
|
||||
pub(crate) fn log_handle(layout: &Layout, matches: &clap::ArgMatches) -> Result<()> {
|
||||
let log_ini_path = layout.base_dir.join("mirror-log.ini");
|
||||
|
||||
// 1. 处理设置日志等级
|
||||
if let Some(level) = matches.get_one::<String>("level") {
|
||||
let content = format!("level = \"{}\"\nlog_dir = \"logs\"\n", level.as_str());
|
||||
fs::write(&log_ini_path, content)
|
||||
.with_context(|| format!("写入日志配置文件失败: {}", log_ini_path.display()))?;
|
||||
println!("日志级别已更新为: {level} ({})", log_ini_path.display());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 2. 处理 tail 查看
|
||||
if let Some(&tail_lines) = matches.get_one::<usize>("tail") {
|
||||
println!("正在检索最后 {tail_lines} 行日志...");
|
||||
// 原生 Rust 高性能按行倒序读取日志文件
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 3. 默认打印当前配置
|
||||
if log_ini_path.is_file() {
|
||||
let current_ini = fs::read_to_string(&log_ini_path)
|
||||
.with_context(|| format!("读取配置文件失败: {}", log_ini_path.display()))?;
|
||||
println!("当前 mirror-log.ini 配置:\n{current_ini}");
|
||||
} else {
|
||||
println!("未找到 mirror-log.ini,当前使用默认全局级别: info");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
139
mirror-cli/src/commands/dynamic.rs
Normal file
139
mirror-cli/src/commands/dynamic.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use crate::validators;
|
||||
use mirror_core::error::validation_error;
|
||||
use mlua::{FromLua, Lua, Value};
|
||||
use std::collections::HashMap;
|
||||
|
||||
const ALLOWED_KEYS: &[&str] = &["handler", "description"];
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct MrCommand {
|
||||
pub commands: HashMap<String, MrSubCommand>,
|
||||
}
|
||||
impl MrCommand {
|
||||
pub fn get(&self, name: &str) -> Option<&MrSubCommand> {
|
||||
self.commands.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for MrCommand {
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
|
||||
let root_tbl = match value {
|
||||
Value::Table(t) => t,
|
||||
other => {
|
||||
return Err(validation_error(format!(
|
||||
"commands 注册表的顶层配置必须是 Table,实际检测到: {}",
|
||||
other.type_name()
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let mut commands = HashMap::new();
|
||||
|
||||
for pair in root_tbl.pairs::<Value, Value>() {
|
||||
let (cmd_name, cmd_entry) = pair?;
|
||||
|
||||
let cmd_name = validators::validate_command_name(&cmd_name)?;
|
||||
// 2. 校验 entry 是否是 Table
|
||||
if !cmd_entry.is_table() {
|
||||
return Err(validation_error(format!(
|
||||
"子命令 '{cmd_name}' 的配置必须是 Table,实际检测到: {}",
|
||||
cmd_entry.type_name()
|
||||
)));
|
||||
}
|
||||
// 3. 直接交由子命令解析,外层负责补充错误上下文
|
||||
let cmd_def = MrSubCommand::from_lua(cmd_entry, lua).map_err(|err| {
|
||||
validation_error(format!("子命令 '{cmd_name}' 配置解析失败:\n{err}"))
|
||||
})?;
|
||||
|
||||
commands.insert(cmd_name, cmd_def);
|
||||
}
|
||||
Ok(Self { commands })
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct MrSubCommand {
|
||||
/// 命令处理函数:必须是 Lua Function
|
||||
pub(crate) module: String,
|
||||
/// 命令行说明:必须是普通文本字符串
|
||||
pub(crate) description: String,
|
||||
}
|
||||
|
||||
impl FromLua for MrSubCommand {
|
||||
fn from_lua(value: Value, _lua: &Lua) -> mlua::Result<Self> {
|
||||
let table = match value {
|
||||
Value::Table(t) => t,
|
||||
other => {
|
||||
return Err(validation_error(format!(
|
||||
"配置必须是 Table,实际检测到: {}",
|
||||
other.type_name()
|
||||
)));
|
||||
}
|
||||
};
|
||||
// validators::LuaValidator::parse_command_entry(&tbl)
|
||||
for pair in table.pairs::<Value, Value>() {
|
||||
let (key, _) = pair?;
|
||||
let key_str = validators::validate_command_name(&key)?;
|
||||
|
||||
if !ALLOWED_KEYS.contains(&key_str.as_str()) {
|
||||
return Err(validation_error(format!(
|
||||
"包含未知字段 '{key_str}'。\n\
|
||||
==> 合法字段仅支持: {}\n\
|
||||
==> 请检查是否存在拼写手误(例如把 handler 写成了 handle/handlr)",
|
||||
ALLOWED_KEYS.join(", ")
|
||||
)));
|
||||
}
|
||||
}
|
||||
let handler_val = table.get::<Value>("handler")?;
|
||||
// let handler_func = match handler_val {
|
||||
// Value::Function(f) => f,
|
||||
// Value::Boolean(true) => {
|
||||
// return Err(validation_error(
|
||||
// "子命令 handler 返回了布尔值 true。\n\
|
||||
// ==>排查提示:脚本已成功加载,但未返回函数。\n\
|
||||
// ==>请在末尾加上 'return function(args) ... end'。",
|
||||
// ));
|
||||
// }
|
||||
// Value::Nil => {
|
||||
// return Err(validation_error(
|
||||
// "子命令缺少必填字段 handler(或对应脚本未返回函数)",
|
||||
// ));
|
||||
// }
|
||||
// other => {
|
||||
// return Err(validation_error(format!(
|
||||
// "'handler' 必须是函数 (function),实际类型是: {}",
|
||||
// other.type_name()
|
||||
// )));
|
||||
// }
|
||||
// };
|
||||
let module = match handler_val {
|
||||
Value::Table(t) if t.get::<bool>("__is_lazy_command").unwrap_or(false) => {
|
||||
t.get::<String>("module")?
|
||||
}
|
||||
Value::String(s) => s.to_str()?.to_string(), // 同时兼容直接写字符串的情况
|
||||
other => {
|
||||
return Err(mlua::Error::runtime(format!(
|
||||
"handler 必须通过 command(\"xxx\") 声明,实际为 {}",
|
||||
other.type_name()
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
// 可选字段: args(缺失或 nil 时默认空列表;保留空字符串参数,与提权路径的 "" 语义一致)
|
||||
let description_val = table.get::<Value>("description")?;
|
||||
let description = match description_val {
|
||||
Value::Nil => String::new(),
|
||||
Value::String(s) => s.to_str()?.to_string(),
|
||||
other => {
|
||||
return Err(validation_error(format!(
|
||||
"必须是字符串文本,实际类型是 {}",
|
||||
other.type_name()
|
||||
)));
|
||||
}
|
||||
};
|
||||
// let handler = lua.create_registry_value(handler_func)?;
|
||||
Ok(Self {
|
||||
module,
|
||||
description,
|
||||
})
|
||||
}
|
||||
}
|
||||
2
mirror-cli/src/commands/mod.rs
Normal file
2
mirror-cli/src/commands/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub(crate) mod builtin;
|
||||
pub(crate) mod dynamic;
|
||||
Reference in New Issue
Block a user