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