1use std::fs;
10use std::path::Path;
11
12use rhai::{AST, Array, Dynamic, Engine, Scope};
13
14use crate::error::{Error, IoCtx, Result};
15
16pub struct ScriptRewriter {
18 engine: Engine,
19 ast: AST,
20}
21
22impl std::fmt::Debug for ScriptRewriter {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 f.debug_struct("ScriptRewriter").finish_non_exhaustive()
25 }
26}
27
28impl ScriptRewriter {
29 pub fn from_source(source: &str) -> Result<Self> {
32 let engine = sandboxed_engine();
33 let ast = engine.compile(source).map_err(|e| Error::ScriptParse(e.to_string()))?;
34 Ok(Self { engine, ast })
35 }
36
37 pub fn from_file(path: &Path) -> Result<Self> {
39 let source = fs::read_to_string(path).io_ctx(path)?;
40 Self::from_source(&source)
41 }
42
43 pub fn fresh(&self) -> Self {
49 Self { engine: sandboxed_engine(), ast: self.ast.clone() }
50 }
51
52 pub fn replace(&self, captures: &[&str]) -> Result<String> {
55 let mut scope = Scope::new();
56 let arr: Array = captures.iter().map(|s| Dynamic::from((*s).to_string())).collect();
57 scope.push("captures", arr);
58 let full = captures.first().copied().unwrap_or("").to_string();
59 scope.push("whole", full);
60 let out: Dynamic = self
61 .engine
62 .eval_ast_with_scope(&mut scope, &self.ast)
63 .map_err(|e| Error::ScriptRuntime(e.to_string()))?;
64 Ok(out.to_string())
65 }
66}
67
68fn sandboxed_engine() -> Engine {
69 let mut engine = Engine::new();
70 engine.set_max_operations(1_000_000);
73 engine.set_max_string_size(1024 * 1024);
74 engine.set_max_array_size(1024);
75 engine.set_max_expr_depths(64, 64);
76 engine
77}
78
79#[cfg(test)]
80#[path = "script_tests.rs"]
81mod tests;