From 22f87b320e1299a94798460471d44ed447e0b2eb Mon Sep 17 00:00:00 2001 From: Mahmoud Emad Date: Fri, 9 May 2025 09:54:32 +0300 Subject: [PATCH] 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. --- src/os/package.rs | 367 +++++++++++++++++++++++----------------- src/text/replace.rs | 73 ++++---- src/virt/rfs/builder.rs | 69 ++++---- src/virt/rfs/cmd.rs | 23 ++- 4 files changed, 300 insertions(+), 232 deletions(-) diff --git a/src/os/package.rs b/src/os/package.rs index 2d2a06b..81a8692 100644 --- a/src/os/package.rs +++ b/src/os/package.rs @@ -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 = 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 { // 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 Result; - + /// Remove a package fn remove(&self, package: &str) -> Result; - + /// Update package lists fn update(&self) -> Result; - + /// Upgrade installed packages fn upgrade(&self) -> Result; - + /// List installed packages fn list_installed(&self) -> Result, PackageError>; - + /// Search for packages fn search(&self, query: &str) -> Result, PackageError>; - + /// Check if a package is installed fn is_installed(&self, package: &str) -> Result; } @@ -185,27 +188,31 @@ impl AptPackageManager { impl PackageManager for AptPackageManager { fn install(&self, package: &str) -> Result { // 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 { // 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 { // 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 { // 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, 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, 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 { 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 { // Use --quiet to reduce output execute_package_command(&["brew", "uninstall", "--quiet", package], self.debug) } - + fn update(&self) -> Result { // Use --quiet to reduce output execute_package_command(&["brew", "update", "--quiet"], self.debug) } - + fn upgrade(&self) -> Result { // Use --quiet to reduce output execute_package_command(&["brew", "upgrade", "--quiet"], self.debug) } - + fn list_installed(&self) -> Result, 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, 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 { 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, 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 { let pm = self.get_package_manager()?; pm.install(package) } - + /// Remove a package pub fn remove(&self, package: &str) -> Result { let pm = self.get_package_manager()?; pm.remove(package) } - + /// Update package lists pub fn update(&self) -> Result { let pm = self.get_package_manager()?; pm.update() } - + /// Upgrade installed packages pub fn upgrade(&self) -> Result { let pm = self.get_package_manager()?; pm.upgrade() } - + /// List installed packages pub fn list_installed(&self) -> Result, PackageError> { let pm = self.get_package_manager()?; pm.list_installed() } - + /// Search for packages pub fn search(&self, query: &str) -> Result, PackageError> { let pm = self.get_package_manager()?; pm.search(query) } - + /// Check if a package is installed pub fn is_installed(&self, package: &str) -> Result { 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>, remove_called: Arc>, @@ -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 { *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 { *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 { *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 { *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, 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, 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 { *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 { let pm = self.get_package_manager()?; pm.install(package) } - + fn remove(&self, package: &str) -> Result { let pm = self.get_package_manager()?; pm.remove(package) } - + fn update(&self) -> Result { let pm = self.get_package_manager()?; pm.update() } - + fn upgrade(&self) -> Result { let pm = self.get_package_manager()?; pm.upgrade() } - + fn list_installed(&self) -> Result, PackageError> { let pm = self.get_package_manager()?; pm.list_installed() } - + fn search(&self, query: &str) -> Result, PackageError> { let pm = self.get_package_manager()?; pm.search(query) } - + fn is_installed(&self, package: &str) -> Result { 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"); } } -} \ No newline at end of file +} diff --git a/src/text/replace.rs b/src/text/replace.rs index be8b0ce..75fd485 100644 --- a/src/text/replace.rs +++ b/src/text/replace.rs @@ -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>(&self, path: P) -> io::Result { 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>(&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, P2: AsRef>( - &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 { // 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(()) } -} \ No newline at end of file +} diff --git a/src/virt/rfs/builder.rs b/src/virt/rfs/builder.rs index a4ec10c..78ce280 100644 --- a/src/virt/rfs/builder.rs +++ b/src/virt/rfs/builder.rs @@ -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, /// Mount ID + #[allow(dead_code)] mount_id: Option, /// 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(()) } -} \ No newline at end of file +} diff --git a/src/virt/rfs/cmd.rs b/src/virt/rfs/cmd.rs index bb9e366..6e69b40 100644 --- a/src/virt/rfs/cmd.rs +++ b/src/virt/rfs/cmd.rs @@ -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` - Command result or error pub fn execute_rfs_command(args: &[&str]) -> Result { 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) -} \ No newline at end of file +}