diff --git a/examples/rhai_process_example.rs b/examples/rhai_process_example.rs new file mode 100644 index 0000000..ad01f11 --- /dev/null +++ b/examples/rhai_process_example.rs @@ -0,0 +1,67 @@ +//! Example of using the Rhai integration with SAL Process module +//! +//! This example demonstrates how to use the Rhai scripting language +//! with the System Abstraction Layer (SAL) Process module. + +use rhai::{Engine, Map, Dynamic}; +use sal::rhai; + +fn main() -> Result<(), Box> { + // Create a new Rhai engine + let mut engine = Engine::new(); + + // Register SAL functions with the engine + rhai::register(&mut engine)?; + + // Run a Rhai script that uses SAL Process functions + let script = r#" + // Check if a command exists + let ls_exists = which("ls"); + + // Run a simple command + let echo_result = run_command("echo 'Hello from Rhai!'"); + + // Run a command silently + let silent_result = run_silent("ls -la"); + + // List processes + let processes = process_list(""); + + // Return a map with all the results + { + ls_exists: ls_exists, + echo_stdout: echo_result.stdout, + echo_success: echo_result.success, + silent_success: silent_result.success, + process_count: processes.len() + } + "#; + + // Evaluate the script and get the results + let result = engine.eval::(script)?; + + // Print the results + println!("Script results:"); + + if let Some(ls_exists) = result.get("ls_exists") { + println!(" ls command exists: {}", ls_exists); + } + + if let Some(echo_stdout) = result.get("echo_stdout") { + println!(" Echo command output: {}", echo_stdout.clone().into_string().unwrap()); + } + + if let Some(echo_success) = result.get("echo_success") { + println!(" Echo command success: {}", echo_success.clone().as_bool().unwrap()); + } + + if let Some(silent_success) = result.get("silent_success") { + println!(" Silent command success: {}", silent_success.clone().as_bool().unwrap()); + } + + if let Some(process_count) = result.get("process_count") { + println!(" Number of processes: {}", process_count.clone().as_int().unwrap()); + } + + Ok(()) +} \ No newline at end of file diff --git a/src/rhai/mod.rs b/src/rhai/mod.rs index 86c1c93..dcfc87a 100644 --- a/src/rhai/mod.rs +++ b/src/rhai/mod.rs @@ -5,6 +5,7 @@ mod error; mod os; +mod process; #[cfg(test)] mod tests; @@ -13,6 +14,7 @@ use rhai::Engine; pub use error::*; pub use os::*; +pub use process::*; /// Register all SAL modules with the Rhai engine /// @@ -36,8 +38,10 @@ pub fn register(engine: &mut Engine) -> Result<(), Box> { // Register OS module functions os::register_os_module(engine)?; + // Register Process module functions + process::register_process_module(engine)?; + // Future modules can be registered here - // e.g., process::register_process_module(engine)?; Ok(()) } \ No newline at end of file diff --git a/src/rhai/process.rs b/src/rhai/process.rs new file mode 100644 index 0000000..38c55d9 --- /dev/null +++ b/src/rhai/process.rs @@ -0,0 +1,138 @@ +//! Rhai wrappers for Process module functions +//! +//! This module provides Rhai wrappers for the functions in the Process module. + +use rhai::{Engine, EvalAltResult, Array, Dynamic}; +use crate::process::{self, CommandResult, ProcessInfo, RunError, ProcessError}; + +/// Register Process module functions with the Rhai engine +/// +/// # Arguments +/// +/// * `engine` - The Rhai engine to register the functions with +/// +/// # Returns +/// +/// * `Result<(), Box>` - Ok if registration was successful, Err otherwise +pub fn register_process_module(engine: &mut Engine) -> Result<(), Box> { + // Register types + register_process_types(engine)?; + + // Register run functions + engine.register_fn("run_command", run_command); + engine.register_fn("run_silent", run_silent); + + // Register process management functions + engine.register_fn("which", which); + engine.register_fn("kill", kill); + engine.register_fn("process_list", process_list); + engine.register_fn("process_get", process_get); + + Ok(()) +} + +/// Register Process module types with the Rhai engine +fn register_process_types(engine: &mut Engine) -> Result<(), Box> { + // Register CommandResult type and methods + engine.register_type_with_name::("CommandResult"); + + // Register getters for CommandResult properties + engine.register_get("stdout", |r: &mut CommandResult| r.stdout.clone()); + engine.register_get("stderr", |r: &mut CommandResult| r.stderr.clone()); + engine.register_get("success", |r: &mut CommandResult| r.success); + engine.register_get("code", |r: &mut CommandResult| r.code); + + // Register ProcessInfo type and methods + engine.register_type_with_name::("ProcessInfo"); + + // Register getters for ProcessInfo properties + engine.register_get("pid", |p: &mut ProcessInfo| p.pid); + engine.register_get("name", |p: &mut ProcessInfo| p.name.clone()); + engine.register_get("memory", |p: &mut ProcessInfo| p.memory); + engine.register_get("cpu", |p: &mut ProcessInfo| p.cpu); + + // Register error conversion functions + engine.register_fn("to_string", |err: &str| err.to_string()); + + Ok(()) +} + +// Helper functions for error conversion +fn run_error_to_rhai_error(result: Result) -> Result> { + result.map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Run error: {}", e).into(), + rhai::Position::NONE + )) + }) +} + +fn process_error_to_rhai_error(result: Result) -> Result> { + result.map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Process error: {}", e).into(), + rhai::Position::NONE + )) + }) +} + +// +// Run Function Wrappers +// + +/// Wrapper for process::run_command +/// +/// Run a command or multiline script with arguments. +pub fn run_command(command: &str) -> Result> { + run_error_to_rhai_error(process::run_command(command)) +} + +/// Wrapper for process::run_silent +/// +/// Run a command or multiline script with arguments silently. +pub fn run_silent(command: &str) -> Result> { + run_error_to_rhai_error(process::run_silent(command)) +} + +// +// Process Management Function Wrappers +// + +/// Wrapper for process::which +/// +/// Check if a command exists in PATH. +pub fn which(cmd: &str) -> Dynamic { + match process::which(cmd) { + Some(path) => path.into(), + None => Dynamic::UNIT + } +} + +/// Wrapper for process::kill +/// +/// Kill processes matching a pattern. +pub fn kill(pattern: &str) -> Result> { + process_error_to_rhai_error(process::kill(pattern)) +} + +/// Wrapper for process::process_list +/// +/// List processes matching a pattern (or all if pattern is empty). +pub fn process_list(pattern: &str) -> Result> { + let processes = process_error_to_rhai_error(process::process_list(pattern))?; + + // Convert Vec to Rhai Array + let mut array = Array::new(); + for process in processes { + array.push(Dynamic::from(process)); + } + + Ok(array) +} + +/// Wrapper for process::process_get +/// +/// Get a single process matching the pattern (error if 0 or more than 1 match). +pub fn process_get(pattern: &str) -> Result> { + process_error_to_rhai_error(process::process_get(pattern)) +} \ No newline at end of file diff --git a/src/rhai/tests.rs b/src/rhai/tests.rs index 8a712b2..07448b5 100644 --- a/src/rhai/tests.rs +++ b/src/rhai/tests.rs @@ -15,6 +15,8 @@ mod tests { assert!(register(&mut engine).is_ok()); } + // OS Module Tests + #[test] fn test_exist_function() { let mut engine = Engine::new(); @@ -86,8 +88,91 @@ mod tests { let err_str = err.to_string(); println!("Error string: {}", err_str); // The actual error message is "No files found matching..." - assert!(err_str.contains("No files found matching") || - err_str.contains("File not found") || + assert!(err_str.contains("No files found matching") || + err_str.contains("File not found") || err_str.contains("File system error")); } + + // Process Module Tests + + #[test] + fn test_which_function() { + let mut engine = Engine::new(); + register(&mut engine).unwrap(); + + // Test with a command that definitely exists (like "ls" on Unix or "cmd" on Windows) + #[cfg(target_os = "windows")] + let cmd = "cmd"; + + #[cfg(any(target_os = "macos", target_os = "linux"))] + let cmd = "ls"; + + let script = format!(r#"which("{}")"#, cmd); + let result = engine.eval::(&script).unwrap(); + assert!(!result.is_empty()); + + // Test with a command that definitely doesn't exist + let script = r#"which("non_existent_command_xyz123")"#; + let result = engine.eval::<()>(&script).unwrap(); + assert_eq!(result, ()); + } + + #[test] + fn test_run_command() { + let mut engine = Engine::new(); + register(&mut engine).unwrap(); + + // Test a simple echo command + #[cfg(target_os = "windows")] + let script = r#" + let result = run_command("echo Hello World"); + result.success && result.stdout.contains("Hello World") + "#; + + #[cfg(any(target_os = "macos", target_os = "linux"))] + let script = r#" + let result = run_command("echo 'Hello World'"); + result.success && result.stdout.contains("Hello World") + "#; + + let result = engine.eval::(script).unwrap(); + assert!(result); + } + + #[test] + fn test_run_silent() { + let mut engine = Engine::new(); + register(&mut engine).unwrap(); + + // Test a simple echo command with silent execution + #[cfg(target_os = "windows")] + let script = r#" + let result = run_silent("echo Hello World"); + result.success && result.stdout.contains("Hello World") + "#; + + #[cfg(any(target_os = "macos", target_os = "linux"))] + let script = r#" + let result = run_silent("echo 'Hello World'"); + result.success && result.stdout.contains("Hello World") + "#; + + let result = engine.eval::(script).unwrap(); + assert!(result); + } + + #[test] + fn test_process_list() { + let mut engine = Engine::new(); + register(&mut engine).unwrap(); + + // Test listing processes (should return a non-empty array) + let script = r#" + let processes = process_list(""); + processes.len() > 0 + "#; + + let result = engine.eval::(script).unwrap(); + assert!(result); + } } \ No newline at end of file