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