robin/utils/
command_utils.rs

1use anyhow::{Result, anyhow};
2use regex::Regex;
3use serde_json;
4
5pub fn split_command_and_args(args: &[String]) -> (String, Vec<String>) {
6    if args.is_empty() {
7        return (String::new(), vec![]);
8    }
9
10    let mut command_parts = Vec::new();
11    let mut var_args = Vec::new();
12    let mut found_args = false;
13
14    for arg in args {
15        if arg.starts_with("--") {
16            found_args = true;
17            var_args.push(arg.clone());
18        } else if !found_args {
19            command_parts.push(arg.clone());
20        } else {
21            var_args.push(arg.clone());
22        }
23    }
24
25    (command_parts.join(" "), var_args)
26}
27
28pub fn replace_variables(script: &serde_json::Value, args: &[String]) -> Result<serde_json::Value> {
29    match script {
30        serde_json::Value::String(cmd) => {
31            let replaced = replace_variables_in_string(cmd, args)?;
32            Ok(serde_json::Value::String(replaced))
33        },
34        serde_json::Value::Array(commands) => {
35            let mut replaced_commands = Vec::new();
36            for cmd in commands {
37                if let Some(cmd_str) = cmd.as_str() {
38                    let replaced = replace_variables_in_string(cmd_str, args)?;
39                    replaced_commands.push(serde_json::Value::String(replaced));
40                } else {
41                    replaced_commands.push(cmd.clone());
42                }
43            }
44            Ok(serde_json::Value::Array(replaced_commands))
45        },
46        _ => Ok(script.clone()),
47    }
48}
49
50fn replace_variables_in_string(script: &str, args: &[String]) -> Result<String> {
51    let var_regex = Regex::new(r"\{\{(\w+)(?:=([^}]+|\[[^\]]+\]))?\}\}").unwrap();
52    let mut result = script.to_string();
53    
54    for capture in var_regex.captures_iter(script) {
55        let full_match = &capture[0];
56        let var_name = &capture[1];
57        let default_or_enum = capture.get(2).map(|m| m.as_str()).unwrap_or("");
58        let var_pattern = format!("--{}=", var_name);
59
60        if default_or_enum.starts_with('[') && default_or_enum.ends_with(']') {
61            let allowed_values: Vec<&str> = default_or_enum[1..default_or_enum.len()-1]
62                .split(',')
63                .map(|s| s.trim())
64                .collect();
65            
66            let value = args.iter()
67                .find(|arg| arg.starts_with(&var_pattern))
68                .map(|arg| arg.trim_start_matches(&var_pattern))
69                .ok_or_else(|| anyhow!("Missing required variable: {}", var_name))?;
70
71            if !allowed_values.contains(&value) {
72                return Err(anyhow!("Value '{}' for {} must be one of: {}", 
73                    value, var_name, allowed_values.join(", ")));
74            }
75            
76            result = result.replace(full_match, value);
77        } else {
78            let value = args.iter()
79                .find(|arg| arg.starts_with(&var_pattern))
80                .map(|arg| arg.trim_start_matches(&var_pattern))
81                .or(if default_or_enum.is_empty() { None } else { Some(default_or_enum) })
82                .ok_or_else(|| anyhow!("Missing required variable: {}", var_name))?;
83
84            result = result.replace(full_match, value);
85        }
86    }
87    
88    Ok(result)
89}