Skip to main content

j_cli/command/
script.rs

1use crate::config::YamlConfig;
2use crate::constants::{section, shell};
3use crate::{error, info};
4use std::fs;
5
6/// 生成脚本末尾的「等待用户按键」模板内容
7fn wait_for_key_template() -> String {
8    if std::env::consts::OS == shell::WINDOWS_OS {
9        "echo.\necho 脚本执行完毕,按任意键退出...\npause >nul".to_string()
10    } else {
11        "echo ''\necho '\\033[32m✅ 脚本执行完毕,按回车键退出...\\033[0m'\nread _".to_string()
12    }
13}
14
15/// 处理 concat 命令: j concat <script_name> ["<script_content>"]
16/// 创建一个脚本文件并注册为别名,脚本持久化在 ~/.jdata/scripts/ 下
17/// 如果没有提供 content,则打开 TUI 编辑器让用户输入
18pub fn handle_concat(name: &str, content: &[String], config: &mut YamlConfig) {
19    // 检查脚本名是否已存在 → 如果存在则进入编辑模式
20    if config.contains(section::PATH, name) {
21        // 获取已有脚本路径
22        let existing_path = match config
23            .get_property(section::SCRIPT, name)
24            .or_else(|| config.get_property(section::PATH, name))
25        {
26            Some(p) => p.clone(),
27            None => {
28                error!("❌ 别名 {{{}}} 已存在,但未找到对应的脚本路径", name);
29                return;
30            }
31        };
32
33        // 读取已有脚本内容
34        let existing_content = match fs::read_to_string(&existing_path) {
35            Ok(c) => c,
36            Err(e) => {
37                error!("❌ 读取已有脚本文件失败: {} (路径: {})", e, existing_path);
38                return;
39            }
40        };
41
42        // 打开 TUI 编辑器让用户修改
43        let initial_lines: Vec<String> = existing_content.lines().map(|l| l.to_string()).collect();
44        match crate::tui::editor::open_multiline_editor_with_content(
45            &format!("📝 编辑脚本: {}", name),
46            &initial_lines,
47        ) {
48            Ok(Some(new_content)) => {
49                if new_content.trim().is_empty() {
50                    error!("⚠️ 脚本内容为空,未保存修改");
51                    return;
52                }
53                // 写回脚本文件
54                match fs::write(&existing_path, &new_content) {
55                    Ok(_) => info!("✅ 脚本 {{{}}} 已更新,路径: {}", name, existing_path),
56                    Err(e) => error!("💥 写入脚本文件失败: {}", e),
57                }
58            }
59            Ok(None) => {
60                info!("已取消编辑脚本");
61            }
62            Err(e) => {
63                error!("❌ 编辑器启动失败: {}", e);
64            }
65        }
66        return;
67    }
68
69    // 获取脚本内容:有参数则直接使用,无参数则打开编辑器
70    let script_content = if content.is_empty() {
71        // 无内容参数:打开 TUI 编辑器
72        let initial_lines = vec![
73            "#!/bin/bash".to_string(),
74            "".to_string(),
75            "# 在此编写脚本内容...".to_string(),
76            "".to_string(),
77            "# --- 以下为等待按键模板(可删除) ---".to_string(),
78            wait_for_key_template(),
79        ];
80
81        match crate::tui::editor::open_multiline_editor_with_content(
82            &format!("📝 编写脚本: {}", name),
83            &initial_lines,
84        ) {
85            Ok(Some(text)) => text,
86            Ok(None) => {
87                info!("已取消创建脚本");
88                return;
89            }
90            Err(e) => {
91                error!("❌ 编辑器启动失败: {}", e);
92                return;
93            }
94        }
95    } else {
96        // 有内容参数:拼接并去除两端引号
97        let text = content.join(" ");
98        text.trim()
99            .trim_start_matches('"')
100            .trim_end_matches('"')
101            .to_string()
102    };
103
104    if script_content.trim().is_empty() {
105        error!("⚠️ 脚本内容为空,无法创建");
106        return;
107    }
108
109    // 脚本统一存储在 ~/.jdata/scripts/ 下
110    let scripts_dir = YamlConfig::scripts_dir();
111
112    // 生成脚本文件路径
113    let ext = if std::env::consts::OS == shell::WINDOWS_OS {
114        ".cmd"
115    } else {
116        ".sh"
117    };
118    let script_path = scripts_dir.join(format!("{}{}", name, ext));
119    let script_path_str = script_path.to_string_lossy().to_string();
120
121    // 确保目录存在(scripts_dir() 已保证,这里冗余保护)
122    if let Some(parent) = script_path.parent() {
123        if let Err(e) = fs::create_dir_all(parent) {
124            error!("❌ 创建目录失败: {}", e);
125            return;
126        }
127    }
128
129    // 写入脚本内容
130    match fs::write(&script_path, &script_content) {
131        Ok(_) => {
132            info!("🎉 文件创建成功: {}", script_path_str);
133        }
134        Err(e) => {
135            error!("💥 写入脚本文件失败: {}", e);
136            return;
137        }
138    }
139
140    // 设置执行权限(非 Windows)
141    #[cfg(unix)]
142    {
143        use std::os::unix::fs::PermissionsExt;
144        if let Ok(metadata) = fs::metadata(&script_path) {
145            let mut perms = metadata.permissions();
146            perms.set_mode(perms.mode() | 0o111); // 添加执行权限
147            if let Err(e) = fs::set_permissions(&script_path, perms) {
148                error!("❌ 设置执行权限失败: {}", e);
149            } else {
150                info!("🔧 已为脚本 {{{}}} 设置执行权限", name);
151            }
152        }
153    }
154
155    // 注册到 path 和 script
156    config.set_property(section::PATH, name, &script_path_str);
157    config.set_property(section::SCRIPT, name, &script_path_str);
158
159    info!("✅ 成功创建脚本 {{{}}},路径: {}", name, script_path_str);
160}