...
This commit is contained in:
		| @@ -145,6 +145,28 @@ delete("temp.txt"); | ||||
| delete("temp_dir"); | ||||
| ``` | ||||
|  | ||||
| ### `mv(src, dest)` | ||||
|  | ||||
| Moves a file or directory from source to destination. | ||||
|  | ||||
| **Parameters:** | ||||
| - `src` (string): The source path | ||||
| - `dest` (string): The destination path | ||||
|  | ||||
| **Returns:** A message confirming the move was successful. | ||||
|  | ||||
| **Example:** | ||||
| ```js | ||||
| // Move a file | ||||
| mv("file.txt", "new_location/file.txt"); | ||||
|  | ||||
| // Move a directory | ||||
| mv("source_dir", "destination_dir"); | ||||
|  | ||||
| // Rename a file | ||||
| mv("old_name.txt", "new_name.txt"); | ||||
| ``` | ||||
|  | ||||
| ### `mkdir(path)` | ||||
|  | ||||
| Creates a directory and all parent directories. This function is defensive and doesn't error if the directory already exists. | ||||
|   | ||||
							
								
								
									
										133
									
								
								src/os/fs.rs
									
									
									
									
									
								
							
							
						
						
									
										133
									
								
								src/os/fs.rs
									
									
									
									
									
								
							| @@ -124,7 +124,16 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> { | ||||
|         for path in paths { | ||||
|             let target_path = if dest_is_dir { | ||||
|                 // If destination is a directory, copy the file into it | ||||
|                 dest_path.join(path.file_name().unwrap_or_default()) | ||||
|                 if path.is_file() { | ||||
|                     // For files, just use the filename | ||||
|                     dest_path.join(path.file_name().unwrap_or_default()) | ||||
|                 } else if path.is_dir() { | ||||
|                     // For directories, use the directory name | ||||
|                     dest_path.join(path.file_name().unwrap_or_default()) | ||||
|                 } else { | ||||
|                     // Fallback | ||||
|                     dest_path.join(path.file_name().unwrap_or_default()) | ||||
|                 } | ||||
|             } else { | ||||
|                 // Otherwise use the destination as is (only makes sense for single file) | ||||
|                 dest_path.to_path_buf() | ||||
| @@ -186,9 +195,17 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> { | ||||
|          | ||||
|         // Copy based on source type | ||||
|         if src_path.is_file() { | ||||
|             // Copy file | ||||
|             fs::copy(src_path, dest_path).map_err(FsError::CopyFailed)?; | ||||
|             Ok(format!("Successfully copied file '{}' to '{}'", src, dest)) | ||||
|             // If destination is a directory, copy the file into it | ||||
|             if dest_path.exists() && dest_path.is_dir() { | ||||
|                 let file_name = src_path.file_name().unwrap_or_default(); | ||||
|                 let new_dest_path = dest_path.join(file_name); | ||||
|                 fs::copy(src_path, new_dest_path).map_err(FsError::CopyFailed)?; | ||||
|                 Ok(format!("Successfully copied file '{}' to '{}/{}'", src, dest, file_name.to_string_lossy())) | ||||
|             } else { | ||||
|                 // Otherwise copy file to the specified destination | ||||
|                 fs::copy(src_path, dest_path).map_err(FsError::CopyFailed)?; | ||||
|                 Ok(format!("Successfully copied file '{}' to '{}'", src, dest)) | ||||
|             } | ||||
|         } else if src_path.is_dir() { | ||||
|             // For directories, use platform-specific command | ||||
|             #[cfg(target_os = "windows")] | ||||
| @@ -743,6 +760,114 @@ pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> { | ||||
|     Ok(format!("Successfully appended to file '{}'", path)) | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Move a file or directory from source to destination. | ||||
|  * | ||||
|  * # Arguments | ||||
|  * | ||||
|  * * `src` - The source path | ||||
|  * * `dest` - The destination path | ||||
|  * | ||||
|  * # Returns | ||||
|  * | ||||
|  * * `Ok(String)` - A success message indicating what was moved | ||||
|  * * `Err(FsError)` - An error if the move operation failed | ||||
|  * | ||||
|  * # Examples | ||||
|  * | ||||
|  * ``` | ||||
|  * // Move a file | ||||
|  * let result = mv("file.txt", "new_location/file.txt")?; | ||||
|  * | ||||
|  * // Move a directory | ||||
|  * let result = mv("src_dir", "dest_dir")?; | ||||
|  * | ||||
|  * // Rename a file | ||||
|  * let result = mv("old_name.txt", "new_name.txt")?; | ||||
|  * ``` | ||||
|  */ | ||||
| pub fn mv(src: &str, dest: &str) -> Result<String, FsError> { | ||||
|     let src_path = Path::new(src); | ||||
|     let dest_path = Path::new(dest); | ||||
|      | ||||
|     // Check if source exists | ||||
|     if !src_path.exists() { | ||||
|         return Err(FsError::FileNotFound(src.to_string())); | ||||
|     } | ||||
|      | ||||
|     // Create parent directories if they don't exist | ||||
|     if let Some(parent) = dest_path.parent() { | ||||
|         fs::create_dir_all(parent).map_err(FsError::CreateDirectoryFailed)?; | ||||
|     } | ||||
|      | ||||
|     // Handle the case where destination is a directory and exists | ||||
|     let final_dest_path = if dest_path.exists() && dest_path.is_dir() && src_path.is_file() { | ||||
|         // If destination is a directory and source is a file, move the file into the directory | ||||
|         let file_name = src_path.file_name().unwrap_or_default(); | ||||
|         dest_path.join(file_name) | ||||
|     } else { | ||||
|         dest_path.to_path_buf() | ||||
|     }; | ||||
|      | ||||
|     // Clone the path for use in the error handler | ||||
|     let final_dest_path_clone = final_dest_path.clone(); | ||||
|      | ||||
|     // Perform the move operation | ||||
|     fs::rename(src_path, &final_dest_path).map_err(|e| { | ||||
|         // If rename fails (possibly due to cross-device link), try copy and delete | ||||
|         if e.kind() == std::io::ErrorKind::CrossesDevices { | ||||
|             // For cross-device moves, we need to copy and then delete | ||||
|             if src_path.is_file() { | ||||
|                 // Copy file | ||||
|                 match fs::copy(src_path, &final_dest_path_clone) { | ||||
|                     Ok(_) => { | ||||
|                         // Delete source after successful copy | ||||
|                         if let Err(del_err) = fs::remove_file(src_path) { | ||||
|                             return FsError::DeleteFailed(del_err); | ||||
|                         } | ||||
|                         return FsError::CommandFailed("".to_string()); // This is a hack to trigger the success message | ||||
|                     }, | ||||
|                     Err(copy_err) => return FsError::CopyFailed(copy_err), | ||||
|                 } | ||||
|             } else if src_path.is_dir() { | ||||
|                 // For directories, use platform-specific command | ||||
|                 #[cfg(target_os = "windows")] | ||||
|                 let output = Command::new("xcopy") | ||||
|                     .args(&["/E", "/I", "/H", "/Y", src, dest]) | ||||
|                     .status(); | ||||
|                      | ||||
|                 #[cfg(not(target_os = "windows"))] | ||||
|                 let output = Command::new("cp") | ||||
|                     .args(&["-R", src, dest]) | ||||
|                     .status(); | ||||
|                  | ||||
|                 match output { | ||||
|                     Ok(status) => { | ||||
|                         if status.success() { | ||||
|                             // Delete source after successful copy | ||||
|                             if let Err(del_err) = fs::remove_dir_all(src_path) { | ||||
|                                 return FsError::DeleteFailed(del_err); | ||||
|                             } | ||||
|                             return FsError::CommandFailed("".to_string()); // This is a hack to trigger the success message | ||||
|                         } else { | ||||
|                             return FsError::CommandFailed("Failed to copy directory for move operation".to_string()); | ||||
|                         } | ||||
|                     }, | ||||
|                     Err(cmd_err) => return FsError::CommandExecutionError(cmd_err), | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         FsError::CommandFailed(format!("Failed to move '{}' to '{}': {}", src, dest, e)) | ||||
|     })?; | ||||
|      | ||||
|     // If we get here, either the rename was successful or our copy-delete hack worked | ||||
|     if src_path.is_file() { | ||||
|         Ok(format!("Successfully moved file '{}' to '{}'", src, dest)) | ||||
|     } else { | ||||
|         Ok(format!("Successfully moved directory '{}' to '{}'", src, dest)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Check if a command exists in the system PATH. | ||||
|  * | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| mod fs; | ||||
| mod download; | ||||
| pub mod package; | ||||
|  | ||||
| pub use fs::*; | ||||
| pub use download::*; | ||||
| pub use download::*; | ||||
| pub use package::*; | ||||
							
								
								
									
										421
									
								
								src/os/package.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										421
									
								
								src/os/package.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,421 @@ | ||||
| use std::process::Command; | ||||
| use crate::process::CommandResult; | ||||
|  | ||||
| /// 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 { | ||||
|     use super::*; | ||||
|      | ||||
|     #[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); | ||||
|     } | ||||
|      | ||||
|     // More tests would be added for each platform-specific implementation | ||||
|     // These would likely be integration tests that are conditionally compiled | ||||
|     // based on the platform they're running on | ||||
| } | ||||
							
								
								
									
										565
									
								
								src/package_implementation_plan.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										565
									
								
								src/package_implementation_plan.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,565 @@ | ||||
| # Package Management Module Implementation Plan | ||||
|  | ||||
| ## Overview | ||||
|  | ||||
| The package management module will: | ||||
| 1. Provide a factory called `PackHero` that detects the current platform | ||||
| 2. Implement platform-specific package managers for Ubuntu (apt) and macOS (brew) | ||||
| 3. Support operations: install, remove, update, upgrade, list installed packages, search for packages, and check if a package is installed | ||||
| 4. Include debug functionality similar to buildah | ||||
| 5. Ensure all operations are non-interactive and have proper error propagation | ||||
| 6. Be wrapped in Rhai for scripting access | ||||
|  | ||||
| ## Architecture | ||||
|  | ||||
| ```mermaid | ||||
| classDiagram | ||||
|     class PackageError { | ||||
|         +CommandFailed(String) | ||||
|         +CommandExecutionFailed(std::io::Error) | ||||
|         +UnsupportedPlatform(String) | ||||
|         +Other(String) | ||||
|     } | ||||
|  | ||||
|     class PackHero { | ||||
|         -platform: Platform | ||||
|         -debug: bool | ||||
|         +new() PackHero | ||||
|         +detect_platform() Platform | ||||
|         +set_debug(bool) PackHero | ||||
|         +debug() bool | ||||
|         +install(package: &str) Result | ||||
|         +remove(package: &str) Result | ||||
|         +update() Result | ||||
|         +upgrade() Result | ||||
|         +list_installed() Result | ||||
|         +search(query: &str) Result | ||||
|         +is_installed(package: &str) Result | ||||
|     } | ||||
|  | ||||
|     class Platform { | ||||
|         <<enumeration>> | ||||
|         Ubuntu | ||||
|         MacOS | ||||
|         Unknown | ||||
|     } | ||||
|  | ||||
|     class PackageManager { | ||||
|         <<interface>> | ||||
|         +install(package: &str) Result | ||||
|         +remove(package: &str) Result | ||||
|         +update() Result | ||||
|         +upgrade() Result | ||||
|         +list_installed() Result | ||||
|         +search(query: &str) Result | ||||
|         +is_installed(package: &str) Result | ||||
|     } | ||||
|  | ||||
|     class AptPackageManager { | ||||
|         -debug: bool | ||||
|         +new(debug: bool) AptPackageManager | ||||
|         +install(package: &str) Result | ||||
|         +remove(package: &str) Result | ||||
|         +update() Result | ||||
|         +upgrade() Result | ||||
|         +list_installed() Result | ||||
|         +search(query: &str) Result | ||||
|         +is_installed(package: &str) Result | ||||
|     } | ||||
|  | ||||
|     class BrewPackageManager { | ||||
|         -debug: bool | ||||
|         +new(debug: bool) BrewPackageManager | ||||
|         +install(package: &str) Result | ||||
|         +remove(package: &str) Result | ||||
|         +update() Result | ||||
|         +upgrade() Result | ||||
|         +list_installed() Result | ||||
|         +search(query: &str) Result | ||||
|         +is_installed(package: &str) Result | ||||
|     } | ||||
|  | ||||
|     PackHero --> Platform : uses | ||||
|     PackHero --> PackageManager : uses | ||||
|     PackageManager <|.. AptPackageManager : implements | ||||
|     PackageManager <|.. BrewPackageManager : implements | ||||
| ``` | ||||
|  | ||||
| ## Implementation Details | ||||
|  | ||||
| ### 1. Package Error Type | ||||
|  | ||||
| Create a custom error type for package management operations: | ||||
|  | ||||
| ```rust | ||||
| pub enum PackageError { | ||||
|     CommandFailed(String), | ||||
|     CommandExecutionFailed(std::io::Error), | ||||
|     UnsupportedPlatform(String), | ||||
|     Other(String), | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### 2. Platform Detection | ||||
|  | ||||
| Implement platform detection to determine which package manager to use: | ||||
|  | ||||
| ```rust | ||||
| pub enum Platform { | ||||
|     Ubuntu, | ||||
|     MacOS, | ||||
|     Unknown, | ||||
| } | ||||
|  | ||||
| impl 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 | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### 3. Package Manager Trait | ||||
|  | ||||
| Define a trait for package managers to implement: | ||||
|  | ||||
| ```rust | ||||
| pub trait PackageManager { | ||||
|     fn install(&self, package: &str) -> Result<CommandResult, PackageError>; | ||||
|     fn remove(&self, package: &str) -> Result<CommandResult, PackageError>; | ||||
|     fn update(&self) -> Result<CommandResult, PackageError>; | ||||
|     fn upgrade(&self) -> Result<CommandResult, PackageError>; | ||||
|     fn list_installed(&self) -> Result<Vec<String>, PackageError>; | ||||
|     fn search(&self, query: &str) -> Result<Vec<String>, PackageError>; | ||||
|     fn is_installed(&self, package: &str) -> Result<bool, PackageError>; | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### 4. Platform-Specific Implementations | ||||
|  | ||||
| #### Ubuntu (apt) Implementation | ||||
|  | ||||
| ```rust | ||||
| pub struct AptPackageManager { | ||||
|     debug: bool, | ||||
| } | ||||
|  | ||||
| impl AptPackageManager { | ||||
|     pub fn new(debug: bool) -> Self { | ||||
|         Self { debug } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl PackageManager for AptPackageManager { | ||||
|     fn install(&self, package: &str) -> Result<CommandResult, PackageError> { | ||||
|         execute_package_command(&["apt-get", "install", "-y", package], self.debug) | ||||
|     } | ||||
|      | ||||
|     fn remove(&self, package: &str) -> Result<CommandResult, PackageError> { | ||||
|         execute_package_command(&["apt-get", "remove", "-y", package], self.debug) | ||||
|     } | ||||
|      | ||||
|     fn update(&self) -> Result<CommandResult, PackageError> { | ||||
|         execute_package_command(&["apt-get", "update", "-y"], self.debug) | ||||
|     } | ||||
|      | ||||
|     fn upgrade(&self) -> Result<CommandResult, PackageError> { | ||||
|         execute_package_command(&["apt-get", "upgrade", "-y"], 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), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### macOS (brew) Implementation | ||||
|  | ||||
| ```rust | ||||
| pub struct BrewPackageManager { | ||||
|     debug: bool, | ||||
| } | ||||
|  | ||||
| impl BrewPackageManager { | ||||
|     pub fn new(debug: bool) -> Self { | ||||
|         Self { debug } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl PackageManager for BrewPackageManager { | ||||
|     fn install(&self, package: &str) -> Result<CommandResult, PackageError> { | ||||
|         execute_package_command(&["brew", "install", package], self.debug) | ||||
|     } | ||||
|      | ||||
|     fn remove(&self, package: &str) -> Result<CommandResult, PackageError> { | ||||
|         execute_package_command(&["brew", "uninstall", package], self.debug) | ||||
|     } | ||||
|      | ||||
|     fn update(&self) -> Result<CommandResult, PackageError> { | ||||
|         execute_package_command(&["brew", "update"], self.debug) | ||||
|     } | ||||
|      | ||||
|     fn upgrade(&self) -> Result<CommandResult, PackageError> { | ||||
|         execute_package_command(&["brew", "upgrade"], 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), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### 5. Command Execution with Debug Support | ||||
|  | ||||
| Implement a function to execute package management commands with debug support: | ||||
|  | ||||
| ```rust | ||||
| // 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)) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### 6. PackHero Factory | ||||
|  | ||||
| Implement the PackHero factory to provide a unified interface: | ||||
|  | ||||
| ```rust | ||||
| pub struct PackHero { | ||||
|     platform: Platform, | ||||
|     debug: bool, | ||||
| } | ||||
|  | ||||
| impl PackHero { | ||||
|     pub fn new() -> Self { | ||||
|         let platform = Platform::detect(); | ||||
|         Self { | ||||
|             platform, | ||||
|             debug: false, | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     pub fn set_debug(&mut self, debug: bool) -> &mut Self { | ||||
|         self.debug = debug; | ||||
|         self | ||||
|     } | ||||
|      | ||||
|     pub fn debug(&self) -> bool { | ||||
|         self.debug | ||||
|     } | ||||
|      | ||||
|     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())), | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     pub fn install(&self, package: &str) -> Result<CommandResult, PackageError> { | ||||
|         let pm = self.get_package_manager()?; | ||||
|         pm.install(package) | ||||
|     } | ||||
|      | ||||
|     pub fn remove(&self, package: &str) -> Result<CommandResult, PackageError> { | ||||
|         let pm = self.get_package_manager()?; | ||||
|         pm.remove(package) | ||||
|     } | ||||
|      | ||||
|     pub fn update(&self) -> Result<CommandResult, PackageError> { | ||||
|         let pm = self.get_package_manager()?; | ||||
|         pm.update() | ||||
|     } | ||||
|      | ||||
|     pub fn upgrade(&self) -> Result<CommandResult, PackageError> { | ||||
|         let pm = self.get_package_manager()?; | ||||
|         pm.upgrade() | ||||
|     } | ||||
|      | ||||
|     pub fn list_installed(&self) -> Result<Vec<String>, PackageError> { | ||||
|         let pm = self.get_package_manager()?; | ||||
|         pm.list_installed() | ||||
|     } | ||||
|      | ||||
|     pub fn search(&self, query: &str) -> Result<Vec<String>, PackageError> { | ||||
|         let pm = self.get_package_manager()?; | ||||
|         pm.search(query) | ||||
|     } | ||||
|      | ||||
|     pub fn is_installed(&self, package: &str) -> Result<bool, PackageError> { | ||||
|         let pm = self.get_package_manager()?; | ||||
|         pm.is_installed(package) | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### 7. Rhai Integration | ||||
|  | ||||
| Update the Rhai OS module to include the package management functions: | ||||
|  | ||||
| ```rust | ||||
| // In rhai/os.rs | ||||
|  | ||||
| // 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); | ||||
|  | ||||
| // Wrapper for os::package::install | ||||
| pub fn package_install(package: &str) -> Result<String, Box<EvalAltResult>> { | ||||
|     let hero = os::package::PackHero::new(); | ||||
|     hero.install(package) | ||||
|         .map(|_| "Package installed successfully".to_string()) | ||||
|         .to_rhai_error() | ||||
| } | ||||
|  | ||||
| // Wrapper for os::package::remove | ||||
| pub fn package_remove(package: &str) -> Result<String, Box<EvalAltResult>> { | ||||
|     let hero = os::package::PackHero::new(); | ||||
|     hero.remove(package) | ||||
|         .map(|_| "Package removed successfully".to_string()) | ||||
|         .to_rhai_error() | ||||
| } | ||||
|  | ||||
| // Wrapper for os::package::update | ||||
| pub fn package_update() -> Result<String, Box<EvalAltResult>> { | ||||
|     let hero = os::package::PackHero::new(); | ||||
|     hero.update() | ||||
|         .map(|_| "Package lists updated successfully".to_string()) | ||||
|         .to_rhai_error() | ||||
| } | ||||
|  | ||||
| // Wrapper for os::package::upgrade | ||||
| pub fn package_upgrade() -> Result<String, Box<EvalAltResult>> { | ||||
|     let hero = os::package::PackHero::new(); | ||||
|     hero.upgrade() | ||||
|         .map(|_| "Packages upgraded successfully".to_string()) | ||||
|         .to_rhai_error() | ||||
| } | ||||
|  | ||||
| // Wrapper for os::package::list_installed | ||||
| pub fn package_list() -> Result<Array, Box<EvalAltResult>> { | ||||
|     let hero = os::package::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::search | ||||
| pub fn package_search(query: &str) -> Result<Array, Box<EvalAltResult>> { | ||||
|     let hero = os::package::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::is_installed | ||||
| pub fn package_is_installed(package: &str) -> Result<bool, Box<EvalAltResult>> { | ||||
|     let hero = os::package::PackHero::new(); | ||||
|     hero.is_installed(package).to_rhai_error() | ||||
| } | ||||
|  | ||||
| // Global debug flag for package management | ||||
| static mut PACKAGE_DEBUG: bool = false; | ||||
|  | ||||
| // Wrapper for setting package debug mode | ||||
| pub fn package_set_debug(debug: bool) -> bool { | ||||
|     unsafe { | ||||
|         PACKAGE_DEBUG = debug; | ||||
|         PACKAGE_DEBUG | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## Implementation Steps | ||||
|  | ||||
| 1. Create the package.rs file in the os directory | ||||
| 2. Implement the PackageError enum | ||||
| 3. Implement the Platform enum with detection logic | ||||
| 4. Implement the PackageManager trait | ||||
| 5. Implement the AptPackageManager for Ubuntu | ||||
| 6. Implement the BrewPackageManager for macOS | ||||
| 7. Implement the debug functionality with thread-local storage | ||||
| 8. Implement the PackHero factory | ||||
| 9. Update os/mod.rs to include the package module | ||||
| 10. Update rhai/os.rs to include the package management functions | ||||
| 11. Test the implementation on both Ubuntu and macOS | ||||
|  | ||||
| ## Testing Strategy | ||||
|  | ||||
| 1. Create unit tests for each platform-specific implementation | ||||
| 2. Create integration tests that verify the PackHero factory correctly detects the platform and uses the appropriate package manager | ||||
| 3. Create Rhai examples that demonstrate the use of the package management functions | ||||
							
								
								
									
										134
									
								
								src/rhai/os.rs
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								src/rhai/os.rs
									
									
									
									
									
								
							| @@ -4,6 +4,7 @@ | ||||
|  | ||||
| use rhai::{Engine, EvalAltResult, Array}; | ||||
| use crate::os; | ||||
| use crate::os::package::PackHero; | ||||
| use super::error::{ToRhaiError, register_error_types}; | ||||
|  | ||||
| /// Register OS module functions with the Rhai engine | ||||
| @@ -45,6 +46,20 @@ pub fn register_os_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> | ||||
|     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); | ||||
|      | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| @@ -166,6 +181,13 @@ pub fn file_write_append(path: &str, content: &str) -> Result<String, Box<EvalAl | ||||
|     os::file_write_append(path, content).to_rhai_error() | ||||
| } | ||||
|  | ||||
| /// Wrapper for os::mv | ||||
| /// | ||||
| /// Move a file or directory from source to destination. | ||||
| pub fn mv(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> { | ||||
|     os::mv(src, dest).to_rhai_error() | ||||
| } | ||||
|  | ||||
| // | ||||
| // Download Function Wrappers | ||||
| // | ||||
| @@ -211,4 +233,116 @@ pub fn which(command: &str) -> String { | ||||
| /// If any command doesn't exist, an error is thrown. | ||||
| pub fn cmd_ensure_exists(commands: &str) -> Result<String, Box<EvalAltResult>> { | ||||
|     os::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() { | ||||
|         os::package::Platform::Ubuntu => "Ubuntu".to_string(), | ||||
|         os::package::Platform::MacOS => "MacOS".to_string(), | ||||
|         os::package::Platform::Unknown => "Unknown".to_string(), | ||||
|     } | ||||
| } | ||||
							
								
								
									
										210
									
								
								src/rhaiexamples/containerd_grpc_setup.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								src/rhaiexamples/containerd_grpc_setup.rhai
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,210 @@ | ||||
| // containerd_grpc_setup.rhai | ||||
| // | ||||
| // This script sets up a Rust project with gRPC connectivity to containerd | ||||
| // Following the steps from the instructions document | ||||
|  | ||||
|  | ||||
| run("apt-get -y protobuf-compiler "); | ||||
|  | ||||
| // Step 1: Set up project directory | ||||
| let project_dir = "/tmp/containerd-rust-client"; | ||||
| print(`Setting up project in: ${project_dir}`); | ||||
|  | ||||
| // Clean up any existing directory | ||||
| if exist(project_dir) { | ||||
|     print("Found existing project directory, removing it..."); | ||||
|     delete(project_dir); | ||||
| } | ||||
|  | ||||
| // Create our project directory | ||||
| mkdir(project_dir); | ||||
|  | ||||
| // Change to the project directory | ||||
| chdir(project_dir); | ||||
|  | ||||
| // Step 2: Clone containerd's gRPC proto files | ||||
| print("Cloning containerd repository to get proto files..."); | ||||
| let git_tree = gittree_new(project_dir); | ||||
| let repos = git_tree.get("https://github.com/containerd/containerd.git"); | ||||
| let repo = repos[0]; | ||||
| print(`Cloned containerd repository to: ${repo.path()}`); | ||||
|  | ||||
| // Step 3: Create necessary project files | ||||
| print("Creating Cargo.toml file..."); | ||||
| // Using raw string with # for multiline content | ||||
| let cargo_toml = #" | ||||
| [package] | ||||
| name = "containerd-rust-client" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| tonic = "0.11" | ||||
| prost = "0.12" | ||||
| tokio = { version = "1", features = ["full"] } | ||||
| hyper-unix-connector = "0.2.0" | ||||
| tower = "0.4" | ||||
|  | ||||
| [build-dependencies] | ||||
| tonic-build = "0.11" | ||||
| "#; | ||||
|  | ||||
| file_write("Cargo.toml", cargo_toml); | ||||
| print("Created Cargo.toml file"); | ||||
|  | ||||
| // Step 4: Set up build.rs to compile protos | ||||
| print("Creating build.rs file..."); | ||||
| let build_rs = #" | ||||
| fn main() { | ||||
|     println!("cargo:rerun-if-changed=containerd/api/services/images/v1/images.proto"); | ||||
|     println!("cargo:rerun-if-changed=containerd/api/services/containers/v1/containers.proto"); | ||||
|      | ||||
|     tonic_build::configure() | ||||
|         .build_server(false) | ||||
|         .compile( | ||||
|             &[ | ||||
|                 "containerd/api/services/images/v1/images.proto",  | ||||
|                 "containerd/api/services/containers/v1/containers.proto", | ||||
|                 // Add more proto files as needed | ||||
|             ], | ||||
|             &[ | ||||
|                 "containerd",  | ||||
|                 "containerd/api",  | ||||
|                 "containerd/api/types" | ||||
|             ], | ||||
|         ) | ||||
|         .unwrap(); | ||||
| } | ||||
| "#; | ||||
|  | ||||
| file_write("build.rs", build_rs); | ||||
| print("Created build.rs file"); | ||||
|  | ||||
| // Step 5: Create src directory and main.rs file | ||||
| mkdir("src"); | ||||
|  | ||||
| // Create a helper function for Unix socket connection | ||||
| print("Creating src/main.rs file..."); | ||||
| let main_rs = #" | ||||
| use tonic::transport::{Channel, Endpoint, Uri}; | ||||
| use tower::service_fn; | ||||
| use std::convert::TryFrom; | ||||
|  | ||||
| // The proto-generated modules will be available after build | ||||
| // use containerd::services::images::v1::{ | ||||
| //     images_client::ImagesClient, | ||||
| //     GetImageRequest, | ||||
| // }; | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     println!("Connecting to containerd gRPC..."); | ||||
|      | ||||
|     // Path to containerd socket | ||||
|     let socket_path = "/run/containerd/containerd.sock"; | ||||
|      | ||||
|     // Connect to the Unix socket | ||||
|     let channel = unix_socket_channel(socket_path).await?; | ||||
|      | ||||
|     // Now we'd create a client and use it | ||||
|     // let mut client = ImagesClient::new(channel); | ||||
|     // let response = client.get(GetImageRequest { | ||||
|     //     name: "docker.io/library/ubuntu:latest".to_string(), | ||||
|     // }).await?; | ||||
|     // println!("Image: {:?}", response.into_inner()); | ||||
|      | ||||
|     println!("Connection to containerd socket established successfully!"); | ||||
|     println!("This is a template - uncomment the client code after building."); | ||||
|      | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| // Helper function to connect to Unix socket | ||||
| async fn unix_socket_channel(path: &str) -> Result<Channel, Box<dyn std::error::Error>> { | ||||
|     // Use a placeholder URI since Unix sockets don't have URIs | ||||
|     let endpoint = Endpoint::try_from("http://[::]:50051")?; | ||||
|      | ||||
|     // The socket path to connect to | ||||
|     let path_to_connect = path.to_string(); | ||||
|      | ||||
|     // Create a connector function that connects to the Unix socket | ||||
|     let channel = endpoint | ||||
|         .connect_with_connector(service_fn(move |_: Uri| { | ||||
|             let path = path_to_connect.clone(); | ||||
|             async move { | ||||
|                 tokio::net::UnixStream::connect(path) | ||||
|                     .await | ||||
|                     .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) | ||||
|             } | ||||
|         })) | ||||
|         .await?; | ||||
|      | ||||
|     Ok(channel) | ||||
| } | ||||
| "#; | ||||
|  | ||||
| file_write("src/main.rs", main_rs); | ||||
| print("Created src/main.rs file"); | ||||
|  | ||||
| // Step 6: Create a README.md file | ||||
| print("Creating README.md file..."); | ||||
| // Using raw string with # for multiline content containing markdown backticks | ||||
| let readme = #"# containerd Rust gRPC Client | ||||
|  | ||||
| A Rust client for interacting with containerd via gRPC. | ||||
|  | ||||
| ## Prerequisites | ||||
|  | ||||
| - Rust and Cargo installed | ||||
| - containerd running on your system | ||||
|  | ||||
| ## Building | ||||
|  | ||||
| ```bash | ||||
| cargo build | ||||
| ``` | ||||
|  | ||||
| ## Running | ||||
|  | ||||
| ```bash | ||||
| cargo run | ||||
| ``` | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| - Connect to containerd via Unix socket | ||||
| - Query image information | ||||
| - Work with containers | ||||
|  | ||||
| ## Structure | ||||
|  | ||||
| - `src/main.rs` - Example client code | ||||
| - `build.rs` - Proto compilation script | ||||
| "#; | ||||
|  | ||||
| file_write("README.md", readme); | ||||
| print("Created README.md file"); | ||||
|  | ||||
| // Step 7: Build the project | ||||
| print("Building the project..."); | ||||
| let build_result = run("cargo build"); | ||||
|  | ||||
| if build_result.success { | ||||
|     print("Project built successfully!"); | ||||
| } else { | ||||
|     print(`Build failed with error: ${build_result.stderr}`); | ||||
| } | ||||
|  | ||||
| print(` | ||||
| -------------------------------------- | ||||
| 🎉 Setup complete! | ||||
|  | ||||
| Project created at: ${project_dir} | ||||
|  | ||||
| To use the project: | ||||
| 1. cd ${project_dir} | ||||
| 2. cargo run | ||||
|  | ||||
| Note: Make sure containerd is running and the socket exists at /run/containerd/containerd.sock | ||||
| -------------------------------------- | ||||
| `); | ||||
| @@ -49,7 +49,7 @@ print(`  Extraction successful: ${success_msg}`); | ||||
|  | ||||
| // Download binary file and check size | ||||
| print("\nTesting binary download:"); | ||||
| download(binary_url, binary_dest, 8000); | ||||
| download_file(binary_url, binary_dest, 8000); | ||||
|  | ||||
| // Check file size using our new file_size function | ||||
| let size_bytes = file_size(binary_dest); | ||||
|   | ||||
							
								
								
									
										124
									
								
								src/rhaiexamples/instructions_grpc.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/rhaiexamples/instructions_grpc.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | ||||
|  | ||||
|  | ||||
| ## ✅ Step-by-step: Connect Rust to containerd via gRPC | ||||
|  | ||||
|  | ||||
| CREATE A rhai script executing the following instructions below | ||||
| use all rhai script features we have + give priority to what functions we have defined in /src/docs/docs/sal in this repo | ||||
|  | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### 1. 🧱 Install Dependencies | ||||
|  | ||||
| Add this to your `Cargo.toml`: | ||||
|  | ||||
| ```toml | ||||
| [dependencies] | ||||
| tonic = "0.11" | ||||
| prost = "0.12" | ||||
| tokio = { version = "1", features = ["full"] } | ||||
|  | ||||
| [build-dependencies] | ||||
| tonic-build = "0.11" | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### 2. 📁 Clone containerd's gRPC proto files | ||||
|  | ||||
| ```bash | ||||
| git clone https://github.com/containerd/containerd.git | ||||
| cd containerd | ||||
| ``` | ||||
|  | ||||
| Containerd's API protos are in:   | ||||
| ``` | ||||
| api/services/         # gRPC service definitions | ||||
| api/types/            # message types | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### 3. 📦 Set up `build.rs` to compile protos | ||||
|  | ||||
| In your Rust project root, create a `build.rs` file: | ||||
|  | ||||
| ```rust | ||||
| fn main() { | ||||
|     tonic_build::configure() | ||||
|         .build_server(false) | ||||
|         .compile( | ||||
|             &[ | ||||
|                 "containerd/api/services/images/v1/images.proto",  | ||||
|                 "containerd/api/services/containers/v1/containers.proto", | ||||
|                 // Add more proto files as needed | ||||
|             ], | ||||
|             &[ | ||||
|                 "containerd/api",  | ||||
|                 "containerd/api/types" | ||||
|             ], | ||||
|         ) | ||||
|         .unwrap(); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Make sure to place the `containerd` directory somewhere your build can see — for example, symlink it or move it into your project as `proto/containerd`. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### 4. 🧪 Example: Connect to containerd's image service | ||||
|  | ||||
| After `build.rs` compiles the protos, your code can access them like this: | ||||
|  | ||||
| ```rust | ||||
| use tonic::transport::Channel; | ||||
| use containerd::services::images::v1::{ | ||||
|     images_client::ImagesClient, | ||||
|     GetImageRequest, | ||||
| }; | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     // Connect to containerd's gRPC socket (default path) | ||||
|     let channel = Channel::from_static("http://[::]:50051") // placeholder | ||||
|         .connect() | ||||
|         .await?; | ||||
|  | ||||
|     let mut client = ImagesClient::new(channel); | ||||
|  | ||||
|     let response = client.get(GetImageRequest { | ||||
|         name: "docker.io/library/ubuntu:latest".to_string(), | ||||
|     }).await?; | ||||
|  | ||||
|     println!("Image: {:?}", response.into_inner()); | ||||
|     Ok(()) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| 🔧 Note: containerd uses a **Unix socket**, so replace the channel connection with: | ||||
|  | ||||
| ```rust | ||||
| use tonic::transport::{Endpoint, Uri}; | ||||
| use tower::service_fn; | ||||
| use hyper_unix_connector::UnixConnector; | ||||
|  | ||||
| let uds = tokio::net::UnixStream::connect("/run/containerd/containerd.sock").await?; | ||||
| let channel = Endpoint::try_from("http://[::]:50051")? | ||||
|     .connect_with_connector(service_fn(move |_| async move { | ||||
|         Ok::<_, std::io::Error>(uds) | ||||
|     })) | ||||
|     .await?; | ||||
| ``` | ||||
|  | ||||
| (We can wrap that part into a helper if you want.) | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### 5. 🔁 Rebuild the project | ||||
|  | ||||
| Each time you add or change a `.proto`, rebuild to regenerate code: | ||||
|  | ||||
| ```bash | ||||
| cargo clean && cargo build | ||||
| ``` | ||||
| @@ -2,25 +2,24 @@ | ||||
|  | ||||
|  | ||||
| fn nerdctl_download(){ | ||||
|     // let name="nerdctl"; | ||||
|     // let url="https://github.com/containerd/nerdctl/releases/download/v2.0.4/nerdctl-2.0.4-linux-amd64.tar.gz"; | ||||
|     // download(url,`/tmp/${name}`,20000); | ||||
|     // copy(`/tmp/${name}/*`,"/root/hero/bin/"); | ||||
|     // delete(`/tmp/${name}`); | ||||
|     let name="nerdctl"; | ||||
|     let url="https://github.com/containerd/nerdctl/releases/download/v2.0.4/nerdctl-2.0.4-linux-amd64.tar.gz"; | ||||
|     download(url,`/tmp/${name}`,20000); | ||||
|     copy(`/tmp/${name}/*`,"/root/hero/bin/"); | ||||
|     delete(`/tmp/${name}`); | ||||
|      | ||||
|     // let name="containerd"; | ||||
|     // let url="https://github.com/containerd/containerd/releases/download/v2.0.4/containerd-2.0.4-linux-amd64.tar.gz"; | ||||
|     // download(url,`/tmp/${name}`,20000); | ||||
|     // copy(`/tmp/${name}/bin/*`,"/root/hero/bin/"); | ||||
|     // delete(`/tmp/${name}`); | ||||
|     let name="containerd"; | ||||
|     let url="https://github.com/containerd/containerd/releases/download/v2.0.4/containerd-2.0.4-linux-amd64.tar.gz"; | ||||
|     download(url,`/tmp/${name}`,20000); | ||||
|     copy(`/tmp/${name}/bin/*`,"/root/hero/bin/"); | ||||
|     delete(`/tmp/${name}`); | ||||
|  | ||||
|     // run("apt-get -y install buildah runc") | ||||
|     run("apt-get -y install buildah runc"); | ||||
|  | ||||
|     let url="https://github.com/threefoldtech/rfs/releases/download/v2.0.6/rfs"; | ||||
|     download_file(url,`/tmp/rfs`,10000); | ||||
|     chmod_exec(url); | ||||
|     copy(`/tmp/rfs`,"/root/hero/bin/"); | ||||
|     //delete(`/tmp/rfs`);     | ||||
|     chmod_exec("/tmp/rfs"); | ||||
|     mv(`/tmp/rfs`,"/root/hero/bin/"); | ||||
|  | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										113
									
								
								src/rhaiexamples/package_management.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/rhaiexamples/package_management.rhai
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| // Example script demonstrating the package management functions | ||||
|  | ||||
| // Set debug mode to true to see detailed output | ||||
| package_set_debug(true); | ||||
|  | ||||
| // Function to demonstrate package management on Ubuntu | ||||
| fn demo_ubuntu() { | ||||
|     print("Demonstrating package management on Ubuntu..."); | ||||
|      | ||||
|     // Update package lists | ||||
|     print("Updating package lists..."); | ||||
|     let result = package_update(); | ||||
|     print(`Update result: ${result}`); | ||||
|      | ||||
|     // Check if a package is installed | ||||
|     let package = "htop"; | ||||
|     print(`Checking if ${package} is installed...`); | ||||
|     let is_installed = package_is_installed(package); | ||||
|     print(`${package} is installed: ${is_installed}`); | ||||
|      | ||||
|     // Install a package if not already installed | ||||
|     if !is_installed { | ||||
|         print(`Installing ${package}...`); | ||||
|         let install_result = package_install(package); | ||||
|         print(`Install result: ${install_result}`); | ||||
|     } | ||||
|      | ||||
|     // List installed packages (limited to first 5 for brevity) | ||||
|     print("Listing installed packages (first 5)..."); | ||||
|     let packages = package_list(); | ||||
|     for i in 0..min(5, packages.len()) { | ||||
|         print(`  - ${packages[i]}`); | ||||
|     } | ||||
|      | ||||
|     // Search for packages | ||||
|     let search_term = "editor"; | ||||
|     print(`Searching for packages with term '${search_term}'...`); | ||||
|     let search_results = package_search(search_term); | ||||
|     print(`Found ${search_results.len()} packages. First 5 results:`); | ||||
|     for i in 0..min(5, search_results.len()) { | ||||
|         print(`  - ${search_results[i]}`); | ||||
|     } | ||||
|      | ||||
|     // Remove the package if we installed it | ||||
|     if !is_installed { | ||||
|         print(`Removing ${package}...`); | ||||
|         let remove_result = package_remove(package); | ||||
|         print(`Remove result: ${remove_result}`); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Function to demonstrate package management on macOS | ||||
| fn demo_macos() { | ||||
|     print("Demonstrating package management on macOS..."); | ||||
|      | ||||
|     // Update package lists | ||||
|     print("Updating package lists..."); | ||||
|     let result = package_update(); | ||||
|     print(`Update result: ${result}`); | ||||
|      | ||||
|     // Check if a package is installed | ||||
|     let package = "wget"; | ||||
|     print(`Checking if ${package} is installed...`); | ||||
|     let is_installed = package_is_installed(package); | ||||
|     print(`${package} is installed: ${is_installed}`); | ||||
|      | ||||
|     // Install a package if not already installed | ||||
|     if !is_installed { | ||||
|         print(`Installing ${package}...`); | ||||
|         let install_result = package_install(package); | ||||
|         print(`Install result: ${install_result}`); | ||||
|     } | ||||
|      | ||||
|     // List installed packages (limited to first 5 for brevity) | ||||
|     print("Listing installed packages (first 5)..."); | ||||
|     let packages = package_list(); | ||||
|     for i in 0..min(5, packages.len()) { | ||||
|         print(`  - ${packages[i]}`); | ||||
|     } | ||||
|      | ||||
|     // Search for packages | ||||
|     let search_term = "editor"; | ||||
|     print(`Searching for packages with term '${search_term}'...`); | ||||
|     let search_results = package_search(search_term); | ||||
|     print(`Found ${search_results.len()} packages. First 5 results:`); | ||||
|     for i in 0..min(5, search_results.len()) { | ||||
|         print(`  - ${search_results[i]}`); | ||||
|     } | ||||
|      | ||||
|     // Remove the package if we installed it | ||||
|     if !is_installed { | ||||
|         print(`Removing ${package}...`); | ||||
|         let remove_result = package_remove(package); | ||||
|         print(`Remove result: ${remove_result}`); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Detect platform and run the appropriate demo | ||||
| fn main() { | ||||
|     // Create a PackHero instance to detect the platform | ||||
|     let platform = package_platform(); | ||||
|      | ||||
|     if platform == "Ubuntu" { | ||||
|         demo_ubuntu(); | ||||
|     } else if platform == "MacOS" { | ||||
|         demo_macos(); | ||||
|     } else { | ||||
|         print(`Unsupported platform: ${platform}`); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Run the main function | ||||
| main(); | ||||
| @@ -1,6 +1,7 @@ | ||||
|  | ||||
| use regex::Regex; | ||||
| use std::fs; | ||||
| use std::io::{self, Read}; | ||||
| use std::io::{self, Read, Seek, SeekFrom}; | ||||
| use std::path::Path; | ||||
|  | ||||
| /// Represents the type of replacement to perform. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user