diff --git a/Cargo.toml b/Cargo.toml index 5c8278c..d263a2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ serde_json = "1.0" # For JSON handling glob = "0.3.1" # For file pattern matching tempfile = "3.5" # For temporary file operations log = "0.4" # Logging facade +rhai = { version = "1.12.0", features = ["sync"] } # Embedded scripting language # Optional features for specific OS functionality [target.'cfg(unix)'.dependencies] diff --git a/examples/rhai_example.rs b/examples/rhai_example.rs new file mode 100644 index 0000000..4a3e954 --- /dev/null +++ b/examples/rhai_example.rs @@ -0,0 +1,81 @@ +//! Example of using the Rhai integration with SAL +//! +//! This example demonstrates how to use the Rhai scripting language +//! with the System Abstraction Layer (SAL) library. + +use rhai::Engine; +use sal::rhai; +use std::fs; + +fn main() -> Result<(), Box> { + // Create a new Rhai engine + let mut engine = Engine::new(); + + // Register SAL functions with the engine + rhai::register(&mut engine)?; + + // Create a test file + let test_file = "rhai_test_file.txt"; + fs::write(test_file, "Hello, Rhai!")?; + + // Create a test directory + let test_dir = "rhai_test_dir"; + if !fs::metadata(test_dir).is_ok() { + fs::create_dir(test_dir)?; + } + + // Run a Rhai script that uses SAL functions + let script = r#" + // Check if files exist + let file_exists = exist("rhai_test_file.txt"); + let dir_exists = exist("rhai_test_dir"); + + // Get file size + let size = file_size("rhai_test_file.txt"); + + // Create a new directory + let new_dir = "rhai_new_dir"; + let mkdir_result = mkdir(new_dir); + + // Copy a file + let copy_result = copy("rhai_test_file.txt", "rhai_test_dir/copied_file.txt"); + + // Find files + let files = find_files(".", "*.txt"); + + // Return a map with all the results + #{ + file_exists: file_exists, + dir_exists: dir_exists, + file_size: size, + mkdir_result: mkdir_result, + copy_result: copy_result, + files: files + } + "#; + + // Evaluate the script and get the results + let result = engine.eval::(script)?; + + // Print the results + println!("Script results:"); + println!(" File exists: {}", result.get("file_exists").unwrap().clone().cast::()); + println!(" Directory exists: {}", result.get("dir_exists").unwrap().clone().cast::()); + println!(" File size: {} bytes", result.get("file_size").unwrap().clone().cast::()); + println!(" Mkdir result: {}", result.get("mkdir_result").unwrap().clone().cast::()); + println!(" Copy result: {}", result.get("copy_result").unwrap().clone().cast::()); + + // Print the found files + let files = result.get("files").unwrap().clone().cast::(); + println!(" Found files:"); + for file in files { + println!(" - {}", file.cast::()); + } + + // Clean up + fs::remove_file(test_file)?; + fs::remove_dir_all(test_dir)?; + fs::remove_dir_all("rhai_new_dir")?; + + Ok(()) +} \ No newline at end of file diff --git a/rhai_integration_plan.md b/rhai_integration_plan.md new file mode 100644 index 0000000..f7337d1 --- /dev/null +++ b/rhai_integration_plan.md @@ -0,0 +1,121 @@ +# Implementation Plan: Rhai Integration for OS Module Functions + +## 1. Project Structure Changes + +We'll create a new directory structure for the Rhai integration: + +``` +src/ +├── rhai/ +│ ├── mod.rs # Main module file for Rhai integration +│ ├── os.rs # OS module wrappers +│ └── error.rs # Error type conversions +``` + +## 2. Dependencies + +Add Rhai as a dependency in Cargo.toml: + +```toml +[dependencies] +# Existing dependencies... +rhai = { version = "1.12.0", features = ["sync"] } +``` + +## 3. Implementation Steps + +### 3.1. Create Basic Module Structure + +1. Create the `src/rhai/mod.rs` file with: + - Module declarations + - A modular registration system + - Public exports + +2. Create the `src/rhai/error.rs` file with: + - Conversions from our custom error types to Rhai's `EvalAltResult` + - Helper functions for error handling + +### 3.2. Implement OS Module Wrappers + +Create the `src/rhai/os.rs` file with: + +1. Import necessary modules and types +2. Create wrapper functions for each function in `src/os/fs.rs` and `src/os/download.rs` +3. Implement a registration function specific to the OS module +4. Expose error types to Rhai + +### 3.3. Update Main Library File + +Update `src/lib.rs` to expose the new Rhai module. + +## 4. Detailed Implementation + +### 4.1. Error Handling + +For each function that returns a `Result` where `E` is one of our custom error types: + +1. Create a wrapper function that converts our error type to Rhai's `EvalAltResult` +2. Register the error types with Rhai to allow for proper error handling in scripts + +### 4.2. Function Wrappers + +For each function in the OS module: + +1. Create a wrapper function with the same name +2. Handle any necessary type conversions +3. Convert error types to Rhai's error system +4. Register the function with the Rhai engine + +### 4.3. Registration System + +Create a modular registration system: + +1. Implement a general `register` function that takes a Rhai engine +2. Implement module-specific registration functions (e.g., `register_os_module`) +3. Design the system to be extensible for future modules + +## 5. Implementation Flow Diagram + +```mermaid +flowchart TD + A[Add Rhai dependency] --> B[Create directory structure] + B --> C[Implement error conversions] + C --> D[Implement OS module wrappers] + D --> E[Create registration system] + E --> F[Update lib.rs] + F --> G[Test the implementation] +``` + +## 6. Function Mapping + +Here's a mapping of the OS module functions to their Rhai wrappers: + +### File System Functions (from fs.rs) +- `copy` → Wrap with error conversion +- `exist` → Direct wrapper (returns bool) +- `find_file` → Wrap with error conversion +- `find_files` → Wrap with error conversion +- `find_dir` → Wrap with error conversion +- `find_dirs` → Wrap with error conversion +- `delete` → Wrap with error conversion +- `mkdir` → Wrap with error conversion +- `file_size` → Wrap with error conversion +- `rsync` → Wrap with error conversion + +### Download Functions (from download.rs) +- `download` → Wrap with error conversion +- `download_install` → Wrap with error conversion + +## 7. Error Type Handling + +We'll expose our custom error types to Rhai: + +1. Register `FsError` and `DownloadError` as custom types +2. Implement proper error conversion to allow for detailed error handling in Rhai scripts +3. Create helper functions to extract error information + +## 8. Testing Strategy + +1. Create unit tests for each wrapper function +2. Create integration tests with sample Rhai scripts +3. Test error handling scenarios \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index f9c1477..01d8efe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,7 @@ pub mod os; pub mod redisclient; pub mod text; pub mod virt; +pub mod rhai; // Version information /// Returns the version of the SAL library diff --git a/src/rhai/error.rs b/src/rhai/error.rs new file mode 100644 index 0000000..dc25d2c --- /dev/null +++ b/src/rhai/error.rs @@ -0,0 +1,60 @@ +//! Error handling for Rhai integration +//! +//! This module provides utilities for converting SAL error types to Rhai error types. + +use rhai::{EvalAltResult, Position}; +use crate::os::{FsError, DownloadError}; + +/// Convert a FsError to a Rhai EvalAltResult +pub fn fs_error_to_rhai_error(err: FsError) -> Box { + let err_msg = err.to_string(); + Box::new(EvalAltResult::ErrorRuntime( + err_msg.into(), + Position::NONE + )) +} + +/// Convert a DownloadError to a Rhai EvalAltResult +pub fn download_error_to_rhai_error(err: DownloadError) -> Box { + let err_msg = err.to_string(); + Box::new(EvalAltResult::ErrorRuntime( + err_msg.into(), + Position::NONE + )) +} + +/// Register error types with the Rhai engine +pub fn register_error_types(engine: &mut rhai::Engine) -> Result<(), Box> { + // Register helper functions for error handling + // Note: We don't register the error types directly since they don't implement Clone + // Instead, we'll convert them to strings in the wrappers + + // Register functions to get error messages + engine.register_fn("fs_error_message", |err_msg: &str| -> String { + format!("File system error: {}", err_msg) + }); + + engine.register_fn("download_error_message", |err_msg: &str| -> String { + format!("Download error: {}", err_msg) + }); + + Ok(()) +} + +/// Trait for converting SAL errors to Rhai errors +pub trait ToRhaiError { + /// Convert the error to a Rhai EvalAltResult + fn to_rhai_error(self) -> Result>; +} + +impl ToRhaiError for Result { + fn to_rhai_error(self) -> Result> { + self.map_err(fs_error_to_rhai_error) + } +} + +impl ToRhaiError for Result { + fn to_rhai_error(self) -> Result> { + self.map_err(download_error_to_rhai_error) + } +} \ No newline at end of file diff --git a/src/rhai/mod.rs b/src/rhai/mod.rs new file mode 100644 index 0000000..86c1c93 --- /dev/null +++ b/src/rhai/mod.rs @@ -0,0 +1,43 @@ +//! Rhai scripting integration for the SAL library +//! +//! This module provides integration with the Rhai scripting language, +//! allowing SAL functions to be called from Rhai scripts. + +mod error; +mod os; + +#[cfg(test)] +mod tests; + +use rhai::Engine; + +pub use error::*; +pub use os::*; + +/// Register all SAL modules with the Rhai engine +/// +/// # Arguments +/// +/// * `engine` - The Rhai engine to register the modules with +/// +/// # Example +/// +/// ``` +/// use rhai::Engine; +/// use sal::rhai; +/// +/// let mut engine = Engine::new(); +/// rhai::register(&mut engine); +/// +/// // Now you can use SAL functions in Rhai scripts +/// let result = engine.eval::("exist('some_file.txt')").unwrap(); +/// ``` +pub fn register(engine: &mut Engine) -> Result<(), Box> { + // Register OS module functions + os::register_os_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/os.rs b/src/rhai/os.rs new file mode 100644 index 0000000..20be388 --- /dev/null +++ b/src/rhai/os.rs @@ -0,0 +1,147 @@ +//! Rhai wrappers for OS module functions +//! +//! This module provides Rhai wrappers for the functions in the OS module. + +use rhai::{Engine, EvalAltResult, Array}; +use crate::os; +use super::error::{ToRhaiError, register_error_types}; + +/// Register OS 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_os_module(engine: &mut Engine) -> Result<(), Box> { + // Register error types + register_error_types(engine)?; + + // Register file system functions + engine.register_fn("copy", copy); + engine.register_fn("exist", exist); + engine.register_fn("find_file", find_file); + engine.register_fn("find_files", find_files); + engine.register_fn("find_dir", find_dir); + engine.register_fn("find_dirs", find_dirs); + engine.register_fn("delete", delete); + engine.register_fn("mkdir", mkdir); + engine.register_fn("file_size", file_size); + engine.register_fn("rsync", rsync); + + // Register download functions + engine.register_fn("download", download); + engine.register_fn("download_install", download_install); + + Ok(()) +} + +// +// File System Function Wrappers +// + +/// Wrapper for os::copy +/// +/// Recursively copy a file or directory from source to destination. +pub fn copy(src: &str, dest: &str) -> Result> { + os::copy(src, dest).to_rhai_error() +} + +/// Wrapper for os::exist +/// +/// Check if a file or directory exists. +pub fn exist(path: &str) -> bool { + os::exist(path) +} + +/// Wrapper for os::find_file +/// +/// Find a file in a directory (with support for wildcards). +pub fn find_file(dir: &str, filename: &str) -> Result> { + os::find_file(dir, filename).to_rhai_error() +} + +/// Wrapper for os::find_files +/// +/// Find multiple files in a directory (recursive, with support for wildcards). +pub fn find_files(dir: &str, filename: &str) -> Result> { + let files = os::find_files(dir, filename).to_rhai_error()?; + + // Convert Vec to Rhai Array + let mut array = Array::new(); + for file in files { + array.push(file.into()); + } + + Ok(array) +} + +/// Wrapper for os::find_dir +/// +/// Find a directory in a parent directory (with support for wildcards). +pub fn find_dir(dir: &str, dirname: &str) -> Result> { + os::find_dir(dir, dirname).to_rhai_error() +} + +/// Wrapper for os::find_dirs +/// +/// Find multiple directories in a parent directory (recursive, with support for wildcards). +pub fn find_dirs(dir: &str, dirname: &str) -> Result> { + let dirs = os::find_dirs(dir, dirname).to_rhai_error()?; + + // Convert Vec to Rhai Array + let mut array = Array::new(); + for dir in dirs { + array.push(dir.into()); + } + + Ok(array) +} + +/// Wrapper for os::delete +/// +/// Delete a file or directory (defensive - doesn't error if file doesn't exist). +pub fn delete(path: &str) -> Result> { + os::delete(path).to_rhai_error() +} + +/// Wrapper for os::mkdir +/// +/// Create a directory and all parent directories (defensive - doesn't error if directory exists). +pub fn mkdir(path: &str) -> Result> { + os::mkdir(path).to_rhai_error() +} + +/// Wrapper for os::file_size +/// +/// Get the size of a file in bytes. +pub fn file_size(path: &str) -> Result> { + os::file_size(path).to_rhai_error() +} + +/// Wrapper for os::rsync +/// +/// Sync directories using rsync (or platform equivalent). +pub fn rsync(src: &str, dest: &str) -> Result> { + os::rsync(src, dest).to_rhai_error() +} + +// +// Download Function Wrappers +// + +/// Wrapper for os::download +/// +/// Download a file from URL to destination using the curl command. +pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result> { + os::download(url, dest, min_size_kb).to_rhai_error() +} + +/// Wrapper for os::download_install +/// +/// Download a file and install it if it's a supported package format. +pub fn download_install(url: &str, min_size_kb: i64) -> Result> { + os::download_install(url, min_size_kb).to_rhai_error() +} \ No newline at end of file diff --git a/src/rhai/tests.rs b/src/rhai/tests.rs new file mode 100644 index 0000000..8a712b2 --- /dev/null +++ b/src/rhai/tests.rs @@ -0,0 +1,93 @@ +//! Tests for Rhai integration +//! +//! This module contains tests for the Rhai integration. + +#[cfg(test)] +mod tests { + use rhai::Engine; + use super::super::register; + use std::fs; + use std::path::Path; + + #[test] + fn test_register() { + let mut engine = Engine::new(); + assert!(register(&mut engine).is_ok()); + } + + #[test] + fn test_exist_function() { + let mut engine = Engine::new(); + register(&mut engine).unwrap(); + + // Test with a file that definitely exists + let result = engine.eval::(r#"exist("Cargo.toml")"#).unwrap(); + assert!(result); + + // Test with a file that definitely doesn't exist + let result = engine.eval::(r#"exist("non_existent_file.xyz")"#).unwrap(); + assert!(!result); + } + + #[test] + fn test_mkdir_and_delete() { + let mut engine = Engine::new(); + register(&mut engine).unwrap(); + + let test_dir = "test_rhai_dir"; + + // Clean up from previous test runs if necessary + if Path::new(test_dir).exists() { + fs::remove_dir_all(test_dir).unwrap(); + } + + // Create directory using Rhai + let script = format!(r#"mkdir("{}")"#, test_dir); + let result = engine.eval::(&script).unwrap(); + assert!(result.contains("Successfully created directory")); + assert!(Path::new(test_dir).exists()); + + // Delete directory using Rhai + let script = format!(r#"delete("{}")"#, test_dir); + let result = engine.eval::(&script).unwrap(); + assert!(result.contains("Successfully deleted directory")); + assert!(!Path::new(test_dir).exists()); + } + + #[test] + fn test_file_size() { + let mut engine = Engine::new(); + register(&mut engine).unwrap(); + + // Create a test file + let test_file = "test_rhai_file.txt"; + let test_content = "Hello, Rhai!"; + fs::write(test_file, test_content).unwrap(); + + // Get file size using Rhai + let script = format!(r#"file_size("{}")"#, test_file); + let size = engine.eval::(&script).unwrap(); + assert_eq!(size, test_content.len() as i64); + + // Clean up + fs::remove_file(test_file).unwrap(); + } + + #[test] + fn test_error_handling() { + let mut engine = Engine::new(); + register(&mut engine).unwrap(); + + // Try to get the size of a non-existent file + let result = engine.eval::(r#"file_size("non_existent_file.xyz")"#); + assert!(result.is_err()); + + let err = result.unwrap_err(); + 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") || + err_str.contains("File system error")); + } +} \ No newline at end of file