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

@@ -11,26 +11,22 @@ categories = ["os", "filesystem", "api-bindings"]
[dependencies]
# Core dependencies for file system operations
dirs = "6.0.0"
glob = "0.3.1"
libc = "0.2"
dirs = { workspace = true }
glob = { workspace = true }
libc = { workspace = true }
# Error handling
thiserror = "2.0.12"
thiserror = { workspace = true }
# Rhai scripting support
rhai = { version = "1.12.0", features = ["sync"] }
rhai = { workspace = true }
# Optional features for specific OS functionality
[target.'cfg(unix)'.dependencies]
nix = "0.30.1"
nix = { workspace = true }
[target.'cfg(windows)'.dependencies]
windows = { version = "0.61.1", features = [
"Win32_Foundation",
"Win32_System_Threading",
"Win32_Storage_FileSystem",
] }
windows = { workspace = true }
[dev-dependencies]
tempfile = "3.5"
tempfile = { workspace = true }

View File

@@ -81,7 +81,7 @@ impl Error for DownloadError {
* # Examples
*
* ```no_run
* use sal::os::download;
* use sal_os::download;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Download a file with no minimum size requirement
@@ -242,7 +242,7 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
* # Examples
*
* ```no_run
* use sal::os::download_file;
* use sal_os::download_file;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Download a file with no minimum size requirement
@@ -335,7 +335,7 @@ pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String,
* # Examples
*
* ```no_run
* use sal::os::chmod_exec;
* use sal_os::chmod_exec;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Make a file executable
@@ -413,7 +413,7 @@ pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
* # Examples
*
* ```no_run
* use sal::os::download_install;
* use sal_os::download_install;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Download and install a .deb package

View File

@@ -1,13 +1,13 @@
use dirs;
use libc;
use std::error::Error;
use std::fmt;
use std::fs;
use std::io;
use std::path::Path;
use std::process::Command;
use libc;
use dirs;
#[cfg(not(target_os = "windows"))]
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::process::Command;
// Define a custom error type for file system operations
#[derive(Debug)]
@@ -299,7 +299,7 @@ fn copy_internal(src: &str, dest: &str, make_executable: bool) -> Result<String,
* # Examples
*
* ```no_run
* use sal::os::copy;
* use sal_os::copy;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Copy a single file
@@ -334,7 +334,7 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
* # Examples
*
* ```no_run
* use sal::os::copy_bin;
* use sal_os::copy_bin;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Copy a binary
@@ -373,7 +373,7 @@ pub fn copy_bin(src: &str) -> Result<String, FsError> {
* # Examples
*
* ```
* use sal::os::exist;
* use sal_os::exist;
*
* if exist("file.txt") {
* println!("File exists");
@@ -400,7 +400,7 @@ pub fn exist(path: &str) -> bool {
* # Examples
*
* ```no_run
* use sal::os::find_file;
* use sal_os::find_file;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* let file_path = find_file("/path/to/dir", "*.txt")?;
@@ -457,7 +457,7 @@ pub fn find_file(dir: &str, filename: &str) -> Result<String, FsError> {
* # Examples
*
* ```no_run
* use sal::os::find_files;
* use sal_os::find_files;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* let files = find_files("/path/to/dir", "*.txt")?;
@@ -505,7 +505,7 @@ pub fn find_files(dir: &str, filename: &str) -> Result<Vec<String>, FsError> {
* # Examples
*
* ```no_run
* use sal::os::find_dir;
* use sal_os::find_dir;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* let dir_path = find_dir("/path/to/parent", "sub*")?;
@@ -557,7 +557,7 @@ pub fn find_dir(dir: &str, dirname: &str) -> Result<String, FsError> {
* # Examples
*
* ```no_run
* use sal::os::find_dirs;
* use sal_os::find_dirs;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* let dirs = find_dirs("/path/to/parent", "sub*")?;
@@ -604,7 +604,7 @@ pub fn find_dirs(dir: &str, dirname: &str) -> Result<Vec<String>, FsError> {
* # Examples
*
* ```
* use sal::os::delete;
* use sal_os::delete;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Delete a file
@@ -652,7 +652,7 @@ pub fn delete(path: &str) -> Result<String, FsError> {
* # Examples
*
* ```
* use sal::os::mkdir;
* use sal_os::mkdir;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* let result = mkdir("path/to/new/directory")?;
@@ -693,7 +693,7 @@ pub fn mkdir(path: &str) -> Result<String, FsError> {
* # Examples
*
* ```no_run
* use sal::os::file_size;
* use sal_os::file_size;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* let size = file_size("file.txt")?;
@@ -736,7 +736,7 @@ pub fn file_size(path: &str) -> Result<i64, FsError> {
* # Examples
*
* ```no_run
* use sal::os::rsync;
* use sal_os::rsync;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* let result = rsync("source_dir/", "backup_dir/")?;
@@ -802,7 +802,7 @@ pub fn rsync(src: &str, dest: &str) -> Result<String, FsError> {
* # Examples
*
* ```no_run
* use sal::os::chdir;
* use sal_os::chdir;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* let result = chdir("/path/to/directory")?;
@@ -845,7 +845,7 @@ pub fn chdir(path: &str) -> Result<String, FsError> {
* # Examples
*
* ```no_run
* use sal::os::file_read;
* use sal_os::file_read;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* let content = file_read("file.txt")?;
@@ -887,7 +887,7 @@ pub fn file_read(path: &str) -> Result<String, FsError> {
* # Examples
*
* ```
* use sal::os::file_write;
* use sal_os::file_write;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* let result = file_write("file.txt", "Hello, world!")?;
@@ -926,7 +926,7 @@ pub fn file_write(path: &str, content: &str) -> Result<String, FsError> {
* # Examples
*
* ```
* use sal::os::file_write_append;
* use sal_os::file_write_append;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* let result = file_write_append("log.txt", "New log entry\n")?;
@@ -974,7 +974,7 @@ pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> {
* # Examples
*
* ```no_run
* use sal::os::mv;
* use sal_os::mv;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Move a file
@@ -1089,7 +1089,7 @@ pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
* # Examples
*
* ```
* use sal::os::which;
* use sal_os::which;
*
* let cmd_path = which("ls");
* if cmd_path != "" {
@@ -1133,15 +1133,15 @@ pub fn which(command: &str) -> String {
*
* # Examples
*
* ```
* use sal::os::cmd_ensure_exists;
* ```no_run
* use sal_os::cmd_ensure_exists;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Check if a single command exists
* let result = cmd_ensure_exists("nerdctl")?;
* let result = cmd_ensure_exists("ls")?;
*
* // Check if multiple commands exist
* let result = cmd_ensure_exists("nerdctl,docker,containerd")?;
* let result = cmd_ensure_exists("ls,cat,grep")?;
*
* Ok(())
* }

View File

@@ -6,14 +6,14 @@ use tempfile::TempDir;
fn test_exist() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
// Test directory exists
assert!(fs::exist(temp_path.to_str().unwrap()));
// Test file doesn't exist
let non_existent = temp_path.join("non_existent.txt");
assert!(!fs::exist(non_existent.to_str().unwrap()));
// Create a file and test it exists
let test_file = temp_path.join("test.txt");
std_fs::write(&test_file, "test content").unwrap();
@@ -24,17 +24,17 @@ fn test_exist() {
fn test_mkdir() {
let temp_dir = TempDir::new().unwrap();
let new_dir = temp_dir.path().join("new_directory");
// Directory shouldn't exist initially
assert!(!fs::exist(new_dir.to_str().unwrap()));
// Create directory
let result = fs::mkdir(new_dir.to_str().unwrap());
assert!(result.is_ok());
// Directory should now exist
assert!(fs::exist(new_dir.to_str().unwrap()));
// Creating existing directory should not error (defensive)
let result2 = fs::mkdir(new_dir.to_str().unwrap());
assert!(result2.is_ok());
@@ -45,14 +45,14 @@ fn test_file_write_and_read() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test_write.txt");
let content = "Hello, World!";
// Write file
let write_result = fs::file_write(test_file.to_str().unwrap(), content);
assert!(write_result.is_ok());
// File should exist
assert!(fs::exist(test_file.to_str().unwrap()));
// Read file
let read_result = fs::file_read(test_file.to_str().unwrap());
assert!(read_result.is_ok());
@@ -63,22 +63,25 @@ fn test_file_write_and_read() {
fn test_file_write_append() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test_append.txt");
// Write initial content
let initial_content = "Line 1\n";
let append_content = "Line 2\n";
let write_result = fs::file_write(test_file.to_str().unwrap(), initial_content);
assert!(write_result.is_ok());
// Append content
let append_result = fs::file_write_append(test_file.to_str().unwrap(), append_content);
assert!(append_result.is_ok());
// Read and verify
let read_result = fs::file_read(test_file.to_str().unwrap());
assert!(read_result.is_ok());
assert_eq!(read_result.unwrap(), format!("{}{}", initial_content, append_content));
assert_eq!(
read_result.unwrap(),
format!("{}{}", initial_content, append_content)
);
}
#[test]
@@ -86,10 +89,10 @@ fn test_file_size() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test_size.txt");
let content = "Hello, World!"; // 13 bytes
// Write file
fs::file_write(test_file.to_str().unwrap(), content).unwrap();
// Check size
let size_result = fs::file_size(test_file.to_str().unwrap());
assert!(size_result.is_ok());
@@ -100,18 +103,18 @@ fn test_file_size() {
fn test_delete() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test_delete.txt");
// Create file
fs::file_write(test_file.to_str().unwrap(), "test").unwrap();
assert!(fs::exist(test_file.to_str().unwrap()));
// Delete file
let delete_result = fs::delete(test_file.to_str().unwrap());
assert!(delete_result.is_ok());
// File should no longer exist
assert!(!fs::exist(test_file.to_str().unwrap()));
// Deleting non-existent file should not error (defensive)
let delete_result2 = fs::delete(test_file.to_str().unwrap());
assert!(delete_result2.is_ok());
@@ -123,14 +126,14 @@ fn test_copy() {
let source_file = temp_dir.path().join("source.txt");
let dest_file = temp_dir.path().join("dest.txt");
let content = "Copy test content";
// Create source file
fs::file_write(source_file.to_str().unwrap(), content).unwrap();
// Copy file
let copy_result = fs::copy(source_file.to_str().unwrap(), dest_file.to_str().unwrap());
assert!(copy_result.is_ok());
// Destination should exist and have same content
assert!(fs::exist(dest_file.to_str().unwrap()));
let dest_content = fs::file_read(dest_file.to_str().unwrap()).unwrap();
@@ -143,18 +146,18 @@ fn test_mv() {
let source_file = temp_dir.path().join("source_mv.txt");
let dest_file = temp_dir.path().join("dest_mv.txt");
let content = "Move test content";
// Create source file
fs::file_write(source_file.to_str().unwrap(), content).unwrap();
// Move file
let mv_result = fs::mv(source_file.to_str().unwrap(), dest_file.to_str().unwrap());
assert!(mv_result.is_ok());
// Source should no longer exist, destination should exist
assert!(!fs::exist(source_file.to_str().unwrap()));
assert!(fs::exist(dest_file.to_str().unwrap()));
// Destination should have same content
let dest_content = fs::file_read(dest_file.to_str().unwrap()).unwrap();
assert_eq!(dest_content, content);
@@ -165,7 +168,7 @@ fn test_which() {
// Test with a command that should exist on most systems
let result = fs::which("ls");
assert!(!result.is_empty());
// Test with a command that shouldn't exist
let result = fs::which("nonexistentcommand12345");
assert!(result.is_empty());
@@ -175,18 +178,22 @@ fn test_which() {
fn test_find_files() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
// Create test files
fs::file_write(&temp_path.join("test1.txt").to_string_lossy(), "content1").unwrap();
fs::file_write(&temp_path.join("test2.txt").to_string_lossy(), "content2").unwrap();
fs::file_write(&temp_path.join("other.log").to_string_lossy(), "log content").unwrap();
fs::file_write(
&temp_path.join("other.log").to_string_lossy(),
"log content",
)
.unwrap();
// Find .txt files
let txt_files = fs::find_files(temp_path.to_str().unwrap(), "*.txt");
assert!(txt_files.is_ok());
let files = txt_files.unwrap();
assert_eq!(files.len(), 2);
// Find all files
let all_files = fs::find_files(temp_path.to_str().unwrap(), "*");
assert!(all_files.is_ok());
@@ -198,12 +205,12 @@ fn test_find_files() {
fn test_find_dirs() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
// Create test directories
fs::mkdir(&temp_path.join("dir1").to_string_lossy()).unwrap();
fs::mkdir(&temp_path.join("dir2").to_string_lossy()).unwrap();
fs::mkdir(&temp_path.join("subdir").to_string_lossy()).unwrap();
// Find directories
let dirs = fs::find_dirs(temp_path.to_str().unwrap(), "dir*");
assert!(dirs.is_ok());

View File

@@ -5,7 +5,7 @@ fn test_platform_detection_consistency() {
// Test that platform detection functions return consistent results
let is_osx = platform::is_osx();
let is_linux = platform::is_linux();
// On any given system, only one of these should be true
// (or both false if running on Windows or other OS)
if is_osx {
@@ -21,7 +21,7 @@ fn test_architecture_detection_consistency() {
// Test that architecture detection functions return consistent results
let is_arm = platform::is_arm();
let is_x86 = platform::is_x86();
// On any given system, only one of these should be true
// (or both false if running on other architectures)
if is_arm {
@@ -76,55 +76,61 @@ fn test_x86_detection() {
#[test]
fn test_check_linux_x86() {
let result = platform::check_linux_x86();
// The result should depend on the current platform
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
{
assert!(result.is_ok(), "Should succeed on Linux x86_64");
}
#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
{
assert!(result.is_err(), "Should fail on non-Linux x86_64 platforms");
// Check that the error message is meaningful
let error = result.unwrap_err();
let error_string = error.to_string();
assert!(error_string.contains("Linux x86_64"),
"Error message should mention Linux x86_64: {}", error_string);
assert!(
error_string.contains("Linux x86_64"),
"Error message should mention Linux x86_64: {}",
error_string
);
}
}
#[test]
fn test_check_macos_arm() {
let result = platform::check_macos_arm();
// The result should depend on the current platform
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
assert!(result.is_ok(), "Should succeed on macOS ARM");
}
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
{
assert!(result.is_err(), "Should fail on non-macOS ARM platforms");
// Check that the error message is meaningful
let error = result.unwrap_err();
let error_string = error.to_string();
assert!(error_string.contains("macOS ARM"),
"Error message should mention macOS ARM: {}", error_string);
assert!(
error_string.contains("macOS ARM"),
"Error message should mention macOS ARM: {}",
error_string
);
}
}
#[test]
fn test_platform_error_creation() {
use sal_os::platform::PlatformError;
// Test that we can create platform errors
let error = PlatformError::new("Test Error", "This is a test error message");
let error_string = error.to_string();
assert!(error_string.contains("Test Error"));
assert!(error_string.contains("This is a test error message"));
}
@@ -132,11 +138,11 @@ fn test_platform_error_creation() {
#[test]
fn test_platform_error_display() {
use sal_os::platform::PlatformError;
// Test error display formatting
let error = PlatformError::Generic("Category".to_string(), "Message".to_string());
let error_string = format!("{}", error);
assert!(error_string.contains("Category"));
assert!(error_string.contains("Message"));
}
@@ -144,11 +150,11 @@ fn test_platform_error_display() {
#[test]
fn test_platform_error_debug() {
use sal_os::platform::PlatformError;
// Test error debug formatting
let error = PlatformError::Generic("Category".to_string(), "Message".to_string());
let debug_string = format!("{:?}", error);
assert!(debug_string.contains("Generic"));
assert!(debug_string.contains("Category"));
assert!(debug_string.contains("Message"));
@@ -160,15 +166,15 @@ fn test_platform_functions_are_deterministic() {
let osx1 = platform::is_osx();
let osx2 = platform::is_osx();
assert_eq!(osx1, osx2);
let linux1 = platform::is_linux();
let linux2 = platform::is_linux();
assert_eq!(linux1, linux2);
let arm1 = platform::is_arm();
let arm2 = platform::is_arm();
assert_eq!(arm1, arm2);
let x86_1 = platform::is_x86();
let x86_2 = platform::is_x86();
assert_eq!(x86_1, x86_2);
@@ -180,7 +186,7 @@ fn test_platform_check_functions_consistency() {
let is_linux_x86 = platform::is_linux() && platform::is_x86();
let check_linux_x86_result = platform::check_linux_x86().is_ok();
assert_eq!(is_linux_x86, check_linux_x86_result);
let is_macos_arm = platform::is_osx() && platform::is_arm();
let check_macos_arm_result = platform::check_macos_arm().is_ok();
assert_eq!(is_macos_arm, check_macos_arm_result);