...
This commit is contained in:
@@ -1,212 +0,0 @@
|
||||
# Git Interface Redesign Plan
|
||||
|
||||
## Current Understanding
|
||||
|
||||
The current git interface consists of standalone functions like `git_clone`, `git_list`, `git_update`, etc. We want to replace this with an object-oriented interface using a builder pattern that allows for method chaining.
|
||||
|
||||
## New Interface Design
|
||||
|
||||
### Core Components
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class GitTree {
|
||||
+String base_path
|
||||
+new(base_path: &str) Result<GitTree, GitError>
|
||||
+list() Result<Vec<String>, GitError>
|
||||
+find(pattern: &str) Result<Vec<String>, GitError>
|
||||
+get(path_pattern: &str) Result<Vec<GitRepo>, GitError>
|
||||
}
|
||||
|
||||
class GitRepo {
|
||||
+String path
|
||||
+pull() Result<GitRepo, GitError>
|
||||
+reset() Result<GitRepo, GitError>
|
||||
+push() Result<GitRepo, GitError>
|
||||
+commit(message: &str) Result<GitRepo, GitError>
|
||||
+has_changes() Result<bool, GitError>
|
||||
}
|
||||
|
||||
GitTree --> GitRepo : creates
|
||||
```
|
||||
|
||||
### Implementation Details
|
||||
|
||||
1. **GitTree Class**:
|
||||
- Constructor takes a base path parameter that specifies where all git repositories will be located
|
||||
- Methods for listing and finding repositories
|
||||
- A `get()` method that returns one or more GitRepo objects based on a path pattern
|
||||
- The `get()` method can also accept a URL (git or http format) and will clone the repository if it doesn't exist
|
||||
|
||||
2. **GitRepo Class**:
|
||||
- Represents a single git repository
|
||||
- Methods for common git operations: pull, reset, push, commit
|
||||
- Each method returns a Result containing either the GitRepo object (for chaining) or an error
|
||||
- If an operation fails, subsequent operations in the chain are skipped
|
||||
|
||||
3. **Error Handling**:
|
||||
- Each method returns a Result type for immediate error handling
|
||||
- Errors are propagated up the call chain
|
||||
- The existing GitError enum will be reused
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### 1. Create the GitTree and GitRepo Structs in git.rs
|
||||
|
||||
```rust
|
||||
pub struct GitTree {
|
||||
base_path: String,
|
||||
}
|
||||
|
||||
pub struct GitRepo {
|
||||
path: String,
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Implement the GitTree Methods
|
||||
|
||||
```rust
|
||||
impl GitTree {
|
||||
pub fn new(base_path: &str) -> Result<Self, GitError> {
|
||||
// Validate the base path
|
||||
// Create the directory if it doesn't exist
|
||||
Ok(GitTree {
|
||||
base_path: base_path.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn list(&self) -> Result<Vec<String>, GitError> {
|
||||
// List all git repositories under the base path
|
||||
}
|
||||
|
||||
pub fn find(&self, pattern: &str) -> Result<Vec<String>, GitError> {
|
||||
// Find repositories matching the pattern
|
||||
}
|
||||
|
||||
pub fn get(&self, path_pattern: &str) -> Result<Vec<GitRepo>, GitError> {
|
||||
// Find repositories matching the pattern
|
||||
// Return GitRepo objects for each match
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Implement the GitRepo Methods
|
||||
|
||||
```rust
|
||||
impl GitRepo {
|
||||
pub fn pull(&self) -> Result<Self, GitError> {
|
||||
// Pull the latest changes
|
||||
// Return self for chaining or an error
|
||||
}
|
||||
|
||||
pub fn reset(&self) -> Result<Self, GitError> {
|
||||
// Reset any local changes
|
||||
// Return self for chaining or an error
|
||||
}
|
||||
|
||||
pub fn push(&self) -> Result<Self, GitError> {
|
||||
// Push changes to the remote
|
||||
// Return self for chaining or an error
|
||||
}
|
||||
|
||||
pub fn commit(&self, message: &str) -> Result<Self, GitError> {
|
||||
// Commit changes with the given message
|
||||
// Return self for chaining or an error
|
||||
}
|
||||
|
||||
pub fn has_changes(&self) -> Result<bool, GitError> {
|
||||
// Check if the repository has uncommitted changes
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Update the Rhai Wrappers in rhai/git.rs
|
||||
|
||||
```rust
|
||||
// Register the GitTree and GitRepo types with Rhai
|
||||
pub fn register_git_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||
// Register the GitTree type
|
||||
engine.register_type::<GitTree>();
|
||||
engine.register_fn("new", git_tree_new);
|
||||
|
||||
// Register GitTree methods
|
||||
engine.register_fn("list", git_tree_list);
|
||||
engine.register_fn("find", git_tree_find);
|
||||
engine.register_fn("get", git_tree_get);
|
||||
|
||||
// Register GitRepo methods
|
||||
engine.register_type::<GitRepo>();
|
||||
engine.register_fn("pull", git_repo_pull);
|
||||
engine.register_fn("reset", git_repo_reset);
|
||||
engine.register_fn("push", git_repo_push);
|
||||
engine.register_fn("commit", git_repo_commit);
|
||||
engine.register_fn("has_changes", git_repo_has_changes);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Update Tests and Examples
|
||||
|
||||
- Update the test files to use the new interface
|
||||
- Create new examples demonstrating the builder pattern and method chaining
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: Basic Repository Operations
|
||||
|
||||
```rhai
|
||||
// Create a new GitTree object
|
||||
let git_tree = new("/home/user/code");
|
||||
|
||||
// List all repositories
|
||||
let repos = git_tree.list();
|
||||
print(`Found ${repos.len()} repositories`);
|
||||
|
||||
// Find repositories matching a pattern
|
||||
let matching = git_tree.find("my-project*");
|
||||
print(`Found ${matching.len()} matching repositories`);
|
||||
|
||||
// Get a repository and perform operations
|
||||
let repo = git_tree.get("my-project")[0];
|
||||
let result = repo.pull().reset().commit("Update files").push();
|
||||
```
|
||||
|
||||
### Example 2: Working with Multiple Repositories
|
||||
|
||||
```rhai
|
||||
// Create a new GitTree object
|
||||
let git_tree = new("/home/user/code");
|
||||
|
||||
// Get all repositories matching a pattern
|
||||
let repos = git_tree.get("project*");
|
||||
print(`Found ${repos.len()} matching repositories`);
|
||||
|
||||
// Perform operations on all repositories
|
||||
for repo in repos {
|
||||
let result = repo.pull();
|
||||
if result.is_ok() {
|
||||
print(`Successfully pulled ${repo.path}`);
|
||||
} else {
|
||||
print(`Failed to pull ${repo.path}: ${result.error}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Cloning a Repository
|
||||
|
||||
```rhai
|
||||
// Create a new GitTree object
|
||||
let git_tree = new("/home/user/code");
|
||||
|
||||
// Clone a repository by URL
|
||||
let repos = git_tree.get("https://github.com/username/repo.git");
|
||||
let repo = repos[0];
|
||||
print(`Repository cloned to: ${repo.path}`);
|
||||
```
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
1. Implement the new interface in git.rs and rhai/git.rs
|
||||
2. Update all tests and examples to use the new interface
|
||||
3. Remove the old standalone functions
|
@@ -1,565 +0,0 @@
|
||||
# Package Management Module Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
The package management module will:
|
||||
1. Provide a factory called `PackHero` that detects the current platform
|
||||
2. Implement platform-specific package managers for Ubuntu (apt) and macOS (brew)
|
||||
3. Support operations: install, remove, update, upgrade, list installed packages, search for packages, and check if a package is installed
|
||||
4. Include debug functionality similar to buildah
|
||||
5. Ensure all operations are non-interactive and have proper error propagation
|
||||
6. Be wrapped in Rhai for scripting access
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class PackageError {
|
||||
+CommandFailed(String)
|
||||
+CommandExecutionFailed(std::io::Error)
|
||||
+UnsupportedPlatform(String)
|
||||
+Other(String)
|
||||
}
|
||||
|
||||
class PackHero {
|
||||
-platform: Platform
|
||||
-debug: bool
|
||||
+new() PackHero
|
||||
+detect_platform() Platform
|
||||
+set_debug(bool) PackHero
|
||||
+debug() bool
|
||||
+install(package: &str) Result
|
||||
+remove(package: &str) Result
|
||||
+update() Result
|
||||
+upgrade() Result
|
||||
+list_installed() Result
|
||||
+search(query: &str) Result
|
||||
+is_installed(package: &str) Result
|
||||
}
|
||||
|
||||
class Platform {
|
||||
<<enumeration>>
|
||||
Ubuntu
|
||||
MacOS
|
||||
Unknown
|
||||
}
|
||||
|
||||
class PackageManager {
|
||||
<<interface>>
|
||||
+install(package: &str) Result
|
||||
+remove(package: &str) Result
|
||||
+update() Result
|
||||
+upgrade() Result
|
||||
+list_installed() Result
|
||||
+search(query: &str) Result
|
||||
+is_installed(package: &str) Result
|
||||
}
|
||||
|
||||
class AptPackageManager {
|
||||
-debug: bool
|
||||
+new(debug: bool) AptPackageManager
|
||||
+install(package: &str) Result
|
||||
+remove(package: &str) Result
|
||||
+update() Result
|
||||
+upgrade() Result
|
||||
+list_installed() Result
|
||||
+search(query: &str) Result
|
||||
+is_installed(package: &str) Result
|
||||
}
|
||||
|
||||
class BrewPackageManager {
|
||||
-debug: bool
|
||||
+new(debug: bool) BrewPackageManager
|
||||
+install(package: &str) Result
|
||||
+remove(package: &str) Result
|
||||
+update() Result
|
||||
+upgrade() Result
|
||||
+list_installed() Result
|
||||
+search(query: &str) Result
|
||||
+is_installed(package: &str) Result
|
||||
}
|
||||
|
||||
PackHero --> Platform : uses
|
||||
PackHero --> PackageManager : uses
|
||||
PackageManager <|.. AptPackageManager : implements
|
||||
PackageManager <|.. BrewPackageManager : implements
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### 1. Package Error Type
|
||||
|
||||
Create a custom error type for package management operations:
|
||||
|
||||
```rust
|
||||
pub enum PackageError {
|
||||
CommandFailed(String),
|
||||
CommandExecutionFailed(std::io::Error),
|
||||
UnsupportedPlatform(String),
|
||||
Other(String),
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Platform Detection
|
||||
|
||||
Implement platform detection to determine which package manager to use:
|
||||
|
||||
```rust
|
||||
pub enum Platform {
|
||||
Ubuntu,
|
||||
MacOS,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
pub fn detect() -> Self {
|
||||
// Check for macOS
|
||||
if std::path::Path::new("/usr/bin/sw_vers").exists() {
|
||||
return Platform::MacOS;
|
||||
}
|
||||
|
||||
// Check for Ubuntu
|
||||
if std::path::Path::new("/etc/lsb-release").exists() {
|
||||
// Read the file to confirm it's Ubuntu
|
||||
if let Ok(content) = std::fs::read_to_string("/etc/lsb-release") {
|
||||
if content.contains("Ubuntu") {
|
||||
return Platform::Ubuntu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Platform::Unknown
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Package Manager Trait
|
||||
|
||||
Define a trait for package managers to implement:
|
||||
|
||||
```rust
|
||||
pub trait PackageManager {
|
||||
fn install(&self, package: &str) -> Result<CommandResult, PackageError>;
|
||||
fn remove(&self, package: &str) -> Result<CommandResult, PackageError>;
|
||||
fn update(&self) -> Result<CommandResult, PackageError>;
|
||||
fn upgrade(&self) -> Result<CommandResult, PackageError>;
|
||||
fn list_installed(&self) -> Result<Vec<String>, PackageError>;
|
||||
fn search(&self, query: &str) -> Result<Vec<String>, PackageError>;
|
||||
fn is_installed(&self, package: &str) -> Result<bool, PackageError>;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Platform-Specific Implementations
|
||||
|
||||
#### Ubuntu (apt) Implementation
|
||||
|
||||
```rust
|
||||
pub struct AptPackageManager {
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
impl AptPackageManager {
|
||||
pub fn new(debug: bool) -> Self {
|
||||
Self { debug }
|
||||
}
|
||||
}
|
||||
|
||||
impl PackageManager for AptPackageManager {
|
||||
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
execute_package_command(&["apt-get", "install", "-y", package], self.debug)
|
||||
}
|
||||
|
||||
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
execute_package_command(&["apt-get", "remove", "-y", package], self.debug)
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<CommandResult, PackageError> {
|
||||
execute_package_command(&["apt-get", "update", "-y"], self.debug)
|
||||
}
|
||||
|
||||
fn upgrade(&self) -> Result<CommandResult, PackageError> {
|
||||
execute_package_command(&["apt-get", "upgrade", "-y"], self.debug)
|
||||
}
|
||||
|
||||
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
|
||||
let result = execute_package_command(&["dpkg", "--get-selections"], self.debug)?;
|
||||
let packages = result.stdout
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() >= 2 && parts[1] == "install" {
|
||||
Some(parts[0].to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Ok(packages)
|
||||
}
|
||||
|
||||
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
|
||||
let result = execute_package_command(&["apt-cache", "search", query], self.debug)?;
|
||||
let packages = result.stdout
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if !parts.is_empty() {
|
||||
parts[0].to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
})
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
Ok(packages)
|
||||
}
|
||||
|
||||
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
|
||||
let result = execute_package_command(&["dpkg", "-s", package], self.debug);
|
||||
match result {
|
||||
Ok(cmd_result) => Ok(cmd_result.success),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### macOS (brew) Implementation
|
||||
|
||||
```rust
|
||||
pub struct BrewPackageManager {
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
impl BrewPackageManager {
|
||||
pub fn new(debug: bool) -> Self {
|
||||
Self { debug }
|
||||
}
|
||||
}
|
||||
|
||||
impl PackageManager for BrewPackageManager {
|
||||
fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
execute_package_command(&["brew", "install", package], self.debug)
|
||||
}
|
||||
|
||||
fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
execute_package_command(&["brew", "uninstall", package], self.debug)
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<CommandResult, PackageError> {
|
||||
execute_package_command(&["brew", "update"], self.debug)
|
||||
}
|
||||
|
||||
fn upgrade(&self) -> Result<CommandResult, PackageError> {
|
||||
execute_package_command(&["brew", "upgrade"], self.debug)
|
||||
}
|
||||
|
||||
fn list_installed(&self) -> Result<Vec<String>, PackageError> {
|
||||
let result = execute_package_command(&["brew", "list", "--formula"], self.debug)?;
|
||||
let packages = result.stdout
|
||||
.lines()
|
||||
.map(|line| line.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
Ok(packages)
|
||||
}
|
||||
|
||||
fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
|
||||
let result = execute_package_command(&["brew", "search", query], self.debug)?;
|
||||
let packages = result.stdout
|
||||
.lines()
|
||||
.map(|line| line.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
Ok(packages)
|
||||
}
|
||||
|
||||
fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
|
||||
let result = execute_package_command(&["brew", "list", package], self.debug);
|
||||
match result {
|
||||
Ok(cmd_result) => Ok(cmd_result.success),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Command Execution with Debug Support
|
||||
|
||||
Implement a function to execute package management commands with debug support:
|
||||
|
||||
```rust
|
||||
// Thread-local storage for debug flag
|
||||
thread_local! {
|
||||
static DEBUG: std::cell::RefCell<bool> = std::cell::RefCell::new(false);
|
||||
}
|
||||
|
||||
/// Set the debug flag for the current thread
|
||||
pub fn set_thread_local_debug(debug: bool) {
|
||||
DEBUG.with(|cell| {
|
||||
*cell.borrow_mut() = debug;
|
||||
});
|
||||
}
|
||||
|
||||
/// Get the debug flag for the current thread
|
||||
pub fn thread_local_debug() -> bool {
|
||||
DEBUG.with(|cell| {
|
||||
*cell.borrow()
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute a package management command and return the result
|
||||
pub fn execute_package_command(args: &[&str], debug: bool) -> Result<CommandResult, PackageError> {
|
||||
// Save the current debug flag
|
||||
let previous_debug = thread_local_debug();
|
||||
|
||||
// Set the thread-local debug flag
|
||||
set_thread_local_debug(debug);
|
||||
|
||||
if debug {
|
||||
println!("Executing command: {}", args.join(" "));
|
||||
}
|
||||
|
||||
let output = Command::new(args[0])
|
||||
.args(&args[1..])
|
||||
.output();
|
||||
|
||||
// Restore the previous debug flag
|
||||
set_thread_local_debug(previous_debug);
|
||||
|
||||
match output {
|
||||
Ok(output) => {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
||||
|
||||
let result = CommandResult {
|
||||
stdout,
|
||||
stderr,
|
||||
success: output.status.success(),
|
||||
code: output.status.code().unwrap_or(-1),
|
||||
};
|
||||
|
||||
// Always output stdout/stderr when debug is true
|
||||
if debug {
|
||||
if !result.stdout.is_empty() {
|
||||
println!("Command stdout: {}", result.stdout);
|
||||
}
|
||||
|
||||
if !result.stderr.is_empty() {
|
||||
println!("Command stderr: {}", result.stderr);
|
||||
}
|
||||
|
||||
if result.success {
|
||||
println!("Command succeeded with code {}", result.code);
|
||||
} else {
|
||||
println!("Command failed with code {}", result.code);
|
||||
}
|
||||
}
|
||||
|
||||
if result.success {
|
||||
Ok(result)
|
||||
} else {
|
||||
// If command failed and debug is false, output stderr
|
||||
if !debug {
|
||||
println!("Command failed with code {}: {}", result.code, result.stderr.trim());
|
||||
}
|
||||
Err(PackageError::CommandFailed(format!("Command failed with code {}: {}",
|
||||
result.code, result.stderr.trim())))
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// Always output error information
|
||||
println!("Command execution failed: {}", e);
|
||||
Err(PackageError::CommandExecutionFailed(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. PackHero Factory
|
||||
|
||||
Implement the PackHero factory to provide a unified interface:
|
||||
|
||||
```rust
|
||||
pub struct PackHero {
|
||||
platform: Platform,
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
impl PackHero {
|
||||
pub fn new() -> Self {
|
||||
let platform = Platform::detect();
|
||||
Self {
|
||||
platform,
|
||||
debug: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_debug(&mut self, debug: bool) -> &mut Self {
|
||||
self.debug = debug;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn debug(&self) -> bool {
|
||||
self.debug
|
||||
}
|
||||
|
||||
fn get_package_manager(&self) -> Result<Box<dyn PackageManager>, PackageError> {
|
||||
match self.platform {
|
||||
Platform::Ubuntu => Ok(Box::new(AptPackageManager::new(self.debug))),
|
||||
Platform::MacOS => Ok(Box::new(BrewPackageManager::new(self.debug))),
|
||||
Platform::Unknown => Err(PackageError::UnsupportedPlatform("Unsupported platform".to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn install(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.install(package)
|
||||
}
|
||||
|
||||
pub fn remove(&self, package: &str) -> Result<CommandResult, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.remove(package)
|
||||
}
|
||||
|
||||
pub fn update(&self) -> Result<CommandResult, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.update()
|
||||
}
|
||||
|
||||
pub fn upgrade(&self) -> Result<CommandResult, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.upgrade()
|
||||
}
|
||||
|
||||
pub fn list_installed(&self) -> Result<Vec<String>, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.list_installed()
|
||||
}
|
||||
|
||||
pub fn search(&self, query: &str) -> Result<Vec<String>, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.search(query)
|
||||
}
|
||||
|
||||
pub fn is_installed(&self, package: &str) -> Result<bool, PackageError> {
|
||||
let pm = self.get_package_manager()?;
|
||||
pm.is_installed(package)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Rhai Integration
|
||||
|
||||
Update the Rhai OS module to include the package management functions:
|
||||
|
||||
```rust
|
||||
// In rhai/os.rs
|
||||
|
||||
// Register package management functions
|
||||
engine.register_fn("package_install", package_install);
|
||||
engine.register_fn("package_remove", package_remove);
|
||||
engine.register_fn("package_update", package_update);
|
||||
engine.register_fn("package_upgrade", package_upgrade);
|
||||
engine.register_fn("package_list", package_list);
|
||||
engine.register_fn("package_search", package_search);
|
||||
engine.register_fn("package_is_installed", package_is_installed);
|
||||
engine.register_fn("package_set_debug", package_set_debug);
|
||||
|
||||
// Wrapper for os::package::install
|
||||
pub fn package_install(package: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
let hero = os::package::PackHero::new();
|
||||
hero.install(package)
|
||||
.map(|_| "Package installed successfully".to_string())
|
||||
.to_rhai_error()
|
||||
}
|
||||
|
||||
// Wrapper for os::package::remove
|
||||
pub fn package_remove(package: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
let hero = os::package::PackHero::new();
|
||||
hero.remove(package)
|
||||
.map(|_| "Package removed successfully".to_string())
|
||||
.to_rhai_error()
|
||||
}
|
||||
|
||||
// Wrapper for os::package::update
|
||||
pub fn package_update() -> Result<String, Box<EvalAltResult>> {
|
||||
let hero = os::package::PackHero::new();
|
||||
hero.update()
|
||||
.map(|_| "Package lists updated successfully".to_string())
|
||||
.to_rhai_error()
|
||||
}
|
||||
|
||||
// Wrapper for os::package::upgrade
|
||||
pub fn package_upgrade() -> Result<String, Box<EvalAltResult>> {
|
||||
let hero = os::package::PackHero::new();
|
||||
hero.upgrade()
|
||||
.map(|_| "Packages upgraded successfully".to_string())
|
||||
.to_rhai_error()
|
||||
}
|
||||
|
||||
// Wrapper for os::package::list_installed
|
||||
pub fn package_list() -> Result<Array, Box<EvalAltResult>> {
|
||||
let hero = os::package::PackHero::new();
|
||||
let packages = hero.list_installed().to_rhai_error()?;
|
||||
|
||||
// Convert Vec<String> to Rhai Array
|
||||
let mut array = Array::new();
|
||||
for package in packages {
|
||||
array.push(package.into());
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
// Wrapper for os::package::search
|
||||
pub fn package_search(query: &str) -> Result<Array, Box<EvalAltResult>> {
|
||||
let hero = os::package::PackHero::new();
|
||||
let packages = hero.search(query).to_rhai_error()?;
|
||||
|
||||
// Convert Vec<String> to Rhai Array
|
||||
let mut array = Array::new();
|
||||
for package in packages {
|
||||
array.push(package.into());
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
// Wrapper for os::package::is_installed
|
||||
pub fn package_is_installed(package: &str) -> Result<bool, Box<EvalAltResult>> {
|
||||
let hero = os::package::PackHero::new();
|
||||
hero.is_installed(package).to_rhai_error()
|
||||
}
|
||||
|
||||
// Global debug flag for package management
|
||||
static mut PACKAGE_DEBUG: bool = false;
|
||||
|
||||
// Wrapper for setting package debug mode
|
||||
pub fn package_set_debug(debug: bool) -> bool {
|
||||
unsafe {
|
||||
PACKAGE_DEBUG = debug;
|
||||
PACKAGE_DEBUG
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. Create the package.rs file in the os directory
|
||||
2. Implement the PackageError enum
|
||||
3. Implement the Platform enum with detection logic
|
||||
4. Implement the PackageManager trait
|
||||
5. Implement the AptPackageManager for Ubuntu
|
||||
6. Implement the BrewPackageManager for macOS
|
||||
7. Implement the debug functionality with thread-local storage
|
||||
8. Implement the PackHero factory
|
||||
9. Update os/mod.rs to include the package module
|
||||
10. Update rhai/os.rs to include the package management functions
|
||||
11. Test the implementation on both Ubuntu and macOS
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
1. Create unit tests for each platform-specific implementation
|
||||
2. Create integration tests that verify the PackHero factory correctly detects the platform and uses the appropriate package manager
|
||||
3. Create Rhai examples that demonstrate the use of the package management functions
|
@@ -34,7 +34,6 @@ pub use os::{
|
||||
pub use process::{
|
||||
register_process_module,
|
||||
// Run functions
|
||||
run, run_silent, run_with_options, new_run_options,
|
||||
// Process management functions
|
||||
which, kill, process_list, process_get
|
||||
};
|
||||
|
@@ -2,8 +2,9 @@
|
||||
//!
|
||||
//! This module provides Rhai wrappers for the functions in the Process module.
|
||||
|
||||
use rhai::{Engine, EvalAltResult, Array, Dynamic, Map};
|
||||
use rhai::{Engine, EvalAltResult, Array, Dynamic};
|
||||
use crate::process::{self, CommandResult, ProcessInfo, RunError, ProcessError};
|
||||
use std::clone::Clone;
|
||||
|
||||
/// Register Process module functions with the Rhai engine
|
||||
///
|
||||
@@ -16,43 +17,36 @@ use crate::process::{self, CommandResult, ProcessInfo, RunError, ProcessError};
|
||||
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
|
||||
pub fn register_process_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||
// Register types
|
||||
register_process_types(engine)?;
|
||||
|
||||
// Register run functions
|
||||
engine.register_fn("run", run);
|
||||
engine.register_fn("run_silent", run_silent);
|
||||
engine.register_fn("run_with_options", run_with_options);
|
||||
engine.register_fn("new_run_options", new_run_options);
|
||||
|
||||
// Register process management functions
|
||||
engine.register_fn("which", which);
|
||||
engine.register_fn("kill", kill);
|
||||
engine.register_fn("process_list", process_list);
|
||||
engine.register_fn("process_get", process_get);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// register_process_types(engine)?; // Removed
|
||||
|
||||
/// Register Process module types with the Rhai engine
|
||||
fn register_process_types(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||
// Register CommandResult type and methods
|
||||
// Register CommandResult type and its methods
|
||||
engine.register_type_with_name::<CommandResult>("CommandResult");
|
||||
|
||||
// Register getters for CommandResult properties
|
||||
engine.register_get("stdout", |r: &mut CommandResult| r.stdout.clone());
|
||||
engine.register_get("stderr", |r: &mut CommandResult| r.stderr.clone());
|
||||
engine.register_get("success", |r: &mut CommandResult| r.success);
|
||||
engine.register_get("code", |r: &mut CommandResult| r.code);
|
||||
|
||||
// Register ProcessInfo type and methods
|
||||
|
||||
// Register ProcessInfo type and its methods
|
||||
engine.register_type_with_name::<ProcessInfo>("ProcessInfo");
|
||||
|
||||
// Register getters for ProcessInfo properties
|
||||
engine.register_get("pid", |p: &mut ProcessInfo| p.pid);
|
||||
engine.register_get("name", |p: &mut ProcessInfo| p.name.clone());
|
||||
engine.register_get("memory", |p: &mut ProcessInfo| p.memory);
|
||||
engine.register_get("cpu", |p: &mut ProcessInfo| p.cpu);
|
||||
|
||||
|
||||
// Register CommandBuilder type and its methods
|
||||
engine.register_type_with_name::<RhaiCommandBuilder>("CommandBuilder");
|
||||
engine.register_fn("run", RhaiCommandBuilder::new_rhai); // This is the builder entry point
|
||||
engine.register_fn("silent", RhaiCommandBuilder::silent); // Method on CommandBuilder
|
||||
engine.register_fn("ignore_error", RhaiCommandBuilder::ignore_error); // Method on CommandBuilder
|
||||
engine.register_fn("log", RhaiCommandBuilder::log); // Method on CommandBuilder
|
||||
engine.register_fn("execute", RhaiCommandBuilder::execute_command); // Method on CommandBuilder
|
||||
|
||||
// Register other process management functions
|
||||
engine.register_fn("which", which);
|
||||
engine.register_fn("kill", kill);
|
||||
engine.register_fn("process_list", process_list);
|
||||
engine.register_fn("process_get", process_get);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -66,6 +60,56 @@ fn run_error_to_rhai_error<T>(result: Result<T, RunError>) -> Result<T, Box<Eval
|
||||
})
|
||||
}
|
||||
|
||||
// Define a Rhai-facing builder struct
|
||||
#[derive(Clone)]
|
||||
struct RhaiCommandBuilder {
|
||||
command: String,
|
||||
die_on_error: bool,
|
||||
is_silent: bool,
|
||||
enable_log: bool,
|
||||
}
|
||||
|
||||
impl RhaiCommandBuilder {
|
||||
// Constructor function for Rhai (registered as `run`)
|
||||
pub fn new_rhai(command: &str) -> Self {
|
||||
Self {
|
||||
command: command.to_string(),
|
||||
die_on_error: true, // Default: die on error
|
||||
is_silent: false,
|
||||
enable_log: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Rhai method: .silent()
|
||||
pub fn silent(mut self) -> Self {
|
||||
self.is_silent = true;
|
||||
self
|
||||
}
|
||||
|
||||
// Rhai method: .ignore_error()
|
||||
pub fn ignore_error(mut self) -> Self {
|
||||
self.die_on_error = false;
|
||||
self
|
||||
}
|
||||
|
||||
// Rhai method: .log()
|
||||
pub fn log(mut self) -> Self {
|
||||
self.enable_log = true;
|
||||
self
|
||||
}
|
||||
|
||||
// Rhai method: .execute() - Execute the command
|
||||
pub fn execute_command(self) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
let builder = process::run(&self.command)
|
||||
.die(self.die_on_error)
|
||||
.silent(self.is_silent)
|
||||
.log(self.enable_log);
|
||||
|
||||
// Execute the command
|
||||
run_error_to_rhai_error(builder.execute())
|
||||
}
|
||||
}
|
||||
|
||||
fn process_error_to_rhai_error<T>(result: Result<T, ProcessError>) -> Result<T, Box<EvalAltResult>> {
|
||||
result.map_err(|e| {
|
||||
Box::new(EvalAltResult::ErrorRuntime(
|
||||
@@ -75,78 +119,6 @@ fn process_error_to_rhai_error<T>(result: Result<T, ProcessError>) -> Result<T,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new Map with default run options
|
||||
pub fn new_run_options() -> Map {
|
||||
let mut map = Map::new();
|
||||
map.insert("die".into(), Dynamic::from(true));
|
||||
map.insert("silent".into(), Dynamic::from(false));
|
||||
map.insert("async_exec".into(), Dynamic::from(false));
|
||||
map.insert("log".into(), Dynamic::from(false));
|
||||
map
|
||||
}
|
||||
|
||||
//
|
||||
// Run Function Wrappers
|
||||
//
|
||||
|
||||
/// Wrapper for process::run_command
|
||||
///
|
||||
/// Run a command or multiline script with arguments.
|
||||
pub fn run(command: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
run_error_to_rhai_error(process::run_command(command))
|
||||
}
|
||||
|
||||
/// Wrapper for process::run_silent
|
||||
///
|
||||
/// Run a command or multiline script with arguments silently.
|
||||
pub fn run_silent(command: &str) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
run_error_to_rhai_error(process::run_silent(command))
|
||||
}
|
||||
|
||||
/// Run a command with options specified in a Map
|
||||
///
|
||||
/// This provides a builder-style interface for Rhai scripts.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rhai
|
||||
/// let options = new_run_options();
|
||||
/// options.die = false;
|
||||
/// options.silent = true;
|
||||
/// let result = run("echo Hello", options);
|
||||
/// ```
|
||||
pub fn run_with_options(command: &str, options: Map) -> Result<CommandResult, Box<EvalAltResult>> {
|
||||
let mut builder = process::run(command);
|
||||
|
||||
// Apply options from the map
|
||||
if let Some(die) = options.get("die") {
|
||||
if let Ok(die_val) = die.clone().as_bool() {
|
||||
builder = builder.die(die_val);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(silent) = options.get("silent") {
|
||||
if let Ok(silent_val) = silent.clone().as_bool() {
|
||||
builder = builder.silent(silent_val);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(async_exec) = options.get("async_exec") {
|
||||
if let Ok(async_val) = async_exec.clone().as_bool() {
|
||||
builder = builder.async_exec(async_val);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(log) = options.get("log") {
|
||||
if let Ok(log_val) = log.clone().as_bool() {
|
||||
builder = builder.log(log_val);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the command
|
||||
run_error_to_rhai_error(builder.execute())
|
||||
}
|
||||
|
||||
//
|
||||
// Process Management Function Wrappers
|
||||
//
|
||||
|
@@ -1,133 +0,0 @@
|
||||
# Implementation Plan: Rhai Wrappers for Text Tools
|
||||
|
||||
## 1. Overview
|
||||
|
||||
We'll create a new module `rhai/text.rs` that will provide Rhai wrappers for all functionality in the text module, including:
|
||||
- TextReplacer (from replace.rs)
|
||||
- TemplateBuilder (from template.rs)
|
||||
- name_fix and path_fix functions (from fix.rs)
|
||||
- dedent and prefix functions (from dedent.rs)
|
||||
|
||||
The implementation will follow the builder pattern for the TextReplacer and TemplateBuilder classes, similar to their Rust implementations, and will use the same error handling pattern as the existing Rhai modules.
|
||||
|
||||
## 2. Module Structure
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[rhai/mod.rs] --> B[rhai/text.rs]
|
||||
B --> C[TextReplacer Wrappers]
|
||||
B --> D[TemplateBuilder Wrappers]
|
||||
B --> E[Fix Function Wrappers]
|
||||
B --> F[Dedent Function Wrappers]
|
||||
B --> G[Error Handling]
|
||||
B --> H[Registration Functions]
|
||||
```
|
||||
|
||||
## 3. Implementation Details
|
||||
|
||||
### 3.1. Module Setup and Registration
|
||||
|
||||
1. Create a new file `rhai/text.rs`
|
||||
2. Add the module to `rhai/mod.rs`
|
||||
3. Implement a `register_text_module` function to register all text-related functions with the Rhai engine
|
||||
4. Update the main `register` function in `rhai/mod.rs` to call `register_text_module`
|
||||
|
||||
### 3.2. TextReplacer Implementation
|
||||
|
||||
1. Register the TextReplacer type with the Rhai engine
|
||||
2. Implement the following functions:
|
||||
- `text_replacer_new()` - Creates a new TextReplacerBuilder
|
||||
- `pattern(builder, pat)` - Sets the pattern to search for and automatically adds any previous pattern/replacement pair to the chain
|
||||
- `replacement(builder, rep)` - Sets the replacement text
|
||||
- `regex(builder, yes)` - Sets whether to use regex
|
||||
- `case_insensitive(builder, yes)` - Sets whether the replacement should be case-insensitive
|
||||
- `build(builder)` - Builds the TextReplacer with all configured replacement operations
|
||||
- `replace(replacer, input)` - Applies all configured replacement operations to the input text
|
||||
- `replace_file(replacer, path)` - Reads a file, applies all replacements, and returns the result as a string
|
||||
- `replace_file_in_place(replacer, path)` - Reads a file, applies all replacements, and writes the result back to the file
|
||||
- `replace_file_to(replacer, input_path, output_path)` - Reads a file, applies all replacements, and writes the result to a new file
|
||||
|
||||
### 3.3. TemplateBuilder Implementation
|
||||
|
||||
1. Register the TemplateBuilder type with the Rhai engine
|
||||
2. Implement the following functions:
|
||||
- `template_builder_open(template_path)` - Creates a new TemplateBuilder with the specified template path
|
||||
- `add_var(builder, name, value)` - Adds a variable to the template context
|
||||
- `add_vars(builder, vars_map)` - Adds multiple variables to the template context from a Map
|
||||
- `render(builder)` - Renders the template with the current context
|
||||
- `render_to_file(builder, output_path)` - Renders the template and writes the result to a file
|
||||
|
||||
### 3.4. Fix Functions Implementation
|
||||
|
||||
1. Implement wrappers for the following functions:
|
||||
- `name_fix(text)` - Sanitizes a name by replacing special characters with underscores
|
||||
- `path_fix(text)` - Applies name_fix to the filename part of a path
|
||||
|
||||
### 3.5. Dedent Functions Implementation
|
||||
|
||||
1. Implement wrappers for the following functions:
|
||||
- `dedent(text)` - Removes common leading whitespace from a multiline string
|
||||
- `prefix(text, prefix)` - Adds a prefix to each line of a multiline string
|
||||
|
||||
### 3.6. Error Handling
|
||||
|
||||
1. Implement helper functions to convert Rust errors to Rhai errors:
|
||||
- `io_error_to_rhai_error` - Converts io::Error to EvalAltResult
|
||||
- `tera_error_to_rhai_error` - Converts tera::Error to EvalAltResult
|
||||
- `string_error_to_rhai_error` - Converts String error to EvalAltResult
|
||||
|
||||
## 4. Example Usage in Rhai Scripts
|
||||
|
||||
### TextReplacer Example
|
||||
```rhai
|
||||
// Create a TextReplacer with multiple replacements
|
||||
let replacer = text_replacer_new()
|
||||
.pattern("foo").replacement("bar").regex(true)
|
||||
.pattern("hello").replacement("world")
|
||||
.build();
|
||||
|
||||
// Use the replacer
|
||||
let result = replacer.replace("foo hello foo");
|
||||
println(result); // Outputs: "bar world bar"
|
||||
|
||||
// Replace in a file
|
||||
let file_result = replacer.replace_file("input.txt");
|
||||
println(file_result);
|
||||
|
||||
// Replace and write to a new file
|
||||
replacer.replace_file_to("input.txt", "output.txt");
|
||||
```
|
||||
|
||||
### TemplateBuilder Example
|
||||
```rhai
|
||||
// Create a TemplateBuilder
|
||||
let template = template_builder_open("template.txt")
|
||||
.add_var("name", "John")
|
||||
.add_var("age", 30)
|
||||
.add_var("items", ["apple", "banana", "cherry"]);
|
||||
|
||||
// Render the template
|
||||
let result = template.render();
|
||||
println(result);
|
||||
|
||||
// Render to a file
|
||||
template.render_to_file("output.html");
|
||||
```
|
||||
|
||||
### Fix and Dedent Examples
|
||||
```rhai
|
||||
// Use name_fix and path_fix
|
||||
let fixed_name = name_fix("Hello World!");
|
||||
println(fixed_name); // Outputs: "hello_world"
|
||||
|
||||
let fixed_path = path_fix("/path/to/Hello World!");
|
||||
println(fixed_path); // Outputs: "/path/to/hello_world"
|
||||
|
||||
// Use dedent and prefix
|
||||
let indented_text = " line 1\n line 2\n line 3";
|
||||
let dedented = dedent(indented_text);
|
||||
println(dedented); // Outputs: "line 1\nline 2\n line 3"
|
||||
|
||||
let text = "line 1\nline 2\nline 3";
|
||||
let prefixed = prefix(text, " ");
|
||||
println(prefixed); // Outputs: " line 1\n line 2\n line 3"
|
@@ -1,27 +0,0 @@
|
||||
extern crate sal;
|
||||
|
||||
use rhai::Engine;
|
||||
use std::fs;
|
||||
use std::error::Error;
|
||||
|
||||
// Import the SAL library
|
||||
use sal::rhai;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Create a new Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register all SAL modules with the engine
|
||||
rhai::register(&mut engine)?;
|
||||
|
||||
// Read the test script
|
||||
let script = fs::read_to_string("test_git.rhai")?;
|
||||
|
||||
// Evaluate the script
|
||||
match engine.eval::<()>(&script) {
|
||||
Ok(_) => println!("Script executed successfully"),
|
||||
Err(e) => eprintln!("Script execution error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
|
||||
use regex::Regex;
|
||||
use std::fs;
|
||||
use std::io::{self, Read};
|
||||
use std::io::{self, Read, Seek, SeekFrom};
|
||||
use std::path::Path;
|
||||
|
||||
/// Represents the type of replacement to perform.
|
||||
|
Reference in New Issue
Block a user