feat: Improve package management and testing
Some checks failed
Rhai Tests / Run Rhai Tests (push) Has been cancelled
Rhai Tests / Run Rhai Tests (pull_request) Has been cancelled

- 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:
Mahmoud Emad 2025-05-09 09:54:32 +03:00
parent f002445c9e
commit 22f87b320e
4 changed files with 300 additions and 232 deletions

View File

@ -1,5 +1,5 @@
use std::process::Command;
use crate::process::CommandResult; use crate::process::CommandResult;
use std::process::Command;
/// Error type for package management operations /// Error type for package management operations
#[derive(Debug)] #[derive(Debug)]
@ -45,7 +45,7 @@ impl Platform {
if std::path::Path::new("/usr/bin/sw_vers").exists() { if std::path::Path::new("/usr/bin/sw_vers").exists() {
return Platform::MacOS; return Platform::MacOS;
} }
// Check for Ubuntu // Check for Ubuntu
if std::path::Path::new("/etc/lsb-release").exists() { if std::path::Path::new("/etc/lsb-release").exists() {
// Read the file to confirm it's Ubuntu // Read the file to confirm it's Ubuntu
@ -55,12 +55,12 @@ impl Platform {
} }
} }
} }
Platform::Unknown Platform::Unknown
} }
} }
/// Thread-local storage for debug flag // Thread-local storage for debug flag
thread_local! { thread_local! {
static DEBUG: std::cell::RefCell<bool> = std::cell::RefCell::new(false); 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 /// Get the debug flag for the current thread
pub fn thread_local_debug() -> bool { pub fn thread_local_debug() -> bool {
DEBUG.with(|cell| { DEBUG.with(|cell| *cell.borrow())
*cell.borrow()
})
} }
/// Execute a package management command and return the result /// Execute a package management command and return the result
pub fn execute_package_command(args: &[&str], debug: bool) -> Result<CommandResult, PackageError> { pub fn execute_package_command(args: &[&str], debug: bool) -> Result<CommandResult, PackageError> {
// Save the current debug flag // Save the current debug flag
let previous_debug = thread_local_debug(); let previous_debug = thread_local_debug();
// Set the thread-local debug flag // Set the thread-local debug flag
set_thread_local_debug(debug); set_thread_local_debug(debug);
if debug { if debug {
println!("Executing command: {}", args.join(" ")); println!("Executing command: {}", args.join(" "));
} }
let output = Command::new(args[0]) let output = Command::new(args[0]).args(&args[1..]).output();
.args(&args[1..])
.output();
// Restore the previous debug flag // Restore the previous debug flag
set_thread_local_debug(previous_debug); set_thread_local_debug(previous_debug);
match output { match output {
Ok(output) => { Ok(output) => {
let stdout = String::from_utf8_lossy(&output.stdout).to_string(); let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string(); let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let result = CommandResult { let result = CommandResult {
stdout, stdout,
stderr, stderr,
success: output.status.success(), success: output.status.success(),
code: output.status.code().unwrap_or(-1), code: output.status.code().unwrap_or(-1),
}; };
// Always output stdout/stderr when debug is true // Always output stdout/stderr when debug is true
if debug { if debug {
if !result.stdout.is_empty() { if !result.stdout.is_empty() {
println!("Command stdout: {}", result.stdout); println!("Command stdout: {}", result.stdout);
} }
if !result.stderr.is_empty() { if !result.stderr.is_empty() {
println!("Command stderr: {}", result.stderr); println!("Command stderr: {}", result.stderr);
} }
if result.success { if result.success {
println!("Command succeeded with code {}", result.code); println!("Command succeeded with code {}", result.code);
} else { } else {
println!("Command failed with code {}", result.code); println!("Command failed with code {}", result.code);
} }
} }
if result.success { if result.success {
Ok(result) Ok(result)
} else { } else {
// If command failed and debug is false, output stderr // If command failed and debug is false, output stderr
if !debug { 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 {}: {}", Err(PackageError::CommandFailed(format!(
result.code, result.stderr.trim()))) "Command failed with code {}: {}",
result.code,
result.stderr.trim()
)))
} }
}, }
Err(e) => { Err(e) => {
// Always output error information // Always output error information
println!("Command execution failed: {}", e); println!("Command execution failed: {}", e);
@ -150,22 +153,22 @@ pub fn execute_package_command(args: &[&str], debug: bool) -> Result<CommandResu
pub trait PackageManager { pub trait PackageManager {
/// Install a package /// Install a package
fn install(&self, package: &str) -> Result<CommandResult, PackageError>; fn install(&self, package: &str) -> Result<CommandResult, PackageError>;
/// Remove a package /// Remove a package
fn remove(&self, package: &str) -> Result<CommandResult, PackageError>; fn remove(&self, package: &str) -> Result<CommandResult, PackageError>;
/// Update package lists /// Update package lists
fn update(&self) -> Result<CommandResult, PackageError>; fn update(&self) -> Result<CommandResult, PackageError>;
/// Upgrade installed packages /// Upgrade installed packages
fn upgrade(&self) -> Result<CommandResult, PackageError>; fn upgrade(&self) -> Result<CommandResult, PackageError>;
/// List installed packages /// List installed packages
fn list_installed(&self) -> Result<Vec<String>, PackageError>; fn list_installed(&self) -> Result<Vec<String>, PackageError>;
/// Search for packages /// Search for packages
fn search(&self, query: &str) -> Result<Vec<String>, PackageError>; fn search(&self, query: &str) -> Result<Vec<String>, PackageError>;
/// Check if a package is installed /// Check if a package is installed
fn is_installed(&self, package: &str) -> Result<bool, PackageError>; fn is_installed(&self, package: &str) -> Result<bool, PackageError>;
} }
@ -185,27 +188,31 @@ impl AptPackageManager {
impl PackageManager for AptPackageManager { impl PackageManager for AptPackageManager {
fn install(&self, package: &str) -> Result<CommandResult, PackageError> { fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
// Use -y to make it non-interactive and --quiet to reduce output // 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> { fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
// Use -y to make it non-interactive and --quiet to reduce output // Use -y to make it non-interactive and --quiet to reduce output
execute_package_command(&["apt-get", "remove", "-y", "--quiet", package], self.debug) execute_package_command(&["apt-get", "remove", "-y", "--quiet", package], self.debug)
} }
fn update(&self) -> Result<CommandResult, PackageError> { fn update(&self) -> Result<CommandResult, PackageError> {
// Use -y to make it non-interactive and --quiet to reduce output // Use -y to make it non-interactive and --quiet to reduce output
execute_package_command(&["apt-get", "update", "-y", "--quiet"], self.debug) execute_package_command(&["apt-get", "update", "-y", "--quiet"], self.debug)
} }
fn upgrade(&self) -> Result<CommandResult, PackageError> { fn upgrade(&self) -> Result<CommandResult, PackageError> {
// Use -y to make it non-interactive and --quiet to reduce output // Use -y to make it non-interactive and --quiet to reduce output
execute_package_command(&["apt-get", "upgrade", "-y", "--quiet"], self.debug) execute_package_command(&["apt-get", "upgrade", "-y", "--quiet"], self.debug)
} }
fn list_installed(&self) -> Result<Vec<String>, PackageError> { fn list_installed(&self) -> Result<Vec<String>, PackageError> {
let result = execute_package_command(&["dpkg", "--get-selections"], self.debug)?; let result = execute_package_command(&["dpkg", "--get-selections"], self.debug)?;
let packages = result.stdout let packages = result
.stdout
.lines() .lines()
.filter_map(|line| { .filter_map(|line| {
let parts: Vec<&str> = line.split_whitespace().collect(); let parts: Vec<&str> = line.split_whitespace().collect();
@ -218,10 +225,11 @@ impl PackageManager for AptPackageManager {
.collect(); .collect();
Ok(packages) Ok(packages)
} }
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> { fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
let result = execute_package_command(&["apt-cache", "search", query], self.debug)?; let result = execute_package_command(&["apt-cache", "search", query], self.debug)?;
let packages = result.stdout let packages = result
.stdout
.lines() .lines()
.map(|line| { .map(|line| {
let parts: Vec<&str> = line.split_whitespace().collect(); let parts: Vec<&str> = line.split_whitespace().collect();
@ -235,7 +243,7 @@ impl PackageManager for AptPackageManager {
.collect(); .collect();
Ok(packages) Ok(packages)
} }
fn is_installed(&self, package: &str) -> Result<bool, PackageError> { fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
let result = execute_package_command(&["dpkg", "-s", package], self.debug); let result = execute_package_command(&["dpkg", "-s", package], self.debug);
match result { match result {
@ -262,42 +270,44 @@ impl PackageManager for BrewPackageManager {
// Use --quiet to reduce output // Use --quiet to reduce output
execute_package_command(&["brew", "install", "--quiet", package], self.debug) execute_package_command(&["brew", "install", "--quiet", package], self.debug)
} }
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> { fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
// Use --quiet to reduce output // Use --quiet to reduce output
execute_package_command(&["brew", "uninstall", "--quiet", package], self.debug) execute_package_command(&["brew", "uninstall", "--quiet", package], self.debug)
} }
fn update(&self) -> Result<CommandResult, PackageError> { fn update(&self) -> Result<CommandResult, PackageError> {
// Use --quiet to reduce output // Use --quiet to reduce output
execute_package_command(&["brew", "update", "--quiet"], self.debug) execute_package_command(&["brew", "update", "--quiet"], self.debug)
} }
fn upgrade(&self) -> Result<CommandResult, PackageError> { fn upgrade(&self) -> Result<CommandResult, PackageError> {
// Use --quiet to reduce output // Use --quiet to reduce output
execute_package_command(&["brew", "upgrade", "--quiet"], self.debug) execute_package_command(&["brew", "upgrade", "--quiet"], self.debug)
} }
fn list_installed(&self) -> Result<Vec<String>, PackageError> { fn list_installed(&self) -> Result<Vec<String>, PackageError> {
let result = execute_package_command(&["brew", "list", "--formula"], self.debug)?; let result = execute_package_command(&["brew", "list", "--formula"], self.debug)?;
let packages = result.stdout let packages = result
.stdout
.lines() .lines()
.map(|line| line.trim().to_string()) .map(|line| line.trim().to_string())
.filter(|s| !s.is_empty()) .filter(|s| !s.is_empty())
.collect(); .collect();
Ok(packages) Ok(packages)
} }
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> { fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
let result = execute_package_command(&["brew", "search", query], self.debug)?; let result = execute_package_command(&["brew", "search", query], self.debug)?;
let packages = result.stdout let packages = result
.stdout
.lines() .lines()
.map(|line| line.trim().to_string()) .map(|line| line.trim().to_string())
.filter(|s| !s.is_empty()) .filter(|s| !s.is_empty())
.collect(); .collect();
Ok(packages) Ok(packages)
} }
fn is_installed(&self, package: &str) -> Result<bool, PackageError> { fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
let result = execute_package_command(&["brew", "list", package], self.debug); let result = execute_package_command(&["brew", "list", package], self.debug);
match result { match result {
@ -322,68 +332,70 @@ impl PackHero {
debug: false, debug: false,
} }
} }
/// Set the debug mode /// Set the debug mode
pub fn set_debug(&mut self, debug: bool) -> &mut Self { pub fn set_debug(&mut self, debug: bool) -> &mut Self {
self.debug = debug; self.debug = debug;
self self
} }
/// Get the debug mode /// Get the debug mode
pub fn debug(&self) -> bool { pub fn debug(&self) -> bool {
self.debug self.debug
} }
/// Get the detected platform /// Get the detected platform
pub fn platform(&self) -> Platform { pub fn platform(&self) -> Platform {
self.platform self.platform
} }
/// Get a package manager for the current platform /// Get a package manager for the current platform
fn get_package_manager(&self) -> Result<Box<dyn PackageManager>, PackageError> { fn get_package_manager(&self) -> Result<Box<dyn PackageManager>, PackageError> {
match self.platform { match self.platform {
Platform::Ubuntu => Ok(Box::new(AptPackageManager::new(self.debug))), Platform::Ubuntu => Ok(Box::new(AptPackageManager::new(self.debug))),
Platform::MacOS => Ok(Box::new(BrewPackageManager::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 /// Install a package
pub fn install(&self, package: &str) -> Result<CommandResult, PackageError> { pub fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?; let pm = self.get_package_manager()?;
pm.install(package) pm.install(package)
} }
/// Remove a package /// Remove a package
pub fn remove(&self, package: &str) -> Result<CommandResult, PackageError> { pub fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?; let pm = self.get_package_manager()?;
pm.remove(package) pm.remove(package)
} }
/// Update package lists /// Update package lists
pub fn update(&self) -> Result<CommandResult, PackageError> { pub fn update(&self) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?; let pm = self.get_package_manager()?;
pm.update() pm.update()
} }
/// Upgrade installed packages /// Upgrade installed packages
pub fn upgrade(&self) -> Result<CommandResult, PackageError> { pub fn upgrade(&self) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?; let pm = self.get_package_manager()?;
pm.upgrade() pm.upgrade()
} }
/// List installed packages /// List installed packages
pub fn list_installed(&self) -> Result<Vec<String>, PackageError> { pub fn list_installed(&self) -> Result<Vec<String>, PackageError> {
let pm = self.get_package_manager()?; let pm = self.get_package_manager()?;
pm.list_installed() pm.list_installed()
} }
/// Search for packages /// Search for packages
pub fn search(&self, query: &str) -> Result<Vec<String>, PackageError> { pub fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
let pm = self.get_package_manager()?; let pm = self.get_package_manager()?;
pm.search(query) pm.search(query)
} }
/// Check if a package is installed /// Check if a package is installed
pub fn is_installed(&self, package: &str) -> Result<bool, PackageError> { pub fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
let pm = self.get_package_manager()?; let pm = self.get_package_manager()?;
@ -394,47 +406,49 @@ impl PackHero {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
// Import the std::process::Command directly for some test-specific commands // Import the std::process::Command directly for some test-specific commands
use std::process::Command as StdCommand;
use super::*; use super::*;
use std::process::Command as StdCommand;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
#[test] #[test]
fn test_platform_detection() { fn test_platform_detection() {
// This test will return different results depending on the platform it's run on // This test will return different results depending on the platform it's run on
let platform = Platform::detect(); let platform = Platform::detect();
println!("Detected platform: {:?}", platform); println!("Detected platform: {:?}", platform);
// Just ensure it doesn't panic // Just ensure it doesn't panic
assert!(true); assert!(true);
} }
#[test] #[test]
fn test_debug_flag() { fn test_debug_flag() {
// Test setting and getting the debug flag // Test setting and getting the debug flag
set_thread_local_debug(true); set_thread_local_debug(true);
assert_eq!(thread_local_debug(), true); assert_eq!(thread_local_debug(), true);
set_thread_local_debug(false); set_thread_local_debug(false);
assert_eq!(thread_local_debug(), false); assert_eq!(thread_local_debug(), false);
} }
#[test] #[test]
fn test_package_error_display() { fn test_package_error_display() {
// Test the Display implementation for PackageError // Test the Display implementation for PackageError
let err1 = PackageError::CommandFailed("command failed".to_string()); let err1 = PackageError::CommandFailed("command failed".to_string());
assert_eq!(err1.to_string(), "Command failed: command failed"); assert_eq!(err1.to_string(), "Command failed: command failed");
let err2 = PackageError::UnsupportedPlatform("test platform".to_string()); let err2 = PackageError::UnsupportedPlatform("test platform".to_string());
assert_eq!(err2.to_string(), "Unsupported platform: test platform"); assert_eq!(err2.to_string(), "Unsupported platform: test platform");
let err3 = PackageError::Other("other error".to_string()); let err3 = PackageError::Other("other error".to_string());
assert_eq!(err3.to_string(), "Error: other error"); assert_eq!(err3.to_string(), "Error: other error");
// We can't easily test CommandExecutionFailed because std::io::Error doesn't implement PartialEq // We can't easily test CommandExecutionFailed because std::io::Error doesn't implement PartialEq
} }
// Mock package manager for testing // Mock package manager for testing
struct MockPackageManager { struct MockPackageManager {
// debug field is kept for consistency with real package managers
#[allow(dead_code)]
debug: bool, debug: bool,
install_called: Arc<Mutex<bool>>, install_called: Arc<Mutex<bool>>,
remove_called: Arc<Mutex<bool>>, remove_called: Arc<Mutex<bool>>,
@ -446,7 +460,7 @@ mod tests {
// Control what the mock returns // Control what the mock returns
should_succeed: bool, should_succeed: bool,
} }
impl MockPackageManager { impl MockPackageManager {
fn new(debug: bool, should_succeed: bool) -> Self { fn new(debug: bool, should_succeed: bool) -> Self {
Self { Self {
@ -462,7 +476,7 @@ mod tests {
} }
} }
} }
impl PackageManager for MockPackageManager { impl PackageManager for MockPackageManager {
fn install(&self, package: &str) -> Result<CommandResult, PackageError> { fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
*self.install_called.lock().unwrap() = true; *self.install_called.lock().unwrap() = true;
@ -474,10 +488,12 @@ mod tests {
code: 0, code: 0,
}) })
} else { } 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> { fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
*self.remove_called.lock().unwrap() = true; *self.remove_called.lock().unwrap() = true;
if self.should_succeed { if self.should_succeed {
@ -488,10 +504,12 @@ mod tests {
code: 0, code: 0,
}) })
} else { } else {
Err(PackageError::CommandFailed("Mock remove failed".to_string())) Err(PackageError::CommandFailed(
"Mock remove failed".to_string(),
))
} }
} }
fn update(&self) -> Result<CommandResult, PackageError> { fn update(&self) -> Result<CommandResult, PackageError> {
*self.update_called.lock().unwrap() = true; *self.update_called.lock().unwrap() = true;
if self.should_succeed { if self.should_succeed {
@ -502,10 +520,12 @@ mod tests {
code: 0, code: 0,
}) })
} else { } else {
Err(PackageError::CommandFailed("Mock update failed".to_string())) Err(PackageError::CommandFailed(
"Mock update failed".to_string(),
))
} }
} }
fn upgrade(&self) -> Result<CommandResult, PackageError> { fn upgrade(&self) -> Result<CommandResult, PackageError> {
*self.upgrade_called.lock().unwrap() = true; *self.upgrade_called.lock().unwrap() = true;
if self.should_succeed { if self.should_succeed {
@ -516,45 +536,57 @@ mod tests {
code: 0, code: 0,
}) })
} else { } 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> { fn list_installed(&self) -> Result<Vec<String>, PackageError> {
*self.list_installed_called.lock().unwrap() = true; *self.list_installed_called.lock().unwrap() = true;
if self.should_succeed { if self.should_succeed {
Ok(vec!["package1".to_string(), "package2".to_string()]) Ok(vec!["package1".to_string(), "package2".to_string()])
} else { } 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> { fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
*self.search_called.lock().unwrap() = true; *self.search_called.lock().unwrap() = true;
if self.should_succeed { if self.should_succeed {
Ok(vec![format!("result1-{}", query), format!("result2-{}", query)]) Ok(vec![
format!("result1-{}", query),
format!("result2-{}", query),
])
} else { } 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> { fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
*self.is_installed_called.lock().unwrap() = true; *self.is_installed_called.lock().unwrap() = true;
if self.should_succeed { if self.should_succeed {
Ok(package == "installed-package") Ok(package == "installed-package")
} else { } 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 // Custom PackHero for testing with a mock package manager
struct TestPackHero { struct TestPackHero {
platform: Platform, platform: Platform,
#[allow(dead_code)]
debug: bool, debug: bool,
mock_manager: MockPackageManager, mock_manager: MockPackageManager,
} }
impl TestPackHero { impl TestPackHero {
fn new(platform: Platform, debug: bool, should_succeed: bool) -> Self { fn new(platform: Platform, debug: bool, should_succeed: bool) -> Self {
Self { Self {
@ -563,144 +595,152 @@ mod tests {
mock_manager: MockPackageManager::new(debug, should_succeed), mock_manager: MockPackageManager::new(debug, should_succeed),
} }
} }
fn get_package_manager(&self) -> Result<&dyn PackageManager, PackageError> { fn get_package_manager(&self) -> Result<&dyn PackageManager, PackageError> {
match self.platform { match self.platform {
Platform::Ubuntu | Platform::MacOS => Ok(&self.mock_manager), 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> { fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?; let pm = self.get_package_manager()?;
pm.install(package) pm.install(package)
} }
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> { fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?; let pm = self.get_package_manager()?;
pm.remove(package) pm.remove(package)
} }
fn update(&self) -> Result<CommandResult, PackageError> { fn update(&self) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?; let pm = self.get_package_manager()?;
pm.update() pm.update()
} }
fn upgrade(&self) -> Result<CommandResult, PackageError> { fn upgrade(&self) -> Result<CommandResult, PackageError> {
let pm = self.get_package_manager()?; let pm = self.get_package_manager()?;
pm.upgrade() pm.upgrade()
} }
fn list_installed(&self) -> Result<Vec<String>, PackageError> { fn list_installed(&self) -> Result<Vec<String>, PackageError> {
let pm = self.get_package_manager()?; let pm = self.get_package_manager()?;
pm.list_installed() pm.list_installed()
} }
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> { fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
let pm = self.get_package_manager()?; let pm = self.get_package_manager()?;
pm.search(query) pm.search(query)
} }
fn is_installed(&self, package: &str) -> Result<bool, PackageError> { fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
let pm = self.get_package_manager()?; let pm = self.get_package_manager()?;
pm.is_installed(package) pm.is_installed(package)
} }
} }
#[test] #[test]
fn test_packhero_with_mock_success() { fn test_packhero_with_mock_success() {
// Test PackHero with a mock package manager that succeeds // Test PackHero with a mock package manager that succeeds
let hero = TestPackHero::new(Platform::Ubuntu, false, true); let hero = TestPackHero::new(Platform::Ubuntu, false, true);
// Test install // Test install
let result = hero.install("test-package"); let result = hero.install("test-package");
assert!(result.is_ok()); assert!(result.is_ok());
assert!(*hero.mock_manager.install_called.lock().unwrap()); assert!(*hero.mock_manager.install_called.lock().unwrap());
// Test remove // Test remove
let result = hero.remove("test-package"); let result = hero.remove("test-package");
assert!(result.is_ok()); assert!(result.is_ok());
assert!(*hero.mock_manager.remove_called.lock().unwrap()); assert!(*hero.mock_manager.remove_called.lock().unwrap());
// Test update // Test update
let result = hero.update(); let result = hero.update();
assert!(result.is_ok()); assert!(result.is_ok());
assert!(*hero.mock_manager.update_called.lock().unwrap()); assert!(*hero.mock_manager.update_called.lock().unwrap());
// Test upgrade // Test upgrade
let result = hero.upgrade(); let result = hero.upgrade();
assert!(result.is_ok()); assert!(result.is_ok());
assert!(*hero.mock_manager.upgrade_called.lock().unwrap()); assert!(*hero.mock_manager.upgrade_called.lock().unwrap());
// Test list_installed // Test list_installed
let result = hero.list_installed(); let result = hero.list_installed();
assert!(result.is_ok()); 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()); assert!(*hero.mock_manager.list_installed_called.lock().unwrap());
// Test search // Test search
let result = hero.search("query"); let result = hero.search("query");
assert!(result.is_ok()); 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()); assert!(*hero.mock_manager.search_called.lock().unwrap());
// Test is_installed // Test is_installed
let result = hero.is_installed("installed-package"); let result = hero.is_installed("installed-package");
assert!(result.is_ok()); assert!(result.is_ok());
assert!(result.unwrap()); assert!(result.unwrap());
assert!(*hero.mock_manager.is_installed_called.lock().unwrap()); assert!(*hero.mock_manager.is_installed_called.lock().unwrap());
let result = hero.is_installed("not-installed-package"); let result = hero.is_installed("not-installed-package");
assert!(result.is_ok()); assert!(result.is_ok());
assert!(!result.unwrap()); assert!(!result.unwrap());
} }
#[test] #[test]
fn test_packhero_with_mock_failure() { fn test_packhero_with_mock_failure() {
// Test PackHero with a mock package manager that fails // Test PackHero with a mock package manager that fails
let hero = TestPackHero::new(Platform::Ubuntu, false, false); let hero = TestPackHero::new(Platform::Ubuntu, false, false);
// Test install // Test install
let result = hero.install("test-package"); let result = hero.install("test-package");
assert!(result.is_err()); assert!(result.is_err());
assert!(*hero.mock_manager.install_called.lock().unwrap()); assert!(*hero.mock_manager.install_called.lock().unwrap());
// Test remove // Test remove
let result = hero.remove("test-package"); let result = hero.remove("test-package");
assert!(result.is_err()); assert!(result.is_err());
assert!(*hero.mock_manager.remove_called.lock().unwrap()); assert!(*hero.mock_manager.remove_called.lock().unwrap());
// Test update // Test update
let result = hero.update(); let result = hero.update();
assert!(result.is_err()); assert!(result.is_err());
assert!(*hero.mock_manager.update_called.lock().unwrap()); assert!(*hero.mock_manager.update_called.lock().unwrap());
// Test upgrade // Test upgrade
let result = hero.upgrade(); let result = hero.upgrade();
assert!(result.is_err()); assert!(result.is_err());
assert!(*hero.mock_manager.upgrade_called.lock().unwrap()); assert!(*hero.mock_manager.upgrade_called.lock().unwrap());
// Test list_installed // Test list_installed
let result = hero.list_installed(); let result = hero.list_installed();
assert!(result.is_err()); assert!(result.is_err());
assert!(*hero.mock_manager.list_installed_called.lock().unwrap()); assert!(*hero.mock_manager.list_installed_called.lock().unwrap());
// Test search // Test search
let result = hero.search("query"); let result = hero.search("query");
assert!(result.is_err()); assert!(result.is_err());
assert!(*hero.mock_manager.search_called.lock().unwrap()); assert!(*hero.mock_manager.search_called.lock().unwrap());
// Test is_installed // Test is_installed
let result = hero.is_installed("installed-package"); let result = hero.is_installed("installed-package");
assert!(result.is_err()); assert!(result.is_err());
assert!(*hero.mock_manager.is_installed_called.lock().unwrap()); assert!(*hero.mock_manager.is_installed_called.lock().unwrap());
} }
#[test] #[test]
fn test_packhero_unsupported_platform() { fn test_packhero_unsupported_platform() {
// Test PackHero with an unsupported platform // Test PackHero with an unsupported platform
let hero = TestPackHero::new(Platform::Unknown, false, true); let hero = TestPackHero::new(Platform::Unknown, false, true);
// All operations should fail with UnsupportedPlatform error // All operations should fail with UnsupportedPlatform error
let result = hero.install("test-package"); let result = hero.install("test-package");
assert!(result.is_err()); assert!(result.is_err());
@ -708,14 +748,14 @@ mod tests {
Err(PackageError::UnsupportedPlatform(_)) => (), Err(PackageError::UnsupportedPlatform(_)) => (),
_ => panic!("Expected UnsupportedPlatform error"), _ => panic!("Expected UnsupportedPlatform error"),
} }
let result = hero.remove("test-package"); let result = hero.remove("test-package");
assert!(result.is_err()); assert!(result.is_err());
match result { match result {
Err(PackageError::UnsupportedPlatform(_)) => (), Err(PackageError::UnsupportedPlatform(_)) => (),
_ => panic!("Expected UnsupportedPlatform error"), _ => panic!("Expected UnsupportedPlatform error"),
} }
let result = hero.update(); let result = hero.update();
assert!(result.is_err()); assert!(result.is_err());
match result { match result {
@ -723,7 +763,7 @@ mod tests {
_ => panic!("Expected UnsupportedPlatform error"), _ => panic!("Expected UnsupportedPlatform error"),
} }
} }
// Real-world tests that actually install and remove packages on Ubuntu // 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 // These tests will only run on Ubuntu and will be skipped on other platforms
#[test] #[test]
@ -731,19 +771,22 @@ mod tests {
// Check if we're on Ubuntu // Check if we're on Ubuntu
let platform = Platform::detect(); let platform = Platform::detect();
if platform != Platform::Ubuntu { 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; return;
} }
println!("Running real package operations test on Ubuntu"); println!("Running real package operations test on Ubuntu");
// Create a PackHero instance with debug enabled // Create a PackHero instance with debug enabled
let mut hero = PackHero::new(); let mut hero = PackHero::new();
hero.set_debug(true); hero.set_debug(true);
// Test package to install/remove // Test package to install/remove
let test_package = "wget"; let test_package = "wget";
// First, check if the package is already installed // First, check if the package is already installed
let is_installed_before = match hero.is_installed(test_package) { let is_installed_before = match hero.is_installed(test_package) {
Ok(result) => result, Ok(result) => result,
@ -752,9 +795,12 @@ mod tests {
return; 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 the package is already installed, we'll remove it first
if is_installed_before { if is_installed_before {
println!("Removing existing package {} before test", test_package); println!("Removing existing package {} before test", test_package);
@ -765,7 +811,7 @@ mod tests {
return; return;
} }
} }
// Verify it was removed // Verify it was removed
match hero.is_installed(test_package) { match hero.is_installed(test_package) {
Ok(is_installed) => { Ok(is_installed) => {
@ -775,14 +821,17 @@ mod tests {
} else { } else {
println!("Verified package {} was removed", test_package); println!("Verified package {} was removed", test_package);
} }
}, }
Err(e) => { Err(e) => {
println!("Error checking if package is installed after removal: {}", e); println!(
"Error checking if package is installed after removal: {}",
e
);
return; return;
} }
} }
} }
// Now install the package // Now install the package
println!("Installing package {}", test_package); println!("Installing package {}", test_package);
match hero.install(test_package) { match hero.install(test_package) {
@ -792,7 +841,7 @@ mod tests {
return; return;
} }
} }
// Verify it was installed // Verify it was installed
match hero.is_installed(test_package) { match hero.is_installed(test_package) {
Ok(is_installed) => { Ok(is_installed) => {
@ -802,41 +851,50 @@ mod tests {
} else { } else {
println!("Verified package {} was installed", test_package); println!("Verified package {} was installed", test_package);
} }
}, }
Err(e) => { Err(e) => {
println!("Error checking if package is installed after installation: {}", e); println!(
"Error checking if package is installed after installation: {}",
e
);
return; return;
} }
} }
// Test the search functionality // Test the search functionality
println!("Searching for packages with 'wget'"); println!("Searching for packages with 'wget'");
match hero.search("wget") { match hero.search("wget") {
Ok(results) => { Ok(results) => {
println!("Search results: {:?}", 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) => { Err(e) => {
println!("Error searching for packages: {}", e); println!("Error searching for packages: {}", e);
return; return;
} }
} }
// Test listing installed packages // Test listing installed packages
println!("Listing installed packages"); println!("Listing installed packages");
match hero.list_installed() { match hero.list_installed() {
Ok(packages) => { Ok(packages) => {
println!("Found {} installed packages", packages.len()); println!("Found {} installed packages", packages.len());
// Check if our test package is in the list // Check if our test package is in the list
assert!(packages.iter().any(|p| p == test_package), assert!(
"Installed packages list should contain {}", test_package); packages.iter().any(|p| p == test_package),
}, "Installed packages list should contain {}",
test_package
);
}
Err(e) => { Err(e) => {
println!("Error listing installed packages: {}", e); println!("Error listing installed packages: {}", e);
return; return;
} }
} }
// Now remove the package if it wasn't installed before // Now remove the package if it wasn't installed before
if !is_installed_before { if !is_installed_before {
println!("Removing package {} after test", test_package); println!("Removing package {} after test", test_package);
@ -847,7 +905,7 @@ mod tests {
return; return;
} }
} }
// Verify it was removed // Verify it was removed
match hero.is_installed(test_package) { match hero.is_installed(test_package) {
Ok(is_installed) => { Ok(is_installed) => {
@ -857,14 +915,17 @@ mod tests {
} else { } else {
println!("Verified package {} was removed", test_package); println!("Verified package {} was removed", test_package);
} }
}, }
Err(e) => { Err(e) => {
println!("Error checking if package is installed after removal: {}", e); println!(
"Error checking if package is installed after removal: {}",
e
);
return; return;
} }
} }
} }
// Test update functionality // Test update functionality
println!("Testing package list update"); println!("Testing package list update");
match hero.update() { match hero.update() {
@ -874,10 +935,10 @@ mod tests {
return; return;
} }
} }
println!("All real package operations tests passed on Ubuntu"); println!("All real package operations tests passed on Ubuntu");
} }
// Test to check if apt-get is available on the system // Test to check if apt-get is available on the system
#[test] #[test]
fn test_apt_get_availability() { fn test_apt_get_availability() {
@ -886,18 +947,18 @@ mod tests {
.arg("apt-get") .arg("apt-get")
.output() .output()
.expect("Failed to execute which apt-get"); .expect("Failed to execute which apt-get");
let success = output.status.success(); let success = output.status.success();
let stdout = String::from_utf8_lossy(&output.stdout).to_string(); let stdout = String::from_utf8_lossy(&output.stdout).to_string();
println!("apt-get available: {}", success); println!("apt-get available: {}", success);
if success { if success {
println!("apt-get path: {}", stdout.trim()); println!("apt-get path: {}", stdout.trim());
} }
// On Ubuntu, this should pass // On Ubuntu, this should pass
if Platform::detect() == Platform::Ubuntu { if Platform::detect() == Platform::Ubuntu {
assert!(success, "apt-get should be available on Ubuntu"); assert!(success, "apt-get should be available on Ubuntu");
} }
} }
} }

View File

@ -1,7 +1,6 @@
use regex::Regex; use regex::Regex;
use std::fs; use std::fs;
use std::io::{self, Read, Seek, SeekFrom}; use std::io::{self, Read};
use std::path::Path; use std::path::Path;
/// Represents the type of replacement to perform. /// Represents the type of replacement to perform.
@ -46,36 +45,36 @@ impl TextReplacer {
/// Applies all configured replacement operations to the input text /// Applies all configured replacement operations to the input text
pub fn replace(&self, input: &str) -> String { pub fn replace(&self, input: &str) -> String {
let mut result = input.to_string(); let mut result = input.to_string();
// Apply each replacement operation in sequence // Apply each replacement operation in sequence
for op in &self.operations { for op in &self.operations {
result = op.apply(&result); result = op.apply(&result);
} }
result result
} }
/// Reads a file, applies all replacements, and returns the result as a string /// 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> { pub fn replace_file<P: AsRef<Path>>(&self, path: P) -> io::Result<String> {
let mut file = fs::File::open(path)?; let mut file = fs::File::open(path)?;
let mut content = String::new(); let mut content = String::new();
file.read_to_string(&mut content)?; file.read_to_string(&mut content)?;
Ok(self.replace(&content)) Ok(self.replace(&content))
} }
/// Reads a file, applies all replacements, and writes the result back to the file /// 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<()> { pub fn replace_file_in_place<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
let content = self.replace_file(&path)?; let content = self.replace_file(&path)?;
fs::write(path, content)?; fs::write(path, content)?;
Ok(()) Ok(())
} }
/// Reads a file, applies all replacements, and writes the result to a new file /// 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>>( pub fn replace_file_to<P1: AsRef<Path>, P2: AsRef<Path>>(
&self, &self,
input_path: P1, input_path: P1,
output_path: P2 output_path: P2,
) -> io::Result<()> { ) -> io::Result<()> {
let content = self.replace_file(&input_path)?; let content = self.replace_file(&input_path)?;
fs::write(output_path, content)?; fs::write(output_path, content)?;
@ -111,13 +110,13 @@ impl TextReplacerBuilder {
self.use_regex = yes; self.use_regex = yes;
self self
} }
/// Sets whether the replacement should be case-insensitive /// Sets whether the replacement should be case-insensitive
pub fn case_insensitive(mut self, yes: bool) -> Self { pub fn case_insensitive(mut self, yes: bool) -> Self {
self.case_insensitive = yes; self.case_insensitive = yes;
self self
} }
/// Adds another replacement operation to the chain and resets the builder for a new operation /// Adds another replacement operation to the chain and resets the builder for a new operation
pub fn and(mut self) -> Self { pub fn and(mut self) -> Self {
self.add_current_operation(); self.add_current_operation();
@ -130,20 +129,20 @@ impl TextReplacerBuilder {
let replacement = self.replacement.take().unwrap_or_default(); let replacement = self.replacement.take().unwrap_or_default();
let use_regex = self.use_regex; let use_regex = self.use_regex;
let case_insensitive = self.case_insensitive; let case_insensitive = self.case_insensitive;
// Reset current settings // Reset current settings
self.use_regex = false; self.use_regex = false;
self.case_insensitive = false; self.case_insensitive = false;
// Create the replacement mode // Create the replacement mode
let mode = if use_regex { let mode = if use_regex {
let mut regex_pattern = pattern; let mut regex_pattern = pattern;
// If case insensitive, add the flag to the regex pattern // If case insensitive, add the flag to the regex pattern
if case_insensitive && !regex_pattern.starts_with("(?i)") { if case_insensitive && !regex_pattern.starts_with("(?i)") {
regex_pattern = format!("(?i){}", regex_pattern); regex_pattern = format!("(?i){}", regex_pattern);
} }
match Regex::new(&regex_pattern) { match Regex::new(&regex_pattern) {
Ok(re) => ReplaceMode::Regex(re), Ok(re) => ReplaceMode::Regex(re),
Err(_) => return false, // Failed to compile regex Err(_) => return false, // Failed to compile regex
@ -156,12 +155,10 @@ impl TextReplacerBuilder {
} }
ReplaceMode::Literal(pattern) ReplaceMode::Literal(pattern)
}; };
self.operations.push(ReplacementOperation { self.operations
mode, .push(ReplacementOperation { mode, replacement });
replacement,
});
true true
} else { } else {
false false
@ -172,12 +169,12 @@ impl TextReplacerBuilder {
pub fn build(mut self) -> Result<TextReplacer, String> { pub fn build(mut self) -> Result<TextReplacer, String> {
// If there's a pending replacement operation, add it // If there's a pending replacement operation, add it
self.add_current_operation(); self.add_current_operation();
// Ensure we have at least one replacement operation // Ensure we have at least one replacement operation
if self.operations.is_empty() { if self.operations.is_empty() {
return Err("No replacement operations configured".to_string()); return Err("No replacement operations configured".to_string());
} }
Ok(TextReplacer { Ok(TextReplacer {
operations: self.operations, operations: self.operations,
}) })
@ -187,7 +184,7 @@ impl TextReplacerBuilder {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::io::Write; use std::io::{Seek, SeekFrom, Write};
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
#[test] #[test]
@ -219,7 +216,7 @@ mod tests {
assert_eq!(output, "qux bar qux baz"); assert_eq!(output, "qux bar qux baz");
} }
#[test] #[test]
fn test_multiple_replacements() { fn test_multiple_replacements() {
let replacer = TextReplacer::builder() let replacer = TextReplacer::builder()
@ -236,7 +233,7 @@ mod tests {
assert_eq!(output, "qux baz qux"); assert_eq!(output, "qux baz qux");
} }
#[test] #[test]
fn test_case_insensitive_regex() { fn test_case_insensitive_regex() {
let replacer = TextReplacer::builder() let replacer = TextReplacer::builder()
@ -252,44 +249,44 @@ mod tests {
assert_eq!(output, "bar bar bar"); assert_eq!(output, "bar bar bar");
} }
#[test] #[test]
fn test_file_operations() -> io::Result<()> { fn test_file_operations() -> io::Result<()> {
// Create a temporary file // Create a temporary file
let mut temp_file = NamedTempFile::new()?; let mut temp_file = NamedTempFile::new()?;
writeln!(temp_file, "foo bar foo baz")?; writeln!(temp_file, "foo bar foo baz")?;
// Flush the file to ensure content is written // Flush the file to ensure content is written
temp_file.as_file_mut().flush()?; temp_file.as_file_mut().flush()?;
let replacer = TextReplacer::builder() let replacer = TextReplacer::builder()
.pattern("foo") .pattern("foo")
.replacement("qux") .replacement("qux")
.build() .build()
.unwrap(); .unwrap();
// Test replace_file // Test replace_file
let result = replacer.replace_file(temp_file.path())?; let result = replacer.replace_file(temp_file.path())?;
assert_eq!(result, "qux bar qux baz\n"); assert_eq!(result, "qux bar qux baz\n");
// Test replace_file_in_place // Test replace_file_in_place
replacer.replace_file_in_place(temp_file.path())?; replacer.replace_file_in_place(temp_file.path())?;
// Verify the file was updated - need to seek to beginning of file first // Verify the file was updated - need to seek to beginning of file first
let mut content = String::new(); let mut content = String::new();
temp_file.as_file_mut().seek(SeekFrom::Start(0))?; temp_file.as_file_mut().seek(SeekFrom::Start(0))?;
temp_file.as_file_mut().read_to_string(&mut content)?; temp_file.as_file_mut().read_to_string(&mut content)?;
assert_eq!(content, "qux bar qux baz\n"); assert_eq!(content, "qux bar qux baz\n");
// Test replace_file_to with a new temporary file // Test replace_file_to with a new temporary file
let output_file = NamedTempFile::new()?; let output_file = NamedTempFile::new()?;
replacer.replace_file_to(temp_file.path(), output_file.path())?; replacer.replace_file_to(temp_file.path(), output_file.path())?;
// Verify the output file has the replaced content // Verify the output file has the replaced content
let mut output_content = String::new(); let mut output_content = String::new();
fs::File::open(output_file.path())?.read_to_string(&mut output_content)?; fs::File::open(output_file.path())?.read_to_string(&mut output_content)?;
assert_eq!(output_content, "qux bar qux baz\n"); assert_eq!(output_content, "qux bar qux baz\n");
Ok(()) Ok(())
} }
} }

View File

@ -1,9 +1,9 @@
use std::collections::HashMap;
use super::{ use super::{
error::RfsError,
cmd::execute_rfs_command, cmd::execute_rfs_command,
error::RfsError,
types::{Mount, MountType, StoreSpec}, types::{Mount, MountType, StoreSpec},
}; };
use std::collections::HashMap;
/// Builder for RFS mount operations /// Builder for RFS mount operations
#[derive(Clone)] #[derive(Clone)]
@ -17,6 +17,7 @@ pub struct RfsBuilder {
/// Mount options /// Mount options
options: HashMap<String, String>, options: HashMap<String, String>,
/// Mount ID /// Mount ID
#[allow(dead_code)]
mount_id: Option<String>, mount_id: Option<String>,
/// Debug mode /// Debug mode
debug: bool, debug: bool,
@ -44,7 +45,7 @@ impl RfsBuilder {
debug: false, debug: false,
} }
} }
/// Add a mount option /// Add a mount option
/// ///
/// # Arguments /// # Arguments
@ -59,7 +60,7 @@ impl RfsBuilder {
self.options.insert(key.to_string(), value.to_string()); self.options.insert(key.to_string(), value.to_string());
self self
} }
/// Add multiple mount options /// Add multiple mount options
/// ///
/// # Arguments /// # Arguments
@ -75,7 +76,7 @@ impl RfsBuilder {
} }
self self
} }
/// Set debug mode /// Set debug mode
/// ///
/// # Arguments /// # Arguments
@ -89,7 +90,7 @@ impl RfsBuilder {
self.debug = debug; self.debug = debug;
self self
} }
/// Mount the filesystem /// Mount the filesystem
/// ///
/// # Returns /// # Returns
@ -99,7 +100,7 @@ impl RfsBuilder {
// Build the command string // Build the command string
let mut cmd = String::from("mount -t "); let mut cmd = String::from("mount -t ");
cmd.push_str(&self.mount_type.to_string()); cmd.push_str(&self.mount_type.to_string());
// Add options if any // Add options if any
if !self.options.is_empty() { if !self.options.is_empty() {
cmd.push_str(" -o "); cmd.push_str(" -o ");
@ -114,35 +115,39 @@ impl RfsBuilder {
first = false; first = false;
} }
} }
// Add source and target // Add source and target
cmd.push_str(" "); cmd.push_str(" ");
cmd.push_str(&self.source); cmd.push_str(&self.source);
cmd.push_str(" "); cmd.push_str(" ");
cmd.push_str(&self.target); cmd.push_str(&self.target);
// Split the command into arguments // Split the command into arguments
let args: Vec<&str> = cmd.split_whitespace().collect(); let args: Vec<&str> = cmd.split_whitespace().collect();
// Execute the command // Execute the command
let result = execute_rfs_command(&args)?; let result = execute_rfs_command(&args)?;
// Parse the output to get the mount ID // Parse the output to get the mount ID
let mount_id = result.stdout.trim().to_string(); let mount_id = result.stdout.trim().to_string();
if mount_id.is_empty() { if mount_id.is_empty() {
return Err(RfsError::MountFailed("Failed to get mount ID".to_string())); return Err(RfsError::MountFailed("Failed to get mount ID".to_string()));
} }
// Create and return the Mount struct // Create and return the Mount struct
Ok(Mount { Ok(Mount {
id: mount_id, id: mount_id,
source: self.source, source: self.source,
target: self.target, target: self.target,
fs_type: self.mount_type.to_string(), 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 /// Unmount the filesystem
/// ///
/// # Returns /// # Returns
@ -151,12 +156,15 @@ impl RfsBuilder {
pub fn unmount(&self) -> Result<(), RfsError> { pub fn unmount(&self) -> Result<(), RfsError> {
// Execute the unmount command // Execute the unmount command
let result = execute_rfs_command(&["unmount", &self.target])?; let result = execute_rfs_command(&["unmount", &self.target])?;
// Check for errors // Check for errors
if !result.success { 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(()) Ok(())
} }
} }
@ -193,7 +201,7 @@ impl PackBuilder {
debug: false, debug: false,
} }
} }
/// Add a store specification /// Add a store specification
/// ///
/// # Arguments /// # Arguments
@ -207,7 +215,7 @@ impl PackBuilder {
self.store_specs.push(store_spec); self.store_specs.push(store_spec);
self self
} }
/// Add multiple store specifications /// Add multiple store specifications
/// ///
/// # Arguments /// # Arguments
@ -221,7 +229,7 @@ impl PackBuilder {
self.store_specs.extend(store_specs); self.store_specs.extend(store_specs);
self self
} }
/// Set debug mode /// Set debug mode
/// ///
/// # Arguments /// # Arguments
@ -235,7 +243,7 @@ impl PackBuilder {
self.debug = debug; self.debug = debug;
self self
} }
/// Pack the directory /// Pack the directory
/// ///
/// # Returns /// # Returns
@ -245,7 +253,7 @@ impl PackBuilder {
// Build the command string // Build the command string
let mut cmd = String::from("pack -m "); let mut cmd = String::from("pack -m ");
cmd.push_str(&self.output); cmd.push_str(&self.output);
// Add store specs if any // Add store specs if any
if !self.store_specs.is_empty() { if !self.store_specs.is_empty() {
cmd.push_str(" -s "); cmd.push_str(" -s ");
@ -259,22 +267,25 @@ impl PackBuilder {
first = false; first = false;
} }
} }
// Add directory // Add directory
cmd.push_str(" "); cmd.push_str(" ");
cmd.push_str(&self.directory); cmd.push_str(&self.directory);
// Split the command into arguments // Split the command into arguments
let args: Vec<&str> = cmd.split_whitespace().collect(); let args: Vec<&str> = cmd.split_whitespace().collect();
// Execute the command // Execute the command
let result = execute_rfs_command(&args)?; let result = execute_rfs_command(&args)?;
// Check for errors // Check for errors
if !result.success { 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(()) Ok(())
} }
} }

View File

@ -1,7 +1,7 @@
use crate::process::{run_command, CommandResult};
use super::error::RfsError; use super::error::RfsError;
use std::thread_local; use crate::process::{run_command, CommandResult};
use std::cell::RefCell; use std::cell::RefCell;
use std::thread_local;
// Thread-local storage for debug flag // Thread-local storage for debug flag
thread_local! { thread_local! {
@ -9,6 +9,7 @@ thread_local! {
} }
/// Set the thread-local debug flag /// Set the thread-local debug flag
#[allow(dead_code)]
pub fn set_thread_local_debug(debug: bool) { pub fn set_thread_local_debug(debug: bool) {
DEBUG.with(|d| { DEBUG.with(|d| {
*d.borrow_mut() = debug; *d.borrow_mut() = debug;
@ -17,9 +18,7 @@ pub fn set_thread_local_debug(debug: bool) {
/// Get the current thread-local debug flag /// Get the current thread-local debug flag
pub fn thread_local_debug() -> bool { pub fn thread_local_debug() -> bool {
DEBUG.with(|d| { DEBUG.with(|d| *d.borrow())
*d.borrow()
})
} }
/// Execute an RFS command with the given arguments /// 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 /// * `Result<CommandResult, RfsError>` - Command result or error
pub fn execute_rfs_command(args: &[&str]) -> Result<CommandResult, RfsError> { pub fn execute_rfs_command(args: &[&str]) -> Result<CommandResult, RfsError> {
let debug = thread_local_debug(); let debug = thread_local_debug();
// Construct the command string // Construct the command string
let mut cmd = String::from("rfs"); let mut cmd = String::from("rfs");
for arg in args { for arg in args {
cmd.push(' '); cmd.push(' ');
cmd.push_str(arg); cmd.push_str(arg);
} }
if debug { if debug {
println!("Executing RFS command: {}", cmd); println!("Executing RFS command: {}", cmd);
} }
// Execute the command // Execute the command
let result = run_command(&cmd) let result = run_command(&cmd)
.map_err(|e| RfsError::CommandFailed(format!("Failed to execute RFS command: {}", e)))?; .map_err(|e| RfsError::CommandFailed(format!("Failed to execute RFS command: {}", e)))?;
if debug { if debug {
println!("RFS command result: {:?}", result); println!("RFS command result: {:?}", result);
} }
// Check if the command was successful // Check if the command was successful
if !result.success && !result.stderr.is_empty() { if !result.success && !result.stderr.is_empty() {
return Err(RfsError::CommandFailed(result.stderr)); return Err(RfsError::CommandFailed(result.stderr));
} }
Ok(result) Ok(result)
} }