eoka_runner/config/
params.rs1use crate::{Error, Result};
2use serde::Deserialize;
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, Default)]
7pub struct Params {
8 values: HashMap<String, String>,
9}
10
11impl Params {
12 pub fn new() -> Self {
14 Self::default()
15 }
16
17 pub fn set(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
19 self.values.insert(key.into(), value.into());
20 self
21 }
22
23 pub fn get(&self, key: &str) -> Option<&str> {
25 self.values.get(key).map(|s| s.as_str())
26 }
27
28 pub fn is_empty(&self) -> bool {
30 self.values.is_empty()
31 }
32
33 pub fn from_args(args: &[String]) -> Result<Self> {
35 let mut params = Self::new();
36 for arg in args {
37 let (key, value) = arg.split_once('=').ok_or_else(|| {
38 Error::Config(format!("invalid param '{}', expected key=value", arg))
39 })?;
40 params.values.insert(key.to_string(), value.to_string());
41 }
42 Ok(params)
43 }
44}
45
46#[derive(Debug, Clone, Deserialize)]
48pub struct ParamDef {
49 #[serde(default)]
51 pub required: bool,
52
53 pub default: Option<String>,
55
56 pub description: Option<String>,
58}
59
60pub fn substitute(
62 template: &str,
63 params: &Params,
64 defs: &HashMap<String, ParamDef>,
65) -> Result<String> {
66 let mut result = template.to_string();
67 let mut start = 0;
68
69 while let Some(var_start) = result[start..].find("${") {
70 let var_start = start + var_start;
71 let Some(var_end) = result[var_start..].find('}') else {
72 break;
73 };
74 let var_end = var_start + var_end;
75
76 let var_name = &result[var_start + 2..var_end];
77
78 let value = if let Some(v) = params.get(var_name) {
79 v.to_string()
80 } else if let Some(def) = defs.get(var_name) {
81 if let Some(ref default) = def.default {
82 default.clone()
83 } else if def.required {
84 return Err(Error::Config(format!(
85 "missing required parameter: {}",
86 var_name
87 )));
88 } else {
89 String::new()
91 }
92 } else {
93 start = var_end + 1;
95 continue;
96 };
97
98 result.replace_range(var_start..=var_end, &value);
99 start = var_start + value.len();
100 }
101
102 Ok(result)
103}
104
105pub fn substitute_value(
107 value: &mut serde_yaml::Value,
108 params: &Params,
109 defs: &HashMap<String, ParamDef>,
110) -> Result<()> {
111 match value {
112 serde_yaml::Value::String(s) => {
113 *s = substitute(s, params, defs)?;
114 }
115 serde_yaml::Value::Mapping(map) => {
116 for (_, v) in map.iter_mut() {
117 substitute_value(v, params, defs)?;
118 }
119 }
120 serde_yaml::Value::Sequence(seq) => {
121 for v in seq.iter_mut() {
122 substitute_value(v, params, defs)?;
123 }
124 }
125 _ => {}
126 }
127 Ok(())
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn test_substitute_simple() {
136 let params = Params::new().set("name", "world");
137 let defs = HashMap::new();
138 let result = substitute("hello ${name}!", ¶ms, &defs).unwrap();
139 assert_eq!(result, "hello world!");
140 }
141
142 #[test]
143 fn test_substitute_multiple() {
144 let params = Params::new().set("a", "1").set("b", "2");
145 let defs = HashMap::new();
146 let result = substitute("${a} + ${b} = 3", ¶ms, &defs).unwrap();
147 assert_eq!(result, "1 + 2 = 3");
148 }
149
150 #[test]
151 fn test_substitute_default() {
152 let params = Params::new();
153 let mut defs = HashMap::new();
154 defs.insert(
155 "name".to_string(),
156 ParamDef {
157 required: false,
158 default: Some("default".to_string()),
159 description: None,
160 },
161 );
162 let result = substitute("hello ${name}", ¶ms, &defs).unwrap();
163 assert_eq!(result, "hello default");
164 }
165
166 #[test]
167 fn test_substitute_required_missing() {
168 let params = Params::new();
169 let mut defs = HashMap::new();
170 defs.insert(
171 "name".to_string(),
172 ParamDef {
173 required: true,
174 default: None,
175 description: None,
176 },
177 );
178 let result = substitute("hello ${name}", ¶ms, &defs);
179 assert!(result.is_err());
180 }
181
182 #[test]
183 fn test_params_from_args() {
184 let args = vec!["user=alice".to_string(), "pass=secret".to_string()];
185 let params = Params::from_args(&args).unwrap();
186 assert_eq!(params.get("user"), Some("alice"));
187 assert_eq!(params.get("pass"), Some("secret"));
188 }
189}