feat: Add support for new OS package
	
		
			
	
		
	
	
		
	
		
			Some checks are pending
		
		
	
	
		
			
				
	
				Rhai Tests / Run Rhai Tests (push) Waiting to run
				
			
		
		
	
	
				
					
				
			
		
			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:
		
							
								
								
									
										522
									
								
								os/src/download.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										522
									
								
								os/src/download.rs
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										1185
									
								
								os/src/fs.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										13
									
								
								os/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								os/src/lib.rs
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										972
									
								
								os/src/package.rs
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										75
									
								
								os/src/platform.rs
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										424
									
								
								os/src/rhai.rs
									
									
									
									
									
										Normal 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,
 | 
			
		||||
        ))
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user