herolib_rust/herodo/src/lib.rs

142 lines
4.5 KiB
Rust

//! Herodo - A Rhai script executor for SAL
//!
//! This library loads the Rhai engine, registers all SAL modules,
//! and executes Rhai scripts from a specified directory in sorted order.
use rhai::{Engine, Scope};
use std::error::Error;
use std::fs;
use std::path::{Path, PathBuf};
use std::process;
/// Run the herodo script executor with the given script path
///
/// # Arguments
///
/// * `script_path` - Path to a Rhai script file or directory containing Rhai scripts
///
/// # Returns
///
/// Result indicating success or failure
pub fn run(script_path: &str) -> Result<(), Box<dyn Error>> {
let path = Path::new(script_path);
// Check if the path exists
if !path.exists() {
eprintln!("Error: '{}' does not exist", script_path);
process::exit(1);
}
// Create a new Rhai engine
let mut engine = Engine::new();
// TODO: if we create a scope here we could clean up all the different functionsand types regsitered wit the engine
// We should generalize the way we add things to the scope for each module sepeartely
let mut scope = Scope::new();
// TODO: this should be done for the other clients as well (but not here of course, in each module)
let hetzner_client = sal::hetzner::api::Client::new(sal::hetzner::config::Config::from_env().unwrap());
scope.push("hetzner", hetzner_client);
// This makes it easy to call e.g. `hetzner.get_server()` or `mycelium.get_connected_peers()`
// --> without the need of manually created a client for each one first
// --> could be conditionally compiled to only use those who we need (we only push the things to the scope that we actually need to run the script)
// Register println function for output
engine.register_fn("println", |s: &str| println!("{}", s));
// Register all SAL modules with the engine
sal::rhai::register(&mut engine)?;
// Collect script files to execute
let script_files: Vec<PathBuf> = if path.is_file() {
// Single file
if let Some(extension) = path.extension() {
if extension != "rhai" {
eprintln!("Warning: '{}' does not have a .rhai extension", script_path);
}
}
vec![path.to_path_buf()]
} else if path.is_dir() {
// Directory - collect all .rhai files recursively and sort them
let mut files = Vec::new();
collect_rhai_files(path, &mut files)?;
if files.is_empty() {
eprintln!("No .rhai files found in directory: {}", script_path);
process::exit(1);
}
// Sort files for consistent execution order
files.sort();
files
} else {
eprintln!("Error: '{}' is neither a file nor a directory", script_path);
process::exit(1);
};
println!(
"Found {} Rhai script{} to execute:",
script_files.len(),
if script_files.len() == 1 { "" } else { "s" }
);
// Execute each script in sorted order
for script_file in script_files {
println!("\nExecuting: {}", script_file.display());
// Read the script content
let script = fs::read_to_string(&script_file)?;
// Execute the script
// match engine.eval::<rhai::Dynamic>(&script) {
// Ok(result) => {
// println!("Script executed successfully");
// if !result.is_unit() {
// println!("Result: {}", result);
// }
// }
// Err(err) => {
// eprintln!("Error executing script: {}", err);
// // Exit with error code when a script fails
// process::exit(1);
// }
// }
engine.run_with_scope(&mut scope, &script)?;
}
println!("\nAll scripts executed successfully!");
Ok(())
}
/// Recursively collect all .rhai files from a directory
///
/// # Arguments
///
/// * `dir` - Directory to search
/// * `files` - Vector to collect files into
///
/// # Returns
///
/// Result indicating success or failure
fn collect_rhai_files(dir: &Path, files: &mut Vec<PathBuf>) -> Result<(), Box<dyn Error>> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
// Recursively search subdirectories
collect_rhai_files(&path, files)?;
} else if path.is_file() {
// Check if it's a .rhai file
if let Some(extension) = path.extension() {
if extension == "rhai" {
files.push(path);
}
}
}
}
Ok(())
}