robin/utils/
command_utils.rs1use 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}