Skip to main content

robin/scripts/
script_runner.rs

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