feat: Add support for new OS package
Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run

- Add a new `sal-os` package containing OS interaction utilities.
- Update workspace members to include the new package.
- Add README and basic usage examples for the new package.
This commit is contained in:
Mahmoud-Emad
2025-06-21 15:45:43 +03:00
parent a35edc2030
commit c4cdb8126c
27 changed files with 1735 additions and 424 deletions

522
os/src/download.rs Normal file
View File

@@ -0,0 +1,522 @@
use std::error::Error;
use std::fmt;
use std::fs;
use std::io;
use std::path::Path;
use std::process::Command;
// Define a custom error type for download operations
#[derive(Debug)]
pub enum DownloadError {
CreateDirectoryFailed(io::Error),
CurlExecutionFailed(io::Error),
DownloadFailed(String),
FileMetadataError(io::Error),
FileTooSmall(i64, i64),
RemoveFileFailed(io::Error),
ExtractionFailed(String),
CommandExecutionFailed(io::Error),
InvalidUrl(String),
NotAFile(String),
PlatformNotSupported(String),
InstallationFailed(String),
}
// Implement Display for DownloadError
impl fmt::Display for DownloadError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DownloadError::CreateDirectoryFailed(e) => {
write!(f, "Error creating directories: {}", e)
}
DownloadError::CurlExecutionFailed(e) => write!(f, "Error executing curl: {}", e),
DownloadError::DownloadFailed(url) => write!(f, "Error downloading url: {}", url),
DownloadError::FileMetadataError(e) => write!(f, "Error getting file metadata: {}", e),
DownloadError::FileTooSmall(size, min) => write!(
f,
"Error: Downloaded file is too small ({}KB < {}KB)",
size, min
),
DownloadError::RemoveFileFailed(e) => write!(f, "Error removing file: {}", e),
DownloadError::ExtractionFailed(e) => write!(f, "Error extracting archive: {}", e),
DownloadError::CommandExecutionFailed(e) => write!(f, "Error executing command: {}", e),
DownloadError::InvalidUrl(url) => write!(f, "Invalid URL: {}", url),
DownloadError::NotAFile(path) => write!(f, "Not a file: {}", path),
DownloadError::PlatformNotSupported(msg) => write!(f, "{}", msg),
DownloadError::InstallationFailed(msg) => write!(f, "{}", msg),
}
}
}
// Implement Error trait for DownloadError
impl Error for DownloadError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
DownloadError::CreateDirectoryFailed(e) => Some(e),
DownloadError::CurlExecutionFailed(e) => Some(e),
DownloadError::FileMetadataError(e) => Some(e),
DownloadError::RemoveFileFailed(e) => Some(e),
DownloadError::CommandExecutionFailed(e) => Some(e),
_ => None,
}
}
}
/**
* Download a file from URL to destination using the curl command.
* This function is primarily intended for downloading archives that will be extracted
* to a directory.
*
* # Arguments
*
* * `url` - The URL to download from
* * `dest` - The destination directory where the file will be saved or extracted
* * `min_size_kb` - Minimum required file size in KB (0 for no minimum)
*
* # Returns
*
* * `Ok(String)` - The path where the file was saved or extracted
* * `Err(DownloadError)` - An error if the download failed
*
* # Examples
*
* ```no_run
* use sal::os::download;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Download a file with no minimum size requirement
* let path = download("https://example.com/file.txt", "/tmp/", 0)?;
*
* // Download a file with minimum size requirement of 100KB
* let path = download("https://example.com/file.zip", "/tmp/", 100)?;
*
* Ok(())
* }
* ```
*
* # Notes
*
* If the URL ends with .tar.gz, .tgz, .tar, or .zip, the file will be automatically
* extracted to the destination directory.
*/
pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, DownloadError> {
// Create parent directories if they don't exist
let dest_path = Path::new(dest);
fs::create_dir_all(dest_path).map_err(DownloadError::CreateDirectoryFailed)?;
// Extract filename from URL
let filename = match url.split('/').last() {
Some(name) => name,
None => {
return Err(DownloadError::InvalidUrl(
"cannot extract filename".to_string(),
))
}
};
// Create a full path for the downloaded file
let file_path = format!("{}/{}", dest.trim_end_matches('/'), filename);
// Create a temporary path for downloading
let temp_path = format!("{}.download", file_path);
// Use curl to download the file with progress bar
println!("Downloading {} to {}", url, file_path);
let output = Command::new("curl")
.args(&[
"--progress-bar",
"--location",
"--fail",
"--output",
&temp_path,
url,
])
.status()
.map_err(DownloadError::CurlExecutionFailed)?;
if !output.success() {
return Err(DownloadError::DownloadFailed(url.to_string()));
}
// Show file size after download
match fs::metadata(&temp_path) {
Ok(metadata) => {
let size_bytes = metadata.len();
let size_kb = size_bytes / 1024;
let size_mb = size_kb / 1024;
if size_mb > 1 {
println!(
"Download complete! File size: {:.2} MB",
size_bytes as f64 / (1024.0 * 1024.0)
);
} else {
println!(
"Download complete! File size: {:.2} KB",
size_bytes as f64 / 1024.0
);
}
}
Err(_) => println!("Download complete!"),
}
// Check file size if minimum size is specified
if min_size_kb > 0 {
let metadata = fs::metadata(&temp_path).map_err(DownloadError::FileMetadataError)?;
let size_kb = metadata.len() as i64 / 1024;
if size_kb < min_size_kb {
fs::remove_file(&temp_path).map_err(DownloadError::RemoveFileFailed)?;
return Err(DownloadError::FileTooSmall(size_kb, min_size_kb));
}
}
// Check if it's a compressed file that needs extraction
let lower_url = url.to_lowercase();
let is_archive = lower_url.ends_with(".tar.gz")
|| lower_url.ends_with(".tgz")
|| lower_url.ends_with(".tar")
|| lower_url.ends_with(".zip");
if is_archive {
// Extract the file using the appropriate command with progress indication
println!("Extracting {} to {}", temp_path, dest);
let output = if lower_url.ends_with(".zip") {
Command::new("unzip")
.args(&["-o", &temp_path, "-d", dest]) // Removed -q for verbosity
.status()
} else if lower_url.ends_with(".tar.gz") || lower_url.ends_with(".tgz") {
Command::new("tar")
.args(&["-xzvf", &temp_path, "-C", dest]) // Added v for verbosity
.status()
} else {
Command::new("tar")
.args(&["-xvf", &temp_path, "-C", dest]) // Added v for verbosity
.status()
};
match output {
Ok(status) => {
if !status.success() {
return Err(DownloadError::ExtractionFailed(
"Error extracting archive".to_string(),
));
}
}
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
}
// Show number of extracted files
match fs::read_dir(dest) {
Ok(entries) => {
let count = entries.count();
println!("Extraction complete! Extracted {} files/directories", count);
}
Err(_) => println!("Extraction complete!"),
}
// Remove the temporary file
fs::remove_file(&temp_path).map_err(DownloadError::RemoveFileFailed)?;
Ok(dest.to_string())
} else {
// Just rename the temporary file to the final destination
fs::rename(&temp_path, &file_path).map_err(|e| DownloadError::CreateDirectoryFailed(e))?;
Ok(file_path)
}
}
/**
* Download a file from URL to a specific file destination using the curl command.
*
* # Arguments
*
* * `url` - The URL to download from
* * `dest` - The destination file path where the file will be saved
* * `min_size_kb` - Minimum required file size in KB (0 for no minimum)
*
* # Returns
*
* * `Ok(String)` - The path where the file was saved
* * `Err(DownloadError)` - An error if the download failed
*
* # Examples
*
* ```no_run
* use sal::os::download_file;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Download a file with no minimum size requirement
* let path = download_file("https://example.com/file.txt", "/tmp/file.txt", 0)?;
*
* // Download a file with minimum size requirement of 100KB
* let path = download_file("https://example.com/file.zip", "/tmp/file.zip", 100)?;
*
* Ok(())
* }
* ```
*/
pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String, DownloadError> {
// Create parent directories if they don't exist
let dest_path = Path::new(dest);
if let Some(parent) = dest_path.parent() {
fs::create_dir_all(parent).map_err(DownloadError::CreateDirectoryFailed)?;
}
// Create a temporary path for downloading
let temp_path = format!("{}.download", dest);
// Use curl to download the file with progress bar
println!("Downloading {} to {}", url, dest);
let output = Command::new("curl")
.args(&[
"--progress-bar",
"--location",
"--fail",
"--output",
&temp_path,
url,
])
.status()
.map_err(DownloadError::CurlExecutionFailed)?;
if !output.success() {
return Err(DownloadError::DownloadFailed(url.to_string()));
}
// Show file size after download
match fs::metadata(&temp_path) {
Ok(metadata) => {
let size_bytes = metadata.len();
let size_kb = size_bytes / 1024;
let size_mb = size_kb / 1024;
if size_mb > 1 {
println!(
"Download complete! File size: {:.2} MB",
size_bytes as f64 / (1024.0 * 1024.0)
);
} else {
println!(
"Download complete! File size: {:.2} KB",
size_bytes as f64 / 1024.0
);
}
}
Err(_) => println!("Download complete!"),
}
// Check file size if minimum size is specified
if min_size_kb > 0 {
let metadata = fs::metadata(&temp_path).map_err(DownloadError::FileMetadataError)?;
let size_kb = metadata.len() as i64 / 1024;
if size_kb < min_size_kb {
fs::remove_file(&temp_path).map_err(DownloadError::RemoveFileFailed)?;
return Err(DownloadError::FileTooSmall(size_kb, min_size_kb));
}
}
// Rename the temporary file to the final destination
fs::rename(&temp_path, dest).map_err(|e| DownloadError::CreateDirectoryFailed(e))?;
Ok(dest.to_string())
}
/**
* Make a file executable (equivalent to chmod +x).
*
* # Arguments
*
* * `path` - The path to the file to make executable
*
* # Returns
*
* * `Ok(String)` - A success message including the path
* * `Err(DownloadError)` - An error if the operation failed
*
* # Examples
*
* ```no_run
* use sal::os::chmod_exec;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Make a file executable
* chmod_exec("/path/to/file")?;
* Ok(())
* }
* ```
*/
pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
let path_obj = Path::new(path);
// Check if the path exists and is a file
if !path_obj.exists() {
return Err(DownloadError::NotAFile(format!(
"Path does not exist: {}",
path
)));
}
if !path_obj.is_file() {
return Err(DownloadError::NotAFile(format!(
"Path is not a file: {}",
path
)));
}
// Get current permissions
let metadata = fs::metadata(path).map_err(DownloadError::FileMetadataError)?;
let mut permissions = metadata.permissions();
// Set executable bit for user, group, and others
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = permissions.mode();
// Add executable bit for user, group, and others (equivalent to +x)
let new_mode = mode | 0o111;
permissions.set_mode(new_mode);
}
#[cfg(not(unix))]
{
// On non-Unix platforms, we can't set executable bit directly
// Just return success with a warning
return Ok(format!(
"Made {} executable (note: non-Unix platform, may not be fully supported)",
path
));
}
// Apply the new permissions
fs::set_permissions(path, permissions).map_err(|e| {
DownloadError::CommandExecutionFailed(io::Error::new(
io::ErrorKind::Other,
format!("Failed to set executable permissions: {}", e),
))
})?;
Ok(format!("Made {} executable", path))
}
/**
* Download a file and install it if it's a supported package format.
*
* # Arguments
*
* * `url` - The URL to download from
* * `min_size_kb` - Minimum required file size in KB (0 for no minimum)
*
* # Returns
*
* * `Ok(String)` - The path where the file was saved or extracted
* * `Err(DownloadError)` - An error if the download or installation failed
*
* # Examples
*
* ```no_run
* use sal::os::download_install;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Download and install a .deb package
* let result = download_install("https://example.com/package.deb", 100)?;
* Ok(())
* }
* ```
*
* # Notes
*
* Currently only supports .deb packages on Debian-based systems.
* For other file types, it behaves the same as the download function.
*/
pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadError> {
// Extract filename from URL
let filename = match url.split('/').last() {
Some(name) => name,
None => {
return Err(DownloadError::InvalidUrl(
"cannot extract filename".to_string(),
))
}
};
// Create a proper destination path
let dest_path = format!("/tmp/{}", filename);
// Check if it's a compressed file that needs extraction
let lower_url = url.to_lowercase();
let is_archive = lower_url.ends_with(".tar.gz")
|| lower_url.ends_with(".tgz")
|| lower_url.ends_with(".tar")
|| lower_url.ends_with(".zip");
let download_result = if is_archive {
// For archives, use the directory-based download function
download(url, "/tmp", min_size_kb)?
} else {
// For regular files, use the file-specific download function
download_file(url, &dest_path, min_size_kb)?
};
// Check if the downloaded result is a file
let path = Path::new(&dest_path);
if !path.is_file() {
return Ok(download_result); // Not a file, might be an extracted directory
}
// Check if it's a .deb package
if dest_path.to_lowercase().ends_with(".deb") {
// Check if we're on a Debian-based platform
let platform_check = Command::new("sh")
.arg("-c")
.arg("command -v dpkg > /dev/null && command -v apt > /dev/null || test -f /etc/debian_version")
.status();
match platform_check {
Ok(status) => {
if !status.success() {
return Err(DownloadError::PlatformNotSupported(
"Cannot install .deb package: not on a Debian-based system".to_string(),
));
}
}
Err(_) => {
return Err(DownloadError::PlatformNotSupported(
"Failed to check system compatibility for .deb installation".to_string(),
))
}
}
// Install the .deb package non-interactively
println!("Installing package: {}", dest_path);
let install_result = Command::new("sudo")
.args(&["dpkg", "--install", &dest_path])
.status();
match install_result {
Ok(status) => {
if !status.success() {
// If dpkg fails, try to fix dependencies and retry
println!("Attempting to resolve dependencies...");
let fix_deps = Command::new("sudo")
.args(&["apt-get", "install", "-f", "-y"])
.status();
if let Ok(fix_status) = fix_deps {
if !fix_status.success() {
return Err(DownloadError::InstallationFailed(
"Failed to resolve package dependencies".to_string(),
));
}
} else {
return Err(DownloadError::InstallationFailed(
"Failed to resolve package dependencies".to_string(),
));
}
}
println!("Package installation completed successfully");
}
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
}
}
Ok(download_result)
}

1185
os/src/fs.rs Normal file

File diff suppressed because it is too large Load Diff

13
os/src/lib.rs Normal file
View File

@@ -0,0 +1,13 @@
pub mod download;
pub mod fs;
pub mod package;
pub mod platform;
// Re-export all public functions and types
pub use download::*;
pub use fs::*;
pub use package::*;
pub use platform::*;
// Rhai integration module
pub mod rhai;

972
os/src/package.rs Normal file
View File

@@ -0,0 +1,972 @@
use std::process::Command;
/// A structure to hold command execution results
#[derive(Debug, Clone)]
pub struct CommandResult {
pub stdout: String,
pub stderr: String,
pub success: bool,
pub code: i32,
}
/// Error type for package management operations
#[derive(Debug)]
pub enum PackageError {
/// Command failed with error message
CommandFailed(String),
/// Command execution failed with IO error
CommandExecutionFailed(std::io::Error),
/// Unsupported platform
UnsupportedPlatform(String),
/// Other error
Other(String),
}
impl std::fmt::Display for PackageError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PackageError::CommandFailed(msg) => write!(f, "Command failed: {}", msg),
PackageError::CommandExecutionFailed(e) => write!(f, "Command execution failed: {}", e),
PackageError::UnsupportedPlatform(msg) => write!(f, "Unsupported platform: {}", msg),
PackageError::Other(msg) => write!(f, "Error: {}", msg),
}
}
}
impl std::error::Error for PackageError {}
/// Platform enum for detecting the current operating system
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Platform {
/// Ubuntu Linux
Ubuntu,
/// macOS
MacOS,
/// Unknown platform
Unknown,
}
impl Platform {
/// Detect the current platform
pub fn detect() -> Self {
// Check for macOS
if std::path::Path::new("/usr/bin/sw_vers").exists() {
return Platform::MacOS;
}
// Check for Ubuntu
if std::path::Path::new("/etc/lsb-release").exists() {
// Read the file to confirm it's Ubuntu
if let Ok(content) = std::fs::read_to_string("/etc/lsb-release") {
if content.contains("Ubuntu") {
return Platform::Ubuntu;
}
}
}
Platform::Unknown
}
}
// Thread-local storage for debug flag
thread_local! {
static DEBUG: std::cell::RefCell<bool> = std::cell::RefCell::new(false);
}
/// Set the debug flag for the current thread
pub fn set_thread_local_debug(debug: bool) {
DEBUG.with(|cell| {
*cell.borrow_mut() = debug;
});
}
/// Get the debug flag for the current thread
pub fn thread_local_debug() -> bool {
DEBUG.with(|cell| *cell.borrow())
}
/// Execute a package management command and return the result
pub fn execute_package_command(args: &[&str], debug: bool) -> Result<CommandResult, PackageError> {
// Save the current debug flag
let previous_debug = thread_local_debug();
// Set the thread-local debug flag
set_thread_local_debug(debug);
if debug {
println!("Executing command: {}", args.join(" "));
}
let output = Command::new(args[0]).args(&args[1..]).output();
// Restore the previous debug flag
set_thread_local_debug(previous_debug);
match output {
Ok(output) => {
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let result = CommandResult {
stdout,
stderr,
success: output.status.success(),
code: output.status.code().unwrap_or(-1),
};
// Always output stdout/stderr when debug is true
if debug {
if !result.stdout.is_empty() {
println!("Command stdout: {}", result.stdout);
}
if !result.stderr.is_empty() {
println!("Command stderr: {}", result.stderr);
}
if result.success {
println!("Command succeeded with code {}", result.code);
} else {
println!("Command failed with code {}", result.code);
}
}
if result.success {
Ok(result)
} else {
// If command failed and debug is false, output stderr
if !debug {
println!(
"Command failed with code {}: {}",
result.code,
result.stderr.trim()
);
}
Err(PackageError::CommandFailed(format!(
"Command failed with code {}: {}",
result.code,
result.stderr.trim()
)))
}
}
Err(e) => {
// Always output error information
println!("Command execution failed: {}", e);
Err(PackageError::CommandExecutionFailed(e))
}
}
}
/// Trait for package managers
pub trait PackageManager {
/// Install a package
fn install(&self, package: &str) -> Result<CommandResult, PackageError>;
/// Remove a package
fn remove(&self, package: &str) -> Result<CommandResult, PackageError>;
/// Update package lists
fn update(&self) -> Result<CommandResult, PackageError>;
/// Upgrade installed packages
fn upgrade(&self) -> Result<CommandResult, PackageError>;
/// List installed packages
fn list_installed(&self) -> Result<Vec<String>, PackageError>;
/// Search for packages
fn search(&self, query: &str) -> Result<Vec<String>, PackageError>;
/// Check if a package is installed
fn is_installed(&self, package: &str) -> Result<bool, PackageError>;
}
/// APT package manager for Ubuntu
pub struct AptPackageManager {
debug: bool,
}
impl AptPackageManager {
/// Create a new APT package manager
pub fn new(debug: bool) -> Self {
Self { debug }
}
}
impl PackageManager for AptPackageManager {
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
// Use -y to make it non-interactive and --quiet to reduce output
execute_package_command(
&["apt-get", "install", "-y", "--quiet", package],
self.debug,
)
}
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
// Use -y to make it non-interactive and --quiet to reduce output
execute_package_command(&["apt-get", "remove", "-y", "--quiet", package], self.debug)
}
fn update(&self) -> Result<CommandResult, PackageError> {
// Use -y to make it non-interactive and --quiet to reduce output
execute_package_command(&["apt-get", "update", "-y", "--quiet"], self.debug)
}
fn upgrade(&self) -> Result<CommandResult, PackageError> {
// Use -y to make it non-interactive and --quiet to reduce output
execute_package_command(&["apt-get", "upgrade", "-y", "--quiet"], self.debug)
}
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
let result = execute_package_command(&["dpkg", "--get-selections"], self.debug)?;
let packages = result
.stdout
.lines()
.filter_map(|line| {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 && parts[1] == "install" {
Some(parts[0].to_string())
} else {
None
}
})
.collect();
Ok(packages)
}
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
let result = execute_package_command(&["apt-cache", "search", query], self.debug)?;
let packages = result
.stdout
.lines()
.map(|line| {
let parts: Vec<&str> = line.split_whitespace().collect();
if !parts.is_empty() {
parts[0].to_string()
} else {
String::new()
}
})
.filter(|s| !s.is_empty())
.collect();
Ok(packages)
}
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
let result = execute_package_command(&["dpkg", "-s", package], self.debug);
match result {
Ok(cmd_result) => Ok(cmd_result.success),
Err(_) => Ok(false),
}
}
}
/// Homebrew package manager for macOS
pub struct BrewPackageManager {
debug: bool,
}
impl BrewPackageManager {
/// Create a new Homebrew package manager
pub fn new(debug: bool) -> Self {
Self { debug }
}
}
impl PackageManager for BrewPackageManager {
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
// Use --quiet to reduce output
execute_package_command(&["brew", "install", "--quiet", package], self.debug)
}
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
// Use --quiet to reduce output
execute_package_command(&["brew", "uninstall", "--quiet", package], self.debug)
}
fn update(&self) -> Result<CommandResult, PackageError> {
// Use --quiet to reduce output
execute_package_command(&["brew", "update", "--quiet"], self.debug)
}
fn upgrade(&self) -> Result<CommandResult, PackageError> {
// Use --quiet to reduce output
execute_package_command(&["brew", "upgrade", "--quiet"], self.debug)
}
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
let result = execute_package_command(&["brew", "list", "--formula"], self.debug)?;
let packages = result
.stdout
.lines()
.map(|line| line.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
Ok(packages)
}
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
let result = execute_package_command(&["brew", "search", query], self.debug)?;
let packages = result
.stdout
.lines()
.map(|line| line.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
Ok(packages)
}
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
let result = execute_package_command(&["brew", "list", package], self.debug);
match result {
Ok(cmd_result) => Ok(cmd_result.success),
Err(_) => Ok(false),
}
}
}
/// PackHero factory for package management
pub struct PackHero {
platform: Platform,
debug: bool,
}
impl PackHero {
/// Create a new PackHero instance
pub fn new() -> Self {
let platform = Platform::detect();
Self {
platform,
debug: false,
}
}
/// Set the debug mode
pub fn set_debug(&mut self, debug: bool) -> &mut Self {
self.debug = debug;
self
}
/// Get the debug mode
pub fn debug(&self) -> bool {
self.debug
}
/// Get the detected platform
pub fn platform(&self) -> Platform {
self.platform
}
/// Get a package manager for the current platform
fn get_package_manager(&self) -> Result<Box<dyn PackageManager>, PackageError> {
match self.platform {
Platform::Ubuntu => Ok(Box::new(AptPackageManager::new(self.debug))),
Platform::MacOS => Ok(Box::new(BrewPackageManager::new(self.debug))),
Platform::Unknown => Err(PackageError::UnsupportedPlatform(
"Unsupported platform".to_string(),
)),
}
}
/// Install a package
pub fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?;
pm.install(package)
}
/// Remove a package
pub fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?;
pm.remove(package)
}
/// Update package lists
pub fn update(&self) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?;
pm.update()
}
/// Upgrade installed packages
pub fn upgrade(&self) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?;
pm.upgrade()
}
/// List installed packages
pub fn list_installed(&self) -> Result<Vec<String>, PackageError> {
let pm = self.get_package_manager()?;
pm.list_installed()
}
/// Search for packages
pub fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
let pm = self.get_package_manager()?;
pm.search(query)
}
/// Check if a package is installed
pub fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
let pm = self.get_package_manager()?;
pm.is_installed(package)
}
}
#[cfg(test)]
mod tests {
// Import the std::process::Command directly for some test-specific commands
use super::*;
use std::process::Command as StdCommand;
use std::sync::{Arc, Mutex};
#[test]
fn test_platform_detection() {
// This test will return different results depending on the platform it's run on
let platform = Platform::detect();
println!("Detected platform: {:?}", platform);
// Just ensure it doesn't panic
assert!(true);
}
#[test]
fn test_debug_flag() {
// Test setting and getting the debug flag
set_thread_local_debug(true);
assert_eq!(thread_local_debug(), true);
set_thread_local_debug(false);
assert_eq!(thread_local_debug(), false);
}
#[test]
fn test_package_error_display() {
// Test the Display implementation for PackageError
let err1 = PackageError::CommandFailed("command failed".to_string());
assert_eq!(err1.to_string(), "Command failed: command failed");
let err2 = PackageError::UnsupportedPlatform("test platform".to_string());
assert_eq!(err2.to_string(), "Unsupported platform: test platform");
let err3 = PackageError::Other("other error".to_string());
assert_eq!(err3.to_string(), "Error: other error");
// We can't easily test CommandExecutionFailed because std::io::Error doesn't implement PartialEq
}
// Mock package manager for testing
struct MockPackageManager {
// debug field is kept for consistency with real package managers
#[allow(dead_code)]
debug: bool,
install_called: Arc<Mutex<bool>>,
remove_called: Arc<Mutex<bool>>,
update_called: Arc<Mutex<bool>>,
upgrade_called: Arc<Mutex<bool>>,
list_installed_called: Arc<Mutex<bool>>,
search_called: Arc<Mutex<bool>>,
is_installed_called: Arc<Mutex<bool>>,
// Control what the mock returns
should_succeed: bool,
}
impl MockPackageManager {
fn new(debug: bool, should_succeed: bool) -> Self {
Self {
debug,
install_called: Arc::new(Mutex::new(false)),
remove_called: Arc::new(Mutex::new(false)),
update_called: Arc::new(Mutex::new(false)),
upgrade_called: Arc::new(Mutex::new(false)),
list_installed_called: Arc::new(Mutex::new(false)),
search_called: Arc::new(Mutex::new(false)),
is_installed_called: Arc::new(Mutex::new(false)),
should_succeed,
}
}
}
impl PackageManager for MockPackageManager {
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
*self.install_called.lock().unwrap() = true;
if self.should_succeed {
Ok(CommandResult {
stdout: format!("Installed package {}", package),
stderr: String::new(),
success: true,
code: 0,
})
} else {
Err(PackageError::CommandFailed(
"Mock install failed".to_string(),
))
}
}
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
*self.remove_called.lock().unwrap() = true;
if self.should_succeed {
Ok(CommandResult {
stdout: format!("Removed package {}", package),
stderr: String::new(),
success: true,
code: 0,
})
} else {
Err(PackageError::CommandFailed(
"Mock remove failed".to_string(),
))
}
}
fn update(&self) -> Result<CommandResult, PackageError> {
*self.update_called.lock().unwrap() = true;
if self.should_succeed {
Ok(CommandResult {
stdout: "Updated package lists".to_string(),
stderr: String::new(),
success: true,
code: 0,
})
} else {
Err(PackageError::CommandFailed(
"Mock update failed".to_string(),
))
}
}
fn upgrade(&self) -> Result<CommandResult, PackageError> {
*self.upgrade_called.lock().unwrap() = true;
if self.should_succeed {
Ok(CommandResult {
stdout: "Upgraded packages".to_string(),
stderr: String::new(),
success: true,
code: 0,
})
} else {
Err(PackageError::CommandFailed(
"Mock upgrade failed".to_string(),
))
}
}
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
*self.list_installed_called.lock().unwrap() = true;
if self.should_succeed {
Ok(vec!["package1".to_string(), "package2".to_string()])
} else {
Err(PackageError::CommandFailed(
"Mock list_installed failed".to_string(),
))
}
}
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
*self.search_called.lock().unwrap() = true;
if self.should_succeed {
Ok(vec![
format!("result1-{}", query),
format!("result2-{}", query),
])
} else {
Err(PackageError::CommandFailed(
"Mock search failed".to_string(),
))
}
}
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
*self.is_installed_called.lock().unwrap() = true;
if self.should_succeed {
Ok(package == "installed-package")
} else {
Err(PackageError::CommandFailed(
"Mock is_installed failed".to_string(),
))
}
}
}
// Custom PackHero for testing with a mock package manager
struct TestPackHero {
platform: Platform,
#[allow(dead_code)]
debug: bool,
mock_manager: MockPackageManager,
}
impl TestPackHero {
fn new(platform: Platform, debug: bool, should_succeed: bool) -> Self {
Self {
platform,
debug,
mock_manager: MockPackageManager::new(debug, should_succeed),
}
}
fn get_package_manager(&self) -> Result<&dyn PackageManager, PackageError> {
match self.platform {
Platform::Ubuntu | Platform::MacOS => Ok(&self.mock_manager),
Platform::Unknown => Err(PackageError::UnsupportedPlatform(
"Unsupported platform".to_string(),
)),
}
}
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?;
pm.install(package)
}
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?;
pm.remove(package)
}
fn update(&self) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?;
pm.update()
}
fn upgrade(&self) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?;
pm.upgrade()
}
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
let pm = self.get_package_manager()?;
pm.list_installed()
}
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
let pm = self.get_package_manager()?;
pm.search(query)
}
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
let pm = self.get_package_manager()?;
pm.is_installed(package)
}
}
#[test]
fn test_packhero_with_mock_success() {
// Test PackHero with a mock package manager that succeeds
let hero = TestPackHero::new(Platform::Ubuntu, false, true);
// Test install
let result = hero.install("test-package");
assert!(result.is_ok());
assert!(*hero.mock_manager.install_called.lock().unwrap());
// Test remove
let result = hero.remove("test-package");
assert!(result.is_ok());
assert!(*hero.mock_manager.remove_called.lock().unwrap());
// Test update
let result = hero.update();
assert!(result.is_ok());
assert!(*hero.mock_manager.update_called.lock().unwrap());
// Test upgrade
let result = hero.upgrade();
assert!(result.is_ok());
assert!(*hero.mock_manager.upgrade_called.lock().unwrap());
// Test list_installed
let result = hero.list_installed();
assert!(result.is_ok());
assert_eq!(
result.unwrap(),
vec!["package1".to_string(), "package2".to_string()]
);
assert!(*hero.mock_manager.list_installed_called.lock().unwrap());
// Test search
let result = hero.search("query");
assert!(result.is_ok());
assert_eq!(
result.unwrap(),
vec!["result1-query".to_string(), "result2-query".to_string()]
);
assert!(*hero.mock_manager.search_called.lock().unwrap());
// Test is_installed
let result = hero.is_installed("installed-package");
assert!(result.is_ok());
assert!(result.unwrap());
assert!(*hero.mock_manager.is_installed_called.lock().unwrap());
let result = hero.is_installed("not-installed-package");
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn test_packhero_with_mock_failure() {
// Test PackHero with a mock package manager that fails
let hero = TestPackHero::new(Platform::Ubuntu, false, false);
// Test install
let result = hero.install("test-package");
assert!(result.is_err());
assert!(*hero.mock_manager.install_called.lock().unwrap());
// Test remove
let result = hero.remove("test-package");
assert!(result.is_err());
assert!(*hero.mock_manager.remove_called.lock().unwrap());
// Test update
let result = hero.update();
assert!(result.is_err());
assert!(*hero.mock_manager.update_called.lock().unwrap());
// Test upgrade
let result = hero.upgrade();
assert!(result.is_err());
assert!(*hero.mock_manager.upgrade_called.lock().unwrap());
// Test list_installed
let result = hero.list_installed();
assert!(result.is_err());
assert!(*hero.mock_manager.list_installed_called.lock().unwrap());
// Test search
let result = hero.search("query");
assert!(result.is_err());
assert!(*hero.mock_manager.search_called.lock().unwrap());
// Test is_installed
let result = hero.is_installed("installed-package");
assert!(result.is_err());
assert!(*hero.mock_manager.is_installed_called.lock().unwrap());
}
#[test]
fn test_packhero_unsupported_platform() {
// Test PackHero with an unsupported platform
let hero = TestPackHero::new(Platform::Unknown, false, true);
// All operations should fail with UnsupportedPlatform error
let result = hero.install("test-package");
assert!(result.is_err());
match result {
Err(PackageError::UnsupportedPlatform(_)) => (),
_ => panic!("Expected UnsupportedPlatform error"),
}
let result = hero.remove("test-package");
assert!(result.is_err());
match result {
Err(PackageError::UnsupportedPlatform(_)) => (),
_ => panic!("Expected UnsupportedPlatform error"),
}
let result = hero.update();
assert!(result.is_err());
match result {
Err(PackageError::UnsupportedPlatform(_)) => (),
_ => panic!("Expected UnsupportedPlatform error"),
}
}
// Real-world tests that actually install and remove packages on Ubuntu
// These tests will only run on Ubuntu and will be skipped on other platforms
#[test]
fn test_real_package_operations_on_ubuntu() {
// Check if we're on Ubuntu
let platform = Platform::detect();
if platform != Platform::Ubuntu {
println!(
"Skipping real package operations test on non-Ubuntu platform: {:?}",
platform
);
return;
}
println!("Running real package operations test on Ubuntu");
// Create a PackHero instance with debug enabled
let mut hero = PackHero::new();
hero.set_debug(true);
// Test package to install/remove
let test_package = "wget";
// First, check if the package is already installed
let is_installed_before = match hero.is_installed(test_package) {
Ok(result) => result,
Err(e) => {
println!("Error checking if package is installed: {}", e);
return;
}
};
println!(
"Package {} is installed before test: {}",
test_package, is_installed_before
);
// If the package is already installed, we'll remove it first
if is_installed_before {
println!("Removing existing package {} before test", test_package);
match hero.remove(test_package) {
Ok(_) => println!("Successfully removed package {}", test_package),
Err(e) => {
println!("Error removing package {}: {}", test_package, e);
return;
}
}
// Verify it was removed
match hero.is_installed(test_package) {
Ok(is_installed) => {
if is_installed {
println!("Failed to remove package {}", test_package);
return;
} else {
println!("Verified package {} was removed", test_package);
}
}
Err(e) => {
println!(
"Error checking if package is installed after removal: {}",
e
);
return;
}
}
}
// Now install the package
println!("Installing package {}", test_package);
match hero.install(test_package) {
Ok(_) => println!("Successfully installed package {}", test_package),
Err(e) => {
println!("Error installing package {}: {}", test_package, e);
return;
}
}
// Verify it was installed
match hero.is_installed(test_package) {
Ok(is_installed) => {
if !is_installed {
println!("Failed to install package {}", test_package);
return;
} else {
println!("Verified package {} was installed", test_package);
}
}
Err(e) => {
println!(
"Error checking if package is installed after installation: {}",
e
);
return;
}
}
// Test the search functionality
println!("Searching for packages with 'wget'");
match hero.search("wget") {
Ok(results) => {
println!("Search results: {:?}", results);
assert!(
results.iter().any(|r| r.contains("wget")),
"Search results should contain wget"
);
}
Err(e) => {
println!("Error searching for packages: {}", e);
return;
}
}
// Test listing installed packages
println!("Listing installed packages");
match hero.list_installed() {
Ok(packages) => {
println!("Found {} installed packages", packages.len());
// Check if our test package is in the list
assert!(
packages.iter().any(|p| p == test_package),
"Installed packages list should contain {}",
test_package
);
}
Err(e) => {
println!("Error listing installed packages: {}", e);
return;
}
}
// Now remove the package if it wasn't installed before
if !is_installed_before {
println!("Removing package {} after test", test_package);
match hero.remove(test_package) {
Ok(_) => println!("Successfully removed package {}", test_package),
Err(e) => {
println!("Error removing package {}: {}", test_package, e);
return;
}
}
// Verify it was removed
match hero.is_installed(test_package) {
Ok(is_installed) => {
if is_installed {
println!("Failed to remove package {}", test_package);
return;
} else {
println!("Verified package {} was removed", test_package);
}
}
Err(e) => {
println!(
"Error checking if package is installed after removal: {}",
e
);
return;
}
}
}
// Test update functionality
println!("Testing package list update");
match hero.update() {
Ok(_) => println!("Successfully updated package lists"),
Err(e) => {
println!("Error updating package lists: {}", e);
return;
}
}
println!("All real package operations tests passed on Ubuntu");
}
// Test to check if apt-get is available on the system
#[test]
fn test_apt_get_availability() {
// This test checks if apt-get is available on the system
let output = StdCommand::new("which")
.arg("apt-get")
.output()
.expect("Failed to execute which apt-get");
let success = output.status.success();
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
println!("apt-get available: {}", success);
if success {
println!("apt-get path: {}", stdout.trim());
}
// On Ubuntu, this should pass
if Platform::detect() == Platform::Ubuntu {
assert!(success, "apt-get should be available on Ubuntu");
}
}
}

75
os/src/platform.rs Normal file
View File

@@ -0,0 +1,75 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum PlatformError {
#[error("{0}: {1}")]
Generic(String, String),
}
impl PlatformError {
pub fn new(kind: &str, message: &str) -> Self {
PlatformError::Generic(kind.to_string(), message.to_string())
}
}
#[cfg(target_os = "macos")]
pub fn is_osx() -> bool {
true
}
#[cfg(not(target_os = "macos"))]
pub fn is_osx() -> bool {
false
}
#[cfg(target_os = "linux")]
pub fn is_linux() -> bool {
true
}
#[cfg(not(target_os = "linux"))]
pub fn is_linux() -> bool {
false
}
#[cfg(target_arch = "aarch64")]
pub fn is_arm() -> bool {
true
}
#[cfg(not(target_arch = "aarch64"))]
pub fn is_arm() -> bool {
false
}
#[cfg(target_arch = "x86_64")]
pub fn is_x86() -> bool {
true
}
#[cfg(not(target_arch = "x86_64"))]
pub fn is_x86() -> bool {
false
}
pub fn check_linux_x86() -> Result<(), PlatformError> {
if is_linux() && is_x86() {
Ok(())
} else {
Err(PlatformError::new(
"Platform Check Error",
"This operation is only supported on Linux x86_64.",
))
}
}
pub fn check_macos_arm() -> Result<(), PlatformError> {
if is_osx() && is_arm() {
Ok(())
} else {
Err(PlatformError::new(
"Platform Check Error",
"This operation is only supported on macOS ARM.",
))
}
}

424
os/src/rhai.rs Normal file
View File

@@ -0,0 +1,424 @@
//! Rhai wrappers for OS module functions
//!
//! This module provides Rhai wrappers for the functions in the OS module.
use crate::package::PackHero;
use crate::{download as dl, fs, package};
use rhai::{Array, Engine, EvalAltResult, Position};
/// A trait for converting a Result to a Rhai-compatible error
pub trait ToRhaiError<T> {
fn to_rhai_error(self) -> Result<T, Box<EvalAltResult>>;
}
impl<T, E: std::error::Error> ToRhaiError<T> for Result<T, E> {
fn to_rhai_error(self) -> Result<T, Box<EvalAltResult>> {
self.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
e.to_string().into(),
Position::NONE,
))
})
}
}
/// Register OS module functions with the Rhai engine
///
/// # Arguments
///
/// * `engine` - The Rhai engine to register the functions with
///
/// # Returns
///
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
pub fn register_os_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register file system functions
engine.register_fn("copy", copy);
engine.register_fn("copy_bin", copy_bin);
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);
engine.register_fn("chdir", chdir);
engine.register_fn("file_read", file_read);
engine.register_fn("file_write", file_write);
engine.register_fn("file_write_append", file_write_append);
// Register command check functions
engine.register_fn("which", which);
engine.register_fn("cmd_ensure_exists", cmd_ensure_exists);
// Register download functions
engine.register_fn("download", download);
engine.register_fn("download_file", download_file);
engine.register_fn("download_install", download_install);
engine.register_fn("chmod_exec", chmod_exec);
// Register move function
engine.register_fn("mv", mv);
// Register package management functions
engine.register_fn("package_install", package_install);
engine.register_fn("package_remove", package_remove);
engine.register_fn("package_update", package_update);
engine.register_fn("package_upgrade", package_upgrade);
engine.register_fn("package_list", package_list);
engine.register_fn("package_search", package_search);
engine.register_fn("package_is_installed", package_is_installed);
engine.register_fn("package_set_debug", package_set_debug);
engine.register_fn("package_platform", package_platform);
// Register platform detection functions
engine.register_fn("platform_is_osx", platform_is_osx);
engine.register_fn("platform_is_linux", platform_is_linux);
engine.register_fn("platform_is_arm", platform_is_arm);
engine.register_fn("platform_is_x86", platform_is_x86);
engine.register_fn("platform_check_linux_x86", platform_check_linux_x86);
engine.register_fn("platform_check_macos_arm", platform_check_macos_arm);
Ok(())
}
//
// File System Function Wrappers
//
/// Wrapper for fs::copy
///
/// Recursively copy a file or directory from source to destination.
pub fn copy(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> {
fs::copy(src, dest).to_rhai_error()
}
/// Wrapper for fs::copy_bin
///
/// Copy a binary to the correct location based on OS and user privileges.
pub fn copy_bin(src: &str) -> Result<String, Box<EvalAltResult>> {
fs::copy_bin(src).to_rhai_error()
}
/// Wrapper for fs::exist
///
/// Check if a file or directory exists.
pub fn exist(path: &str) -> bool {
fs::exist(path)
}
/// Wrapper for fs::find_file
///
/// Find a file in a directory (with support for wildcards).
pub fn find_file(dir: &str, filename: &str) -> Result<String, Box<EvalAltResult>> {
fs::find_file(dir, filename).to_rhai_error()
}
/// Wrapper for fs::find_files
///
/// Find multiple files in a directory (recursive, with support for wildcards).
pub fn find_files(dir: &str, filename: &str) -> Result<Array, Box<EvalAltResult>> {
let files = fs::find_files(dir, filename).to_rhai_error()?;
// Convert Vec<String> to Rhai Array
let mut array = Array::new();
for file in files {
array.push(file.into());
}
Ok(array)
}
/// Wrapper for fs::find_dir
///
/// Find a directory in a parent directory (with support for wildcards).
pub fn find_dir(dir: &str, dirname: &str) -> Result<String, Box<EvalAltResult>> {
fs::find_dir(dir, dirname).to_rhai_error()
}
/// Wrapper for fs::find_dirs
///
/// Find multiple directories in a parent directory (recursive, with support for wildcards).
pub fn find_dirs(dir: &str, dirname: &str) -> Result<Array, Box<EvalAltResult>> {
let dirs = fs::find_dirs(dir, dirname).to_rhai_error()?;
// Convert Vec<String> to Rhai Array
let mut array = Array::new();
for dir in dirs {
array.push(dir.into());
}
Ok(array)
}
/// Wrapper for fs::delete
///
/// Delete a file or directory (defensive - doesn't error if file doesn't exist).
pub fn delete(path: &str) -> Result<String, Box<EvalAltResult>> {
fs::delete(path).to_rhai_error()
}
/// Wrapper for fs::mkdir
///
/// Create a directory and all parent directories (defensive - doesn't error if directory exists).
pub fn mkdir(path: &str) -> Result<String, Box<EvalAltResult>> {
fs::mkdir(path).to_rhai_error()
}
/// Wrapper for fs::file_size
///
/// Get the size of a file in bytes.
pub fn file_size(path: &str) -> Result<i64, Box<EvalAltResult>> {
fs::file_size(path).to_rhai_error()
}
/// Wrapper for fs::rsync
///
/// Sync directories using rsync (or platform equivalent).
pub fn rsync(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> {
fs::rsync(src, dest).to_rhai_error()
}
/// Wrapper for fs::chdir
///
/// Change the current working directory.
pub fn chdir(path: &str) -> Result<String, Box<EvalAltResult>> {
fs::chdir(path).to_rhai_error()
}
/// Wrapper for fs::file_read
///
/// Read the contents of a file.
pub fn file_read(path: &str) -> Result<String, Box<EvalAltResult>> {
fs::file_read(path).to_rhai_error()
}
/// Wrapper for fs::file_write
///
/// Write content to a file (creates the file if it doesn't exist, overwrites if it does).
pub fn file_write(path: &str, content: &str) -> Result<String, Box<EvalAltResult>> {
fs::file_write(path, content).to_rhai_error()
}
/// Wrapper for fs::file_write_append
///
/// Append content to a file (creates the file if it doesn't exist).
pub fn file_write_append(path: &str, content: &str) -> Result<String, Box<EvalAltResult>> {
fs::file_write_append(path, content).to_rhai_error()
}
/// Wrapper for fs::mv
///
/// Move a file or directory from source to destination.
pub fn mv(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> {
fs::mv(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<String, Box<EvalAltResult>> {
dl::download(url, dest, min_size_kb).to_rhai_error()
}
/// Wrapper for os::download_file
///
/// Download a file from URL to a specific file destination using the curl command.
pub fn download_file(
url: &str,
dest: &str,
min_size_kb: i64,
) -> Result<String, Box<EvalAltResult>> {
dl::download_file(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<String, Box<EvalAltResult>> {
dl::download_install(url, min_size_kb).to_rhai_error()
}
/// Wrapper for os::chmod_exec
///
/// Make a file executable (equivalent to chmod +x).
pub fn chmod_exec(path: &str) -> Result<String, Box<EvalAltResult>> {
dl::chmod_exec(path).to_rhai_error()
}
/// Wrapper for os::which
///
/// Check if a command exists in the system PATH.
pub fn which(command: &str) -> String {
fs::which(command)
}
/// Wrapper for os::cmd_ensure_exists
///
/// Ensure that one or more commands exist in the system PATH.
/// If any command doesn't exist, an error is thrown.
pub fn cmd_ensure_exists(commands: &str) -> Result<String, Box<EvalAltResult>> {
fs::cmd_ensure_exists(commands).to_rhai_error()
}
//
// Package Management Function Wrappers
//
/// Wrapper for os::package::PackHero::install
///
/// Install a package using the system package manager.
pub fn package_install(package: &str) -> Result<String, Box<EvalAltResult>> {
let hero = PackHero::new();
hero.install(package)
.map(|_| format!("Package '{}' installed successfully", package))
.to_rhai_error()
}
/// Wrapper for os::package::PackHero::remove
///
/// Remove a package using the system package manager.
pub fn package_remove(package: &str) -> Result<String, Box<EvalAltResult>> {
let hero = PackHero::new();
hero.remove(package)
.map(|_| format!("Package '{}' removed successfully", package))
.to_rhai_error()
}
/// Wrapper for os::package::PackHero::update
///
/// Update package lists using the system package manager.
pub fn package_update() -> Result<String, Box<EvalAltResult>> {
let hero = PackHero::new();
hero.update()
.map(|_| "Package lists updated successfully".to_string())
.to_rhai_error()
}
/// Wrapper for os::package::PackHero::upgrade
///
/// Upgrade installed packages using the system package manager.
pub fn package_upgrade() -> Result<String, Box<EvalAltResult>> {
let hero = PackHero::new();
hero.upgrade()
.map(|_| "Packages upgraded successfully".to_string())
.to_rhai_error()
}
/// Wrapper for os::package::PackHero::list_installed
///
/// List installed packages using the system package manager.
pub fn package_list() -> Result<Array, Box<EvalAltResult>> {
let hero = PackHero::new();
let packages = hero.list_installed().to_rhai_error()?;
// Convert Vec<String> to Rhai Array
let mut array = Array::new();
for package in packages {
array.push(package.into());
}
Ok(array)
}
/// Wrapper for os::package::PackHero::search
///
/// Search for packages using the system package manager.
pub fn package_search(query: &str) -> Result<Array, Box<EvalAltResult>> {
let hero = PackHero::new();
let packages = hero.search(query).to_rhai_error()?;
// Convert Vec<String> to Rhai Array
let mut array = Array::new();
for package in packages {
array.push(package.into());
}
Ok(array)
}
/// Wrapper for os::package::PackHero::is_installed
///
/// Check if a package is installed using the system package manager.
pub fn package_is_installed(package: &str) -> Result<bool, Box<EvalAltResult>> {
let hero = PackHero::new();
hero.is_installed(package).to_rhai_error()
}
// Thread-local storage for package debug flag
thread_local! {
static PACKAGE_DEBUG: std::cell::RefCell<bool> = std::cell::RefCell::new(false);
}
/// Set the debug mode for package management operations
pub fn package_set_debug(debug: bool) -> bool {
let mut hero = PackHero::new();
hero.set_debug(debug);
// Also set the thread-local debug flag
PACKAGE_DEBUG.with(|cell| {
*cell.borrow_mut() = debug;
});
debug
}
/// Get the current platform name for package management
pub fn package_platform() -> String {
let hero = PackHero::new();
match hero.platform() {
package::Platform::Ubuntu => "Ubuntu".to_string(),
package::Platform::MacOS => "MacOS".to_string(),
package::Platform::Unknown => "Unknown".to_string(),
}
}
//
// Platform Detection Function Wrappers
//
/// Wrapper for platform::is_osx
pub fn platform_is_osx() -> bool {
crate::platform::is_osx()
}
/// Wrapper for platform::is_linux
pub fn platform_is_linux() -> bool {
crate::platform::is_linux()
}
/// Wrapper for platform::is_arm
pub fn platform_is_arm() -> bool {
crate::platform::is_arm()
}
/// Wrapper for platform::is_x86
pub fn platform_is_x86() -> bool {
crate::platform::is_x86()
}
/// Wrapper for platform::check_linux_x86
pub fn platform_check_linux_x86() -> Result<(), Box<EvalAltResult>> {
crate::platform::check_linux_x86().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Platform Check Error: {}", e).into(),
Position::NONE,
))
})
}
/// Wrapper for platform::check_macos_arm
pub fn platform_check_macos_arm() -> Result<(), Box<EvalAltResult>> {
crate::platform::check_macos_arm().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Platform Check Error: {}", e).into(),
Position::NONE,
))
})
}