feat: Improve package management and testing
- Improve platform detection logic for more robust package management. - Enhance error handling and reporting in package commands. - Refactor code for better readability and maintainability. - Add comprehensive tests to cover package management functionality. - Improve test coverage for various scenarios and edge cases.
This commit is contained in:
		| @@ -1,5 +1,5 @@ | ||||
| use std::process::Command; | ||||
| use crate::process::CommandResult; | ||||
| use std::process::Command; | ||||
|  | ||||
| /// Error type for package management operations | ||||
| #[derive(Debug)] | ||||
| @@ -45,7 +45,7 @@ impl Platform { | ||||
|         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 | ||||
| @@ -55,12 +55,12 @@ impl Platform { | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|          | ||||
|  | ||||
|         Platform::Unknown | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Thread-local storage for debug flag | ||||
| // Thread-local storage for debug flag | ||||
| thread_local! { | ||||
|     static DEBUG: std::cell::RefCell<bool> = std::cell::RefCell::new(false); | ||||
| } | ||||
| @@ -74,70 +74,73 @@ pub fn set_thread_local_debug(debug: bool) { | ||||
|  | ||||
| /// Get the debug flag for the current thread | ||||
| pub fn thread_local_debug() -> bool { | ||||
|     DEBUG.with(|cell| { | ||||
|         *cell.borrow() | ||||
|     }) | ||||
|     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(); | ||||
|      | ||||
|  | ||||
|     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()); | ||||
|                     println!( | ||||
|                         "Command failed with code {}: {}", | ||||
|                         result.code, | ||||
|                         result.stderr.trim() | ||||
|                     ); | ||||
|                 } | ||||
|                 Err(PackageError::CommandFailed(format!("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); | ||||
| @@ -150,22 +153,22 @@ pub fn execute_package_command(args: &[&str], debug: bool) -> Result<CommandResu | ||||
| 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>; | ||||
| } | ||||
| @@ -185,27 +188,31 @@ impl AptPackageManager { | ||||
| 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) | ||||
|         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 | ||||
|         let packages = result | ||||
|             .stdout | ||||
|             .lines() | ||||
|             .filter_map(|line| { | ||||
|                 let parts: Vec<&str> = line.split_whitespace().collect(); | ||||
| @@ -218,10 +225,11 @@ impl PackageManager for AptPackageManager { | ||||
|             .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 | ||||
|         let packages = result | ||||
|             .stdout | ||||
|             .lines() | ||||
|             .map(|line| { | ||||
|                 let parts: Vec<&str> = line.split_whitespace().collect(); | ||||
| @@ -235,7 +243,7 @@ impl PackageManager for AptPackageManager { | ||||
|             .collect(); | ||||
|         Ok(packages) | ||||
|     } | ||||
|      | ||||
|  | ||||
|     fn is_installed(&self, package: &str) -> Result<bool, PackageError> { | ||||
|         let result = execute_package_command(&["dpkg", "-s", package], self.debug); | ||||
|         match result { | ||||
| @@ -262,42 +270,44 @@ impl PackageManager for BrewPackageManager { | ||||
|         // 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 | ||||
|         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 | ||||
|         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 { | ||||
| @@ -322,68 +332,70 @@ impl PackHero { | ||||
|             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())), | ||||
|             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()?; | ||||
| @@ -394,47 +406,49 @@ impl PackHero { | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     // Import the std::process::Command directly for some test-specific commands | ||||
|     use std::process::Command as StdCommand; | ||||
|     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>>, | ||||
| @@ -446,7 +460,7 @@ mod tests { | ||||
|         // Control what the mock returns | ||||
|         should_succeed: bool, | ||||
|     } | ||||
|      | ||||
|  | ||||
|     impl MockPackageManager { | ||||
|         fn new(debug: bool, should_succeed: bool) -> Self { | ||||
|             Self { | ||||
| @@ -462,7 +476,7 @@ mod tests { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|  | ||||
|     impl PackageManager for MockPackageManager { | ||||
|         fn install(&self, package: &str) -> Result<CommandResult, PackageError> { | ||||
|             *self.install_called.lock().unwrap() = true; | ||||
| @@ -474,10 +488,12 @@ mod tests { | ||||
|                     code: 0, | ||||
|                 }) | ||||
|             } else { | ||||
|                 Err(PackageError::CommandFailed("Mock install failed".to_string())) | ||||
|                 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 { | ||||
| @@ -488,10 +504,12 @@ mod tests { | ||||
|                     code: 0, | ||||
|                 }) | ||||
|             } else { | ||||
|                 Err(PackageError::CommandFailed("Mock remove failed".to_string())) | ||||
|                 Err(PackageError::CommandFailed( | ||||
|                     "Mock remove failed".to_string(), | ||||
|                 )) | ||||
|             } | ||||
|         } | ||||
|          | ||||
|  | ||||
|         fn update(&self) -> Result<CommandResult, PackageError> { | ||||
|             *self.update_called.lock().unwrap() = true; | ||||
|             if self.should_succeed { | ||||
| @@ -502,10 +520,12 @@ mod tests { | ||||
|                     code: 0, | ||||
|                 }) | ||||
|             } else { | ||||
|                 Err(PackageError::CommandFailed("Mock update failed".to_string())) | ||||
|                 Err(PackageError::CommandFailed( | ||||
|                     "Mock update failed".to_string(), | ||||
|                 )) | ||||
|             } | ||||
|         } | ||||
|          | ||||
|  | ||||
|         fn upgrade(&self) -> Result<CommandResult, PackageError> { | ||||
|             *self.upgrade_called.lock().unwrap() = true; | ||||
|             if self.should_succeed { | ||||
| @@ -516,45 +536,57 @@ mod tests { | ||||
|                     code: 0, | ||||
|                 }) | ||||
|             } else { | ||||
|                 Err(PackageError::CommandFailed("Mock upgrade failed".to_string())) | ||||
|                 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())) | ||||
|                 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)]) | ||||
|                 Ok(vec![ | ||||
|                     format!("result1-{}", query), | ||||
|                     format!("result2-{}", query), | ||||
|                 ]) | ||||
|             } else { | ||||
|                 Err(PackageError::CommandFailed("Mock search failed".to_string())) | ||||
|                 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())) | ||||
|                 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 { | ||||
| @@ -563,144 +595,152 @@ mod tests { | ||||
|                 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())), | ||||
|                 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_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_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()); | ||||
| @@ -708,14 +748,14 @@ mod tests { | ||||
|             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 { | ||||
| @@ -723,7 +763,7 @@ mod tests { | ||||
|             _ => 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] | ||||
| @@ -731,19 +771,22 @@ mod tests { | ||||
|         // 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); | ||||
|             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, | ||||
| @@ -752,9 +795,12 @@ mod tests { | ||||
|                 return; | ||||
|             } | ||||
|         }; | ||||
|          | ||||
|         println!("Package {} is installed before test: {}", test_package, is_installed_before); | ||||
|          | ||||
|  | ||||
|         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); | ||||
| @@ -765,7 +811,7 @@ mod tests { | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|  | ||||
|             // Verify it was removed | ||||
|             match hero.is_installed(test_package) { | ||||
|                 Ok(is_installed) => { | ||||
| @@ -775,14 +821,17 @@ mod tests { | ||||
|                     } else { | ||||
|                         println!("Verified package {} was removed", test_package); | ||||
|                     } | ||||
|                 }, | ||||
|                 } | ||||
|                 Err(e) => { | ||||
|                     println!("Error checking if package is installed after removal: {}", 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) { | ||||
| @@ -792,7 +841,7 @@ mod tests { | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|  | ||||
|         // Verify it was installed | ||||
|         match hero.is_installed(test_package) { | ||||
|             Ok(is_installed) => { | ||||
| @@ -802,41 +851,50 @@ mod tests { | ||||
|                 } else { | ||||
|                     println!("Verified package {} was installed", test_package); | ||||
|                 } | ||||
|             }, | ||||
|             } | ||||
|             Err(e) => { | ||||
|                 println!("Error checking if package is installed after installation: {}", 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"); | ||||
|             }, | ||||
|                 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); | ||||
|             }, | ||||
|                 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); | ||||
| @@ -847,7 +905,7 @@ mod tests { | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|  | ||||
|             // Verify it was removed | ||||
|             match hero.is_installed(test_package) { | ||||
|                 Ok(is_installed) => { | ||||
| @@ -857,14 +915,17 @@ mod tests { | ||||
|                     } else { | ||||
|                         println!("Verified package {} was removed", test_package); | ||||
|                     } | ||||
|                 }, | ||||
|                 } | ||||
|                 Err(e) => { | ||||
|                     println!("Error checking if package is installed after removal: {}", e); | ||||
|                     println!( | ||||
|                         "Error checking if package is installed after removal: {}", | ||||
|                         e | ||||
|                     ); | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|          | ||||
|  | ||||
|         // Test update functionality | ||||
|         println!("Testing package list update"); | ||||
|         match hero.update() { | ||||
| @@ -874,10 +935,10 @@ mod tests { | ||||
|                 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() { | ||||
| @@ -886,18 +947,18 @@ mod tests { | ||||
|             .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"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
|  | ||||
| use regex::Regex; | ||||
| use std::fs; | ||||
| use std::io::{self, Read, Seek, SeekFrom}; | ||||
| use std::io::{self, Read}; | ||||
| use std::path::Path; | ||||
|  | ||||
| /// Represents the type of replacement to perform. | ||||
| @@ -46,36 +45,36 @@ impl TextReplacer { | ||||
|     /// Applies all configured replacement operations to the input text | ||||
|     pub fn replace(&self, input: &str) -> String { | ||||
|         let mut result = input.to_string(); | ||||
|          | ||||
|  | ||||
|         // Apply each replacement operation in sequence | ||||
|         for op in &self.operations { | ||||
|             result = op.apply(&result); | ||||
|         } | ||||
|          | ||||
|  | ||||
|         result | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Reads a file, applies all replacements, and returns the result as a string | ||||
|     pub fn replace_file<P: AsRef<Path>>(&self, path: P) -> io::Result<String> { | ||||
|         let mut file = fs::File::open(path)?; | ||||
|         let mut content = String::new(); | ||||
|         file.read_to_string(&mut content)?; | ||||
|          | ||||
|  | ||||
|         Ok(self.replace(&content)) | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Reads a file, applies all replacements, and writes the result back to the file | ||||
|     pub fn replace_file_in_place<P: AsRef<Path>>(&self, path: P) -> io::Result<()> { | ||||
|         let content = self.replace_file(&path)?; | ||||
|         fs::write(path, content)?; | ||||
|         Ok(()) | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Reads a file, applies all replacements, and writes the result to a new file | ||||
|     pub fn replace_file_to<P1: AsRef<Path>, P2: AsRef<Path>>( | ||||
|         &self,  | ||||
|         input_path: P1,  | ||||
|         output_path: P2 | ||||
|         &self, | ||||
|         input_path: P1, | ||||
|         output_path: P2, | ||||
|     ) -> io::Result<()> { | ||||
|         let content = self.replace_file(&input_path)?; | ||||
|         fs::write(output_path, content)?; | ||||
| @@ -111,13 +110,13 @@ impl TextReplacerBuilder { | ||||
|         self.use_regex = yes; | ||||
|         self | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Sets whether the replacement should be case-insensitive | ||||
|     pub fn case_insensitive(mut self, yes: bool) -> Self { | ||||
|         self.case_insensitive = yes; | ||||
|         self | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Adds another replacement operation to the chain and resets the builder for a new operation | ||||
|     pub fn and(mut self) -> Self { | ||||
|         self.add_current_operation(); | ||||
| @@ -130,20 +129,20 @@ impl TextReplacerBuilder { | ||||
|             let replacement = self.replacement.take().unwrap_or_default(); | ||||
|             let use_regex = self.use_regex; | ||||
|             let case_insensitive = self.case_insensitive; | ||||
|              | ||||
|  | ||||
|             // Reset current settings | ||||
|             self.use_regex = false; | ||||
|             self.case_insensitive = false; | ||||
|              | ||||
|  | ||||
|             // Create the replacement mode | ||||
|             let mode = if use_regex { | ||||
|                 let mut regex_pattern = pattern; | ||||
|                  | ||||
|  | ||||
|                 // If case insensitive, add the flag to the regex pattern | ||||
|                 if case_insensitive && !regex_pattern.starts_with("(?i)") { | ||||
|                     regex_pattern = format!("(?i){}", regex_pattern); | ||||
|                 } | ||||
|                  | ||||
|  | ||||
|                 match Regex::new(®ex_pattern) { | ||||
|                     Ok(re) => ReplaceMode::Regex(re), | ||||
|                     Err(_) => return false, // Failed to compile regex | ||||
| @@ -156,12 +155,10 @@ impl TextReplacerBuilder { | ||||
|                 } | ||||
|                 ReplaceMode::Literal(pattern) | ||||
|             }; | ||||
|              | ||||
|             self.operations.push(ReplacementOperation { | ||||
|                 mode, | ||||
|                 replacement, | ||||
|             }); | ||||
|              | ||||
|  | ||||
|             self.operations | ||||
|                 .push(ReplacementOperation { mode, replacement }); | ||||
|  | ||||
|             true | ||||
|         } else { | ||||
|             false | ||||
| @@ -172,12 +169,12 @@ impl TextReplacerBuilder { | ||||
|     pub fn build(mut self) -> Result<TextReplacer, String> { | ||||
|         // If there's a pending replacement operation, add it | ||||
|         self.add_current_operation(); | ||||
|          | ||||
|  | ||||
|         // Ensure we have at least one replacement operation | ||||
|         if self.operations.is_empty() { | ||||
|             return Err("No replacement operations configured".to_string()); | ||||
|         } | ||||
|          | ||||
|  | ||||
|         Ok(TextReplacer { | ||||
|             operations: self.operations, | ||||
|         }) | ||||
| @@ -187,7 +184,7 @@ impl TextReplacerBuilder { | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|     use std::io::Write; | ||||
|     use std::io::{Seek, SeekFrom, Write}; | ||||
|     use tempfile::NamedTempFile; | ||||
|  | ||||
|     #[test] | ||||
| @@ -219,7 +216,7 @@ mod tests { | ||||
|  | ||||
|         assert_eq!(output, "qux bar qux baz"); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     #[test] | ||||
|     fn test_multiple_replacements() { | ||||
|         let replacer = TextReplacer::builder() | ||||
| @@ -236,7 +233,7 @@ mod tests { | ||||
|  | ||||
|         assert_eq!(output, "qux baz qux"); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     #[test] | ||||
|     fn test_case_insensitive_regex() { | ||||
|         let replacer = TextReplacer::builder() | ||||
| @@ -252,44 +249,44 @@ mod tests { | ||||
|  | ||||
|         assert_eq!(output, "bar bar bar"); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     #[test] | ||||
|     fn test_file_operations() -> io::Result<()> { | ||||
|         // Create a temporary file | ||||
|         let mut temp_file = NamedTempFile::new()?; | ||||
|         writeln!(temp_file, "foo bar foo baz")?; | ||||
|          | ||||
|  | ||||
|         // Flush the file to ensure content is written | ||||
|         temp_file.as_file_mut().flush()?; | ||||
|          | ||||
|  | ||||
|         let replacer = TextReplacer::builder() | ||||
|             .pattern("foo") | ||||
|             .replacement("qux") | ||||
|             .build() | ||||
|             .unwrap(); | ||||
|              | ||||
|  | ||||
|         // Test replace_file | ||||
|         let result = replacer.replace_file(temp_file.path())?; | ||||
|         assert_eq!(result, "qux bar qux baz\n"); | ||||
|          | ||||
|  | ||||
|         // Test replace_file_in_place | ||||
|         replacer.replace_file_in_place(temp_file.path())?; | ||||
|          | ||||
|  | ||||
|         // Verify the file was updated - need to seek to beginning of file first | ||||
|         let mut content = String::new(); | ||||
|         temp_file.as_file_mut().seek(SeekFrom::Start(0))?; | ||||
|         temp_file.as_file_mut().read_to_string(&mut content)?; | ||||
|         assert_eq!(content, "qux bar qux baz\n"); | ||||
|          | ||||
|  | ||||
|         // Test replace_file_to with a new temporary file | ||||
|         let output_file = NamedTempFile::new()?; | ||||
|         replacer.replace_file_to(temp_file.path(), output_file.path())?; | ||||
|          | ||||
|  | ||||
|         // Verify the output file has the replaced content | ||||
|         let mut output_content = String::new(); | ||||
|         fs::File::open(output_file.path())?.read_to_string(&mut output_content)?; | ||||
|         assert_eq!(output_content, "qux bar qux baz\n"); | ||||
|          | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| use std::collections::HashMap; | ||||
| use super::{ | ||||
|     error::RfsError, | ||||
|     cmd::execute_rfs_command, | ||||
|     error::RfsError, | ||||
|     types::{Mount, MountType, StoreSpec}, | ||||
| }; | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| /// Builder for RFS mount operations | ||||
| #[derive(Clone)] | ||||
| @@ -17,6 +17,7 @@ pub struct RfsBuilder { | ||||
|     /// Mount options | ||||
|     options: HashMap<String, String>, | ||||
|     /// Mount ID | ||||
|     #[allow(dead_code)] | ||||
|     mount_id: Option<String>, | ||||
|     /// Debug mode | ||||
|     debug: bool, | ||||
| @@ -44,7 +45,7 @@ impl RfsBuilder { | ||||
|             debug: false, | ||||
|         } | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Add a mount option | ||||
|     /// | ||||
|     /// # Arguments | ||||
| @@ -59,7 +60,7 @@ impl RfsBuilder { | ||||
|         self.options.insert(key.to_string(), value.to_string()); | ||||
|         self | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Add multiple mount options | ||||
|     /// | ||||
|     /// # Arguments | ||||
| @@ -75,7 +76,7 @@ impl RfsBuilder { | ||||
|         } | ||||
|         self | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Set debug mode | ||||
|     /// | ||||
|     /// # Arguments | ||||
| @@ -89,7 +90,7 @@ impl RfsBuilder { | ||||
|         self.debug = debug; | ||||
|         self | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Mount the filesystem | ||||
|     /// | ||||
|     /// # Returns | ||||
| @@ -99,7 +100,7 @@ impl RfsBuilder { | ||||
|         // Build the command string | ||||
|         let mut cmd = String::from("mount -t "); | ||||
|         cmd.push_str(&self.mount_type.to_string()); | ||||
|          | ||||
|  | ||||
|         // Add options if any | ||||
|         if !self.options.is_empty() { | ||||
|             cmd.push_str(" -o "); | ||||
| @@ -114,35 +115,39 @@ impl RfsBuilder { | ||||
|                 first = false; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|  | ||||
|         // Add source and target | ||||
|         cmd.push_str(" "); | ||||
|         cmd.push_str(&self.source); | ||||
|         cmd.push_str(" "); | ||||
|         cmd.push_str(&self.target); | ||||
|          | ||||
|  | ||||
|         // Split the command into arguments | ||||
|         let args: Vec<&str> = cmd.split_whitespace().collect(); | ||||
|          | ||||
|  | ||||
|         // Execute the command | ||||
|         let result = execute_rfs_command(&args)?; | ||||
|          | ||||
|  | ||||
|         // Parse the output to get the mount ID | ||||
|         let mount_id = result.stdout.trim().to_string(); | ||||
|         if mount_id.is_empty() { | ||||
|             return Err(RfsError::MountFailed("Failed to get mount ID".to_string())); | ||||
|         } | ||||
|          | ||||
|  | ||||
|         // Create and return the Mount struct | ||||
|         Ok(Mount { | ||||
|             id: mount_id, | ||||
|             source: self.source, | ||||
|             target: self.target, | ||||
|             fs_type: self.mount_type.to_string(), | ||||
|             options: self.options.iter().map(|(k, v)| format!("{}={}", k, v)).collect(), | ||||
|             options: self | ||||
|                 .options | ||||
|                 .iter() | ||||
|                 .map(|(k, v)| format!("{}={}", k, v)) | ||||
|                 .collect(), | ||||
|         }) | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Unmount the filesystem | ||||
|     /// | ||||
|     /// # Returns | ||||
| @@ -151,12 +156,15 @@ impl RfsBuilder { | ||||
|     pub fn unmount(&self) -> Result<(), RfsError> { | ||||
|         // Execute the unmount command | ||||
|         let result = execute_rfs_command(&["unmount", &self.target])?; | ||||
|          | ||||
|  | ||||
|         // Check for errors | ||||
|         if !result.success { | ||||
|             return Err(RfsError::UnmountFailed(format!("Failed to unmount {}: {}", self.target, result.stderr))); | ||||
|             return Err(RfsError::UnmountFailed(format!( | ||||
|                 "Failed to unmount {}: {}", | ||||
|                 self.target, result.stderr | ||||
|             ))); | ||||
|         } | ||||
|          | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| @@ -193,7 +201,7 @@ impl PackBuilder { | ||||
|             debug: false, | ||||
|         } | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Add a store specification | ||||
|     /// | ||||
|     /// # Arguments | ||||
| @@ -207,7 +215,7 @@ impl PackBuilder { | ||||
|         self.store_specs.push(store_spec); | ||||
|         self | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Add multiple store specifications | ||||
|     /// | ||||
|     /// # Arguments | ||||
| @@ -221,7 +229,7 @@ impl PackBuilder { | ||||
|         self.store_specs.extend(store_specs); | ||||
|         self | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Set debug mode | ||||
|     /// | ||||
|     /// # Arguments | ||||
| @@ -235,7 +243,7 @@ impl PackBuilder { | ||||
|         self.debug = debug; | ||||
|         self | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Pack the directory | ||||
|     /// | ||||
|     /// # Returns | ||||
| @@ -245,7 +253,7 @@ impl PackBuilder { | ||||
|         // Build the command string | ||||
|         let mut cmd = String::from("pack -m "); | ||||
|         cmd.push_str(&self.output); | ||||
|          | ||||
|  | ||||
|         // Add store specs if any | ||||
|         if !self.store_specs.is_empty() { | ||||
|             cmd.push_str(" -s "); | ||||
| @@ -259,22 +267,25 @@ impl PackBuilder { | ||||
|                 first = false; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|  | ||||
|         // Add directory | ||||
|         cmd.push_str(" "); | ||||
|         cmd.push_str(&self.directory); | ||||
|          | ||||
|  | ||||
|         // Split the command into arguments | ||||
|         let args: Vec<&str> = cmd.split_whitespace().collect(); | ||||
|          | ||||
|  | ||||
|         // Execute the command | ||||
|         let result = execute_rfs_command(&args)?; | ||||
|          | ||||
|  | ||||
|         // Check for errors | ||||
|         if !result.success { | ||||
|             return Err(RfsError::PackFailed(format!("Failed to pack {}: {}", self.directory, result.stderr))); | ||||
|             return Err(RfsError::PackFailed(format!( | ||||
|                 "Failed to pack {}: {}", | ||||
|                 self.directory, result.stderr | ||||
|             ))); | ||||
|         } | ||||
|          | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| use crate::process::{run_command, CommandResult}; | ||||
| use super::error::RfsError; | ||||
| use std::thread_local; | ||||
| use crate::process::{run_command, CommandResult}; | ||||
| use std::cell::RefCell; | ||||
| use std::thread_local; | ||||
|  | ||||
| // Thread-local storage for debug flag | ||||
| thread_local! { | ||||
| @@ -9,6 +9,7 @@ thread_local! { | ||||
| } | ||||
|  | ||||
| /// Set the thread-local debug flag | ||||
| #[allow(dead_code)] | ||||
| pub fn set_thread_local_debug(debug: bool) { | ||||
|     DEBUG.with(|d| { | ||||
|         *d.borrow_mut() = debug; | ||||
| @@ -17,9 +18,7 @@ pub fn set_thread_local_debug(debug: bool) { | ||||
|  | ||||
| /// Get the current thread-local debug flag | ||||
| pub fn thread_local_debug() -> bool { | ||||
|     DEBUG.with(|d| { | ||||
|         *d.borrow() | ||||
|     }) | ||||
|     DEBUG.with(|d| *d.borrow()) | ||||
| } | ||||
|  | ||||
| /// Execute an RFS command with the given arguments | ||||
| @@ -33,30 +32,30 @@ pub fn thread_local_debug() -> bool { | ||||
| /// * `Result<CommandResult, RfsError>` - Command result or error | ||||
| pub fn execute_rfs_command(args: &[&str]) -> Result<CommandResult, RfsError> { | ||||
|     let debug = thread_local_debug(); | ||||
|      | ||||
|  | ||||
|     // Construct the command string | ||||
|     let mut cmd = String::from("rfs"); | ||||
|     for arg in args { | ||||
|         cmd.push(' '); | ||||
|         cmd.push_str(arg); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     if debug { | ||||
|         println!("Executing RFS command: {}", cmd); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     // Execute the command | ||||
|     let result = run_command(&cmd) | ||||
|         .map_err(|e| RfsError::CommandFailed(format!("Failed to execute RFS command: {}", e)))?; | ||||
|      | ||||
|  | ||||
|     if debug { | ||||
|         println!("RFS command result: {:?}", result); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     // Check if the command was successful | ||||
|     if !result.success && !result.stderr.is_empty() { | ||||
|         return Err(RfsError::CommandFailed(result.stderr)); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     Ok(result) | ||||
| } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user