robin/scripts/
script_runner.rs

1use std::process::Command;
2use std::path::PathBuf;
3use anyhow::{Result, Context, anyhow};
4use colored::*;
5use inquire::Select;
6use serde_json;
7
8use crate::config::RobinConfig;
9use crate::utils::send_notification;
10
11pub fn run_script(script: &serde_json::Value, notify: bool) -> Result<()> {
12    let start_time = std::time::Instant::now();
13    
14    match script {
15        serde_json::Value::String(cmd) => {
16            let status = if cfg!(target_os = "windows") {
17                Command::new("cmd")
18                    .args(["/C", cmd])
19                    .status()
20            } else {
21                Command::new("sh")
22                    .arg("-c")
23                    .arg(cmd)
24                    .status()
25            }.with_context(|| format!("Failed to execute script: {}", cmd))?;
26
27            if notify {
28                let duration = start_time.elapsed();
29                let success = status.success();
30                let message = if success {
31                    format!("Completed in {:.1}s", duration.as_secs_f32())
32                } else {
33                    "Failed".to_string()
34                };
35                
36                send_notification(
37                    "Robin",
38                    &format!("Command '{}' {}", cmd.split_whitespace().next().unwrap_or(cmd), message),
39                    success,
40                )?;
41            }
42
43            if !status.success() {
44                println!("{}", "Script failed!".red());
45                return Err(anyhow!("Script failed: {}", cmd));
46            }
47            Ok(())
48        },
49        serde_json::Value::Array(commands) => {
50            for cmd in commands {
51                if let Some(cmd_str) = cmd.as_str() {
52                    let status = if cfg!(target_os = "windows") {
53                        Command::new("cmd")
54                            .args(["/C", cmd_str])
55                            .status()
56                    } else {
57                        Command::new("sh")
58                            .arg("-c")
59                            .arg(cmd_str)
60                            .status()
61                    }.with_context(|| format!("Failed to execute script: {}", cmd_str))?;
62
63                    if !status.success() {
64                        println!("{}", format!("Script failed: {}", cmd_str).red());
65                        return Err(anyhow!("Script failed: {}", cmd_str));
66                    }
67                }
68            }
69
70            if notify {
71                let duration = start_time.elapsed();
72                send_notification(
73                    "Robin",
74                    &format!("Command sequence completed in {:.1}s", duration.as_secs_f32()),
75                    true,
76                )?;
77            }
78            Ok(())
79        },
80        _ => Err(anyhow!("Invalid script type: must be string or array of strings")),
81    }
82}
83
84pub fn list_commands(config_path: &PathBuf) -> Result<()> {
85    let config = RobinConfig::load(config_path)
86        .with_context(|| "No .robin.json found. Run 'robin init' first")?;
87
88    // Find the longest command name for padding
89    let max_len = config.scripts.keys()
90        .map(|name| name.len())
91        .max()
92        .unwrap_or(0);
93
94    // Convert to sorted vec for alphabetical ordering
95    let mut commands: Vec<_> = config.scripts.iter().collect();
96    commands.sort_by(|a, b| a.0.cmp(b.0));
97
98    for (name, script) in commands {
99        match script {
100            serde_json::Value::String(cmd) => {
101                println!("==> {:<width$} # {}", name.blue(), cmd, width = max_len);
102            },
103            serde_json::Value::Array(commands) => {
104                println!("==> {:<width$} # [", name.blue(), width = max_len);
105                for cmd in commands {
106                    if let Some(cmd_str) = cmd.as_str() {
107                        println!("       {}", cmd_str);
108                    }
109                }
110                println!("     ]");
111            },
112            _ => println!("==> {:<width$} # <invalid script type>", name.blue(), width = max_len),
113        }
114    }
115
116    Ok(())
117}
118
119pub fn interactive_mode(config_path: &PathBuf) -> Result<()> {
120    let config = RobinConfig::load(config_path)
121        .with_context(|| "No .robin.json found. Run 'robin init' first")?;
122
123    let commands: Vec<String> = config.scripts.keys().cloned().collect();
124    if commands.is_empty() {
125        println!("{}", "No commands available".red());
126        return Ok(());
127    }
128
129    let selection = Select::new("Select a command to run:", commands).prompt()?;
130    if let Some(script) = config.scripts.get(&selection) {
131        run_script(script, false)?;
132    }
133
134    Ok(())
135}