1use rhai::{Engine, AST, Dynamic, Scope};
6use std::path::Path;
7use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
14pub struct ScriptResult {
15 pub env_vars: HashMap<String, String>,
17 pub success: bool,
19 pub error: Option<String>,
21 pub pre_build_continue: bool,
23 pub post_build_output: Option<String>,
25 pub features: HashMap<String, bool>,
27 pub workbench: Option<String>,
29}
30
31#[derive(Debug, Clone)]
32pub struct ScriptContext {
33 pub profile_name: String,
35 pub cwd: String,
37 pub manifest_dir: String,
39 pub target_triple: Option<String>,
41 pub workbench_type: Option<String>,
43 pub features: HashMap<String, bool>,
45}
46
47pub fn execute_profile_script(
63 engine: &Engine,
64 script_path: &str,
65 context: &ScriptContext,
66) -> Result<ScriptResult, String> {
67 let ast = load_script(engine, script_path)?;
68 let mut scope = create_scope(context);
69
70 let execution_result = engine.run_ast_with_scope(&mut scope, &ast);
72
73 let mut result = ScriptResult {
74 env_vars: HashMap::new(),
75 success: execution_result.is_ok(),
76 error: None,
77 pre_build_continue: true,
78 post_build_output: None,
79 features: HashMap::new(),
80 workbench: None,
81 };
82
83 if let Err(e) = execution_result {
84 result.error = Some(e.to_string());
85 result.pre_build_continue = false;
86 return Ok(result);
87 }
88
89 if let Ok(env_map) = engine.call_fn(&mut scope, &ast, "get_env_vars", ()) {
91 result.env_vars = extract_env_map(env_map);
92 }
93
94 if let Ok(feature_map) = engine.call_fn(&mut scope, &ast, "get_features", ()) {
96 result.features = extract_feature_map(feature_map);
97 }
98
99 if let Ok(workbench) = engine.call_fn::<String>(&mut scope, &ast, "get_workbench", ()) {
101 result.workbench = Some(workbench);
102 }
103
104 if let Ok(continue_result) = engine.call_fn::<bool>(&mut scope, &ast, "pre_build_continue", ()) {
106 result.pre_build_continue = continue_result;
107 }
108
109 if let Ok(output) = engine.call_fn::<String>(&mut scope, &ast, "post_build_output", ()) {
111 result.post_build_output = Some(output);
112 }
113
114 Ok(result)
115}
116
117pub fn load_script(engine: &Engine, script_path: &str) -> Result<AST, String> {
128 if !Path::new(script_path).exists() {
129 return Err(format!("Script file not found: {}", script_path));
130 }
131
132 let content = std::fs::read_to_string(script_path)
133 .map_err(|e| format!("Failed to read script: {}", e))?;
134
135 let ast = engine.compile(&content)
136 .map_err(|e| format!("Failed to compile script: {}", e))?;
137
138 Ok(ast)
139}
140
141pub fn create_engine() -> Engine {
147 let mut engine = Engine::new();
148
149 engine.register_fn("env", |name: &str| -> String {
151 std::env::var(name).unwrap_or_default()
152 });
153
154 engine.register_fn("env_or", |name: &str, default: &str| -> String {
155 std::env::var(name).unwrap_or_else(|_| default.to_string())
156 });
157
158 engine.register_fn("set_env", |name: &str, value: &str| {
159 unsafe { std::env::set_var(name, value); }
164 });
165
166 engine.register_fn("log", |message: &str| {
167 println!("[Rhai] {}", message);
168 });
169
170 engine.register_fn("log_error", |message: &str| {
171 eprintln!("[Rhai ERROR] {}", message);
172 });
173
174 engine.register_fn("log_warn", |message: &str| {
175 eprintln!("[Rhai WARN] {}", message);
176 });
177
178 engine.register_fn("path_join", |base: &str, suffix: &str| -> String {
180 Path::new(base).join(suffix).to_string_lossy().to_string()
181 });
182
183 engine.register_fn("path_exists", |path: &str| -> bool {
184 Path::new(path).exists()
185 });
186
187 engine.register_fn("to_uppercase", |s: &str| -> String {
189 s.to_uppercase()
190 });
191
192 engine.register_fn("to_lowercase", |s: &str| -> String {
193 s.to_lowercase()
194 });
195
196 engine
197}
198
199fn create_scope(context: &ScriptContext) -> Scope<'_> {
205 let mut scope = Scope::new();
206
207 scope.push("profile_name", context.profile_name.clone());
208 scope.push("cwd", context.cwd.clone());
209 scope.push("manifest_dir", context.manifest_dir.clone());
210 scope.push("target_triple", context.target_triple.clone().unwrap_or_default());
211 scope.push("workbench_type", context.workbench_type.clone().unwrap_or_default());
212
213 let mut features_map = rhai::Map::new();
215 for (key, value) in &context.features {
216 features_map.insert(key.into(), (*value).into());
217 }
218 scope.push("features", features_map);
219
220 scope
221}
222
223fn extract_env_map(dynamic: Dynamic) -> HashMap<String, String> {
225 let mut env_map = HashMap::new();
226
227 if let Some(map) = dynamic.try_cast::<rhai::Map>() {
228 for (key, value) in map {
229 if value.is_string() {
230 env_map.insert(key.to_string(), value.to_string());
231 } else if value.is_int() {
232 env_map.insert(key.to_string(), value.as_int().unwrap_or(0).to_string());
233 } else if value.is_bool() {
234 env_map.insert(key.to_string(), value.as_bool().unwrap_or(false).to_string());
235 } else {
236 env_map.insert(key.to_string(), value.to_string());
237 }
238 }
239 }
240
241 env_map
242}
243
244fn extract_feature_map(dynamic: Dynamic) -> HashMap<String, bool> {
246 let mut feature_map = HashMap::new();
247
248 if let Some(map) = dynamic.try_cast::<rhai::Map>() {
249 for (key, value) in map {
250 if value.is_bool() {
251 feature_map.insert(key.to_string(), value.as_bool().unwrap_or(false));
252 }
253 }
254 }
255
256 feature_map
257}
258
259#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_create_engine() {
269 let engine = create_engine();
270 assert!(engine.compile("let x = 1;").is_ok());
272 }
273
274 #[test]
275 fn test_extract_env_map() {
276 let mut map = rhai::Map::new();
277 map.insert("KEY1".into(), "value1".into());
278 map.insert("KEY2".into(), 42.into());
279 map.insert("KEY3".into(), true.into());
280
281 let dynamic = Dynamic::from(map);
282 let env = extract_env_map(dynamic);
283
284 assert_eq!(env.get("KEY1"), Some(&"value1".to_string()));
285 assert_eq!(env.get("KEY2"), Some(&"42".to_string()));
286 assert_eq!(env.get("KEY3"), Some(&"true".to_string()));
287 }
288
289 #[test]
290 fn test_extract_feature_map() {
291 let mut map = rhai::Map::new();
292 map.insert("feature1".into(), true.into());
293 map.insert("feature2".into(), false.into());
294
295 let dynamic = Dynamic::from(map);
296 let features = extract_feature_map(dynamic);
297
298 assert_eq!(features.get("feature1"), Some(&true));
299 assert_eq!(features.get("feature2"), Some(&false));
300 }
301}