feat: CLI 补全至 GUI 功能 100% 对等 — 新增 9 条命令

新增: edit, move-up, move-down, clean, enable, disable, import, export, backup
core: registry.rs +clean_paths, fs.rs +import_paths +export_paths
CLI 特有增强: move-up/move-down 支持 --steps N 一次移动多格

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 23:43:05 +08:00
parent c181fe15d4
commit a553a16a64
3 changed files with 328 additions and 18 deletions
+102
View File
@@ -2,3 +2,105 @@
pub fn read_text_file(path: &str) -> Result<String, String> {
std::fs::read_to_string(path).map_err(|e| format!("无法读取文件: {}", e))
}
/// 导入路径文件(JSON / CSV / TXT),返回 (系统路径, 用户路径)
pub fn import_paths(path: &str, content: &str) -> Result<(Vec<String>, Vec<String>), String> {
let ext = std::path::Path::new(path)
.extension()
.map(|e| e.to_ascii_lowercase())
.unwrap_or_default();
let ext = ext.to_string_lossy();
match ext.as_ref() {
"json" => import_json(content),
"csv" => import_csv(content),
"txt" => import_txt(content),
_ => Err(format!("不支持的格式: .{}", ext)),
}
}
fn import_json(content: &str) -> Result<(Vec<String>, Vec<String>), String> {
#[derive(serde::Deserialize)]
struct ImportData {
#[serde(default)]
system: Vec<String>,
#[serde(default)]
user: Vec<String>,
}
let data: ImportData =
serde_json::from_str(content).map_err(|e| format!("JSON 解析失败: {}", e))?;
Ok((data.system, data.user))
}
fn import_csv(content: &str) -> Result<(Vec<String>, Vec<String>), String> {
let mut sys = Vec::new();
let mut usr = Vec::new();
for line in content.lines() {
let fields: Vec<&str> = line.split(',').collect();
if fields.len() >= 2 {
match fields[0].trim() {
"system" | "sys" => sys.push(fields[1].trim().to_string()),
"user" | "usr" => usr.push(fields[1].trim().to_string()),
_ => {}
}
}
}
if sys.is_empty() && usr.is_empty() {
return Err("CSV 文件中未找到有效路径".into());
}
Ok((sys, usr))
}
fn import_txt(content: &str) -> Result<(Vec<String>, Vec<String>), String> {
let paths: Vec<String> = content
.lines()
.map(|l| l.trim().to_string())
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.collect();
if paths.is_empty() {
return Err("TXT 文件中未找到路径".into());
}
// TXT 格式全部导入为用户路径
Ok((vec![], paths))
}
/// 导出 PATH 为指定格式字符串
pub fn export_paths(sys: &[String], usr: &[String], format: &str) -> String {
match format {
"json" => {
let data = serde_json::json!({
"version": "5.0.0",
"timestamp": chrono::Local::now().format("%Y-%m-%dT%H:%M:%S").to_string(),
"system": sys,
"user": usr,
});
serde_json::to_string_pretty(&data).unwrap_or_default()
}
"csv" => {
let mut out = String::from("type,path\n");
for p in sys {
out.push_str(&format!("system,{}\n", p));
}
for p in usr {
out.push_str(&format!("user,{}\n", p));
}
out
}
_ => {
let mut out = String::new();
if !sys.is_empty() {
out.push_str(&format!("# 系统 PATH ({})\n", sys.len()));
for p in sys {
out.push_str(&format!("{}\n", p));
}
}
if !usr.is_empty() {
out.push_str(&format!("# 用户 PATH ({})\n", usr.len()));
for p in usr {
out.push_str(&format!("{}\n", p));
}
}
out
}
}
}
+23
View File
@@ -81,6 +81,29 @@ fn join_path(paths: &[String]) -> String {
.join(";")
}
/// 清理路径列表:移除不存在的目录 + 重复路径(保留首次出现)
/// 返回 (保留的路径, 被移除的路径)
pub fn clean_paths(paths: Vec<String>) -> (Vec<String>, Vec<String>) {
use std::collections::HashSet;
let mut seen: HashSet<String> = HashSet::new();
let mut kept = Vec::new();
let mut removed = Vec::new();
for p in paths {
let key = p.trim().to_lowercase();
if seen.contains(&key) {
removed.push(p);
continue;
}
seen.insert(key);
if !p.contains('%') && !std::path::Path::new(&p).is_dir() {
removed.push(p);
continue;
}
kept.push(p);
}
(kept, removed)
}
#[cfg(test)]
mod tests {
use super::*;