1use std::{collections::HashMap, path::Path};
6
7use rhai::{AST, Dynamic, Engine, Scope};
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 ExecuteProfileScript(engine:&Engine, script_path:&str, context:&ScriptContext) -> Result<ScriptResult, String> {
63 let Ast = LoadScript(engine, script_path)?;
64 let mut Scope = CreateScope(context);
65
66 let ExecutionResult = engine.run_ast_with_scope(&mut Scope, &Ast);
68
69 let mut Result = ScriptResult {
70 env_vars:HashMap::new(),
71 success:ExecutionResult.is_ok(),
72 error:None,
73 pre_build_continue:true,
74 post_build_output:None,
75 features:HashMap::new(),
76 workbench:None,
77 };
78
79 if let Err(Error) = ExecutionResult {
80 Result.error = Some(Error.to_string());
81 Result.pre_build_continue = false;
82 return Ok(Result);
83 }
84
85 if let Ok(EnvMap) = engine.call_fn(&mut Scope, &Ast, "get_env_vars", ()) {
87 Result.env_vars = ExtractEnvMap(EnvMap);
88 }
89
90 if let Ok(FeatureMap) = engine.call_fn(&mut Scope, &Ast, "get_features", ()) {
92 Result.features = ExtractFeatureMap(FeatureMap);
93 }
94
95 if let Ok(Workbench) = engine.call_fn::<String>(&mut Scope, &Ast, "get_workbench", ()) {
97 Result.workbench = Some(Workbench);
98 }
99
100 if let Ok(ContinueResult) = engine.call_fn::<bool>(&mut Scope, &Ast, "pre_build_continue", ()) {
102 Result.pre_build_continue = ContinueResult;
103 }
104
105 if let Ok(Output) = engine.call_fn::<String>(&mut Scope, &Ast, "post_build_output", ()) {
107 Result.post_build_output = Some(Output);
108 }
109
110 Ok(Result)
111}
112
113pub fn LoadScript(engine:&Engine, script_path:&str) -> Result<AST, String> {
124 if !Path::new(script_path).exists() {
125 return Err(format!("Script file not found: {}", script_path));
126 }
127
128 let Content = std::fs::read_to_string(script_path).map_err(|Error| format!("Failed to read script: {}", Error))?;
129
130 let Ast = engine
131 .compile(&Content)
132 .map_err(|Error| format!("Failed to compile script: {}", Error))?;
133
134 Ok(Ast)
135}
136
137pub fn CreateEngine() -> Engine {
143 let mut Engine = Engine::new();
144
145 Engine.register_fn("env", |name:&str| -> String { std::env::var(name).unwrap_or_default() });
147
148 Engine.register_fn("env_or", |name:&str, default:&str| -> String {
149 std::env::var(name).unwrap_or_else(|_| default.to_string())
150 });
151
152 Engine.register_fn("set_env", |name:&str, value:&str| {
153 unsafe {
158 std::env::set_var(name, value);
159 }
160 });
161
162 Engine.register_fn("log", |message:&str| {
163 println!("[Rhai] {}", message);
164 });
165
166 Engine.register_fn("log_error", |message:&str| {
167 eprintln!("[Rhai ERROR] {}", message);
168 });
169
170 Engine.register_fn("log_warn", |message:&str| {
171 eprintln!("[Rhai WARN] {}", message);
172 });
173
174 Engine.register_fn("path_join", |base:&str, suffix:&str| -> String {
176 Path::new(base).join(suffix).to_string_lossy().to_string()
177 });
178
179 Engine.register_fn("path_exists", |path:&str| -> bool { Path::new(path).exists() });
180
181 Engine.register_fn("to_uppercase", |s:&str| -> String { s.to_uppercase() });
183
184 Engine.register_fn("to_lowercase", |s:&str| -> String { s.to_lowercase() });
185
186 Engine
187}
188
189fn CreateScope(context:&ScriptContext) -> Scope<'_> {
195 let mut Scope = Scope::new();
196
197 Scope.push("profile_name", context.profile_name.clone());
198 Scope.push("cwd", context.cwd.clone());
199 Scope.push("manifest_dir", context.manifest_dir.clone());
200 Scope.push("target_triple", context.target_triple.clone().unwrap_or_default());
201 Scope.push("workbench_type", context.workbench_type.clone().unwrap_or_default());
202
203 let mut FeaturesMap = rhai::Map::new();
205 for (Key, Value) in &context.features {
206 FeaturesMap.insert(Key.into(), (*Value).into());
207 }
208 Scope.push("features", FeaturesMap);
209
210 Scope
211}
212
213fn ExtractEnvMap(Dynamic:Dynamic) -> HashMap<String, String> {
215 let mut EnvMap = HashMap::new();
216
217 if let Some(Map) = Dynamic.try_cast::<rhai::Map>() {
218 for (Key, Value) in Map {
219 if Value.is_string() {
220 EnvMap.insert(Key.to_string(), Value.to_string());
221 } else if Value.is_int() {
222 EnvMap.insert(Key.to_string(), Value.as_int().unwrap_or(0).to_string());
223 } else if Value.is_bool() {
224 EnvMap.insert(Key.to_string(), Value.as_bool().unwrap_or(false).to_string());
225 } else {
226 EnvMap.insert(Key.to_string(), Value.to_string());
227 }
228 }
229 }
230
231 EnvMap
232}
233
234fn ExtractFeatureMap(Dynamic:Dynamic) -> HashMap<String, bool> {
236 let mut FeatureMap = HashMap::new();
237
238 if let Some(Map) = Dynamic.try_cast::<rhai::Map>() {
239 for (Key, Value) in Map {
240 if Value.is_bool() {
241 FeatureMap.insert(Key.to_string(), Value.as_bool().unwrap_or(false));
242 }
243 }
244 }
245
246 FeatureMap
247}
248
249#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn test_create_engine() {
259 let Engine = CreateEngine();
260 assert!(Engine.compile("let x = 1;").is_ok());
262 }
263
264 #[test]
265 fn test_extract_env_map() {
266 let mut map = rhai::Map::new();
267 map.insert("KEY1".into(), "value1".into());
268 map.insert("KEY2".into(), 42.into());
269 map.insert("KEY3".into(), true.into());
270
271 let Dynamic = Dynamic::from(map);
272 let Env = ExtractEnvMap(Dynamic);
273
274 assert_eq!(Env.get("KEY1"), Some(&"value1".to_string()));
275 assert_eq!(Env.get("KEY2"), Some(&"42".to_string()));
276 assert_eq!(Env.get("KEY3"), Some(&"true".to_string()));
277 }
278
279 #[test]
280 fn test_extract_feature_map() {
281 let mut map = rhai::Map::new();
282 map.insert("feature1".into(), true.into());
283 map.insert("feature2".into(), false.into());
284
285 let Dynamic = Dynamic::from(map);
286 let Features = ExtractFeatureMap(Dynamic);
287
288 assert_eq!(Features.get("feature1"), Some(&true));
289 assert_eq!(Features.get("feature2"), Some(&false));
290 }
291}