feat: Migrate SAL to Cargo workspace
Some checks failed
Rhai Tests / Run Rhai Tests (push) Has been cancelled
Rhai Tests / Run Rhai Tests (pull_request) Has been cancelled

- Migrate individual modules to independent crates
- Refactor dependencies for improved modularity
- Update build system and testing infrastructure
- Update documentation to reflect new structure
This commit is contained in:
Mahmoud-Emad
2025-06-24 12:39:18 +03:00
parent 8012a66250
commit e125bb6511
54 changed files with 1196 additions and 1582 deletions

View File

@@ -15,11 +15,11 @@ path = "src/main.rs"
[dependencies]
# Core dependencies for herodo binary
env_logger = "0.11.8"
rhai = { version = "1.12.0", features = ["sync"] }
env_logger = { workspace = true }
rhai = { workspace = true }
# SAL library for Rhai module registration
sal = { path = ".." }
[dev-dependencies]
tempfile = "3.5"
tempfile = { workspace = true }

View File

@@ -49,32 +49,34 @@ pub fn run(script_path: &str) -> Result<(), Box<dyn Error>> {
// 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" });
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) => {
@@ -82,7 +84,7 @@ pub fn run(script_path: &str) -> Result<(), Box<dyn Error>> {
if !result.is_unit() {
println!("Result: {}", result);
}
},
}
Err(err) => {
eprintln!("Error executing script: {}", err);
// Exit with error code when a script fails
@@ -109,7 +111,7 @@ fn collect_rhai_files(dir: &Path, files: &mut Vec<PathBuf>) -> Result<(), Box<dy
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)?;
@@ -122,6 +124,6 @@ fn collect_rhai_files(dir: &Path, files: &mut Vec<PathBuf>) -> Result<(), Box<dy
}
}
}
Ok(())
}

View File

@@ -12,14 +12,18 @@ use tempfile::TempDir;
fn test_simple_script_execution() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let script_path = temp_dir.path().join("test.rhai");
// Create a simple test script
fs::write(&script_path, r#"
fs::write(
&script_path,
r#"
println("Hello from herodo test!");
let result = 42;
result
"#).expect("Failed to write test script");
"#,
)
.expect("Failed to write test script");
// Execute the script
let result = herodo::run(script_path.to_str().unwrap());
assert!(result.is_ok(), "Script execution should succeed");
@@ -29,23 +33,35 @@ fn test_simple_script_execution() {
#[test]
fn test_directory_script_execution() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
// Create multiple test scripts
fs::write(temp_dir.path().join("01_first.rhai"), r#"
fs::write(
temp_dir.path().join("01_first.rhai"),
r#"
println("First script executing");
let first = 1;
"#).expect("Failed to write first script");
fs::write(temp_dir.path().join("02_second.rhai"), r#"
"#,
)
.expect("Failed to write first script");
fs::write(
temp_dir.path().join("02_second.rhai"),
r#"
println("Second script executing");
let second = 2;
"#).expect("Failed to write second script");
fs::write(temp_dir.path().join("03_third.rhai"), r#"
"#,
)
.expect("Failed to write second script");
fs::write(
temp_dir.path().join("03_third.rhai"),
r#"
println("Third script executing");
let third = 3;
"#).expect("Failed to write third script");
"#,
)
.expect("Failed to write third script");
// Execute all scripts in the directory
let result = herodo::run(temp_dir.path().to_str().unwrap());
assert!(result.is_ok(), "Directory script execution should succeed");
@@ -57,7 +73,7 @@ fn test_nonexistent_path_handling() {
// This test verifies error handling but herodo::run calls process::exit
// In a real scenario, we would need to refactor herodo to return errors
// instead of calling process::exit for better testability
// For now, we test that the path validation logic works
let nonexistent_path = "/this/path/does/not/exist";
let path = Path::new(nonexistent_path);
@@ -69,9 +85,11 @@ fn test_nonexistent_path_handling() {
fn test_sal_module_integration() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let script_path = temp_dir.path().join("sal_test.rhai");
// Create a script that uses SAL functions
fs::write(&script_path, r#"
fs::write(
&script_path,
r#"
println("Testing SAL module integration");
// Test file existence check (should work with temp directory)
@@ -84,52 +102,71 @@ fn test_sal_module_integration() {
println("Trimmed text: '" + trimmed + "'");
println("SAL integration test completed");
"#).expect("Failed to write SAL test script");
"#,
)
.expect("Failed to write SAL test script");
// Execute the script
let result = herodo::run(script_path.to_str().unwrap());
assert!(result.is_ok(), "SAL integration script should execute successfully");
assert!(
result.is_ok(),
"SAL integration script should execute successfully"
);
}
/// Test script execution with subdirectories
#[test]
fn test_recursive_directory_execution() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
// Create subdirectory
let sub_dir = temp_dir.path().join("subdir");
fs::create_dir(&sub_dir).expect("Failed to create subdirectory");
// Create scripts in main directory
fs::write(temp_dir.path().join("main.rhai"), r#"
fs::write(
temp_dir.path().join("main.rhai"),
r#"
println("Main directory script");
"#).expect("Failed to write main script");
"#,
)
.expect("Failed to write main script");
// Create scripts in subdirectory
fs::write(sub_dir.join("sub.rhai"), r#"
fs::write(
sub_dir.join("sub.rhai"),
r#"
println("Subdirectory script");
"#).expect("Failed to write sub script");
"#,
)
.expect("Failed to write sub script");
// Execute all scripts recursively
let result = herodo::run(temp_dir.path().to_str().unwrap());
assert!(result.is_ok(), "Recursive directory execution should succeed");
assert!(
result.is_ok(),
"Recursive directory execution should succeed"
);
}
/// Test that herodo handles empty directories gracefully
#[test]
fn test_empty_directory_handling() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
// Create an empty subdirectory
let empty_dir = temp_dir.path().join("empty");
fs::create_dir(&empty_dir).expect("Failed to create empty directory");
// This should handle the empty directory case
// Note: herodo::run will call process::exit(1) for empty directories
// In a production refactor, this should return an error instead
let path = empty_dir.to_str().unwrap();
let path_obj = Path::new(path);
assert!(path_obj.is_dir(), "Empty directory should exist and be a directory");
assert!(
path_obj.is_dir(),
"Empty directory should exist and be a directory"
);
}
/// Test script with syntax errors
@@ -137,39 +174,49 @@ fn test_empty_directory_handling() {
fn test_syntax_error_handling() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let script_path = temp_dir.path().join("syntax_error.rhai");
// Create a script with syntax errors
fs::write(&script_path, r#"
fs::write(
&script_path,
r#"
println("This script has syntax errors");
let invalid syntax here;
missing_function_call(;
"#).expect("Failed to write syntax error script");
"#,
)
.expect("Failed to write syntax error script");
// Note: herodo::run will call process::exit(1) on script errors
// In a production refactor, this should return an error instead
// For now, we just verify the file exists and can be read
assert!(script_path.exists(), "Syntax error script should exist");
let content = fs::read_to_string(&script_path).expect("Should be able to read script");
assert!(content.contains("syntax errors"), "Script should contain expected content");
assert!(
content.contains("syntax errors"),
"Script should contain expected content"
);
}
/// Test file extension validation
#[test]
fn test_file_extension_validation() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
// Create files with different extensions
let rhai_file = temp_dir.path().join("valid.rhai");
let txt_file = temp_dir.path().join("invalid.txt");
fs::write(&rhai_file, "println(\"Valid rhai file\");").expect("Failed to write rhai file");
fs::write(&txt_file, "This is not a rhai file").expect("Failed to write txt file");
// Verify file extensions
assert_eq!(rhai_file.extension().unwrap(), "rhai");
assert_eq!(txt_file.extension().unwrap(), "txt");
// herodo should execute .rhai files and warn about non-.rhai files
let result = herodo::run(rhai_file.to_str().unwrap());
assert!(result.is_ok(), "Valid .rhai file should execute successfully");
assert!(
result.is_ok(),
"Valid .rhai file should execute successfully"
);
}