This commit is contained in:
despiegk 2025-05-08 09:54:20 +03:00
parent 104097ee4b
commit 2cd9faf4fa
52 changed files with 1115 additions and 1757 deletions

View File

@ -1,207 +0,0 @@
# Buildah Debug Implementation Plan
## Current State
1. The `Builder` struct already has a `debug` field and methods to get and set it (`debug()` and `set_debug()`).
2. There's a thread-local `DEBUG` variable with functions to get and set it.
3. The `execute_buildah_command` function checks the thread-local debug flag and outputs some debug information.
4. There's an unused `execute_buildah_command_with_debug` function that takes a `Builder` reference.
## Requirements
1. Use only the Builder's debug flag, not the thread-local debug flag.
2. When debug is true, output stdout/stderr regardless of whether the command succeeds or fails.
3. If debug is false but there's an error, still output all information.
## Implementation Plan
### 1. Keep the Existing Thread-Local DEBUG Variable
We'll keep the existing thread-local DEBUG variable that's already in the code:
```rust
// Thread-local storage for debug flag
thread_local! {
static DEBUG: std::cell::RefCell<bool> = std::cell::RefCell::new(false);
}
```
### 2. Modify the Builder Methods to Set/Clear the Thread-Local Debug Flag
We'll modify each Builder method to set the thread-local debug flag from the Builder's debug flag before calling `execute_buildah_command` and restore it afterward:
```rust
pub fn run(&self, command: &str) -> Result<CommandResult, BuildahError> {
if let Some(container_id) = &self.container_id {
// Save the current debug flag
let previous_debug = thread_local_debug();
// Set the thread-local debug flag from the Builder's debug flag
set_thread_local_debug(self.debug);
// Execute the command
let result = execute_buildah_command(&["run", container_id, "sh", "-c", command]);
// Restore the previous debug flag
set_thread_local_debug(previous_debug);
result
} else {
Err(BuildahError::Other("No container ID available".to_string()))
}
}
```
### 3. Keep the Existing execute_buildah_command Function
The existing `execute_buildah_command` function already checks the thread-local debug flag, so we don't need to modify it:
```rust
pub fn execute_buildah_command(args: &[&str]) -> Result<CommandResult, BuildahError> {
// Get the debug flag from thread-local storage
let debug = thread_local_debug();
if debug {
println!("Executing buildah command: buildah {}", args.join(" "));
}
// ... rest of the function ...
}
```
### 4. Update the execute_buildah_command Function to Output stdout/stderr
We need to modify the `execute_buildah_command` function to output stdout/stderr when debug is true, regardless of success/failure:
```rust
pub fn execute_buildah_command(args: &[&str]) -> Result<CommandResult, BuildahError> {
// Get the debug flag from thread-local storage
let debug = thread_local_debug();
if debug {
println!("Executing buildah command: buildah {}", args.join(" "));
}
let output = Command::new("buildah")
.args(args)
.output();
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(BuildahError::CommandFailed(format!("Command failed with code {}: {}",
result.code, result.stderr.trim())))
}
},
Err(e) => {
// Always output error information
println!("Command execution failed: {}", e);
Err(BuildahError::CommandExecutionFailed(e))
}
}
}
```
### 5. Handle Static Methods
For static methods, we'll just call `execute_buildah_command` directly, which will use the thread-local debug flag:
```rust
pub fn images() -> Result<Vec<Image>, BuildahError> {
let result = execute_buildah_command(&["images", "--json"])?;
// Rest of the method...
}
```
If we want to support debugging in static methods, we could add an optional debug parameter:
```rust
pub fn images(debug: bool) -> Result<Vec<Image>, BuildahError> {
// Save the current debug flag
let previous_debug = thread_local_debug();
// Set the thread-local debug flag
set_thread_local_debug(debug);
// Execute the command
let result = execute_buildah_command(&["images", "--json"]);
// Restore the previous debug flag
set_thread_local_debug(previous_debug);
// Process the result
match result {
Ok(cmd_result) => {
// Parse JSON and return images...
},
Err(e) => Err(e),
}
}
// Backward compatibility method
pub fn images() -> Result<Vec<Image>, BuildahError> {
Self::images(false)
}
```
### 6. Update Rhai Bindings
We still need to update the Rhai bindings to expose the debug functionality:
```rust
// Add a debug getter and setter
engine.register_get("debug", |builder: &mut Builder| builder.debug());
engine.register_set("debug", |builder: &mut Builder, debug: bool| { builder.set_debug(debug); });
```
### 7. Remove Unused Code
We can remove the unused `execute_buildah_command_with_debug` function.
## Implementation Flow
1. Modify the `execute_buildah_command` function to output stdout/stderr when debug is true
2. Update all Builder methods to set/restore the thread-local debug flag
3. Update static methods to optionally accept a debug parameter
4. Update Rhai bindings to expose the debug functionality
5. Remove unused code
## Benefits
1. Minimal changes to the existing codebase
2. No changes to function signatures
3. Backward compatibility with existing code
4. Improved debugging capabilities

View File

@ -183,36 +183,33 @@ Commits staged changes.
Pushes committed changes to the remote.
- **Description**: Performs `git push` to the default remote and branch.
- **Returns**: `GitRepo` - The same `GitRepo` object for chaining. Halts on error (e.g., network issues, push rejected).
- **Rhai Example** (assuming `app_repo` is a `GitRepo` object with committed changes):
```rhai
print(`Pushing changes for ${app_repo.path()}...`);
app_repo.push(); // Halts on error
print("Push successful.");
```
- **Returns**: `GitRepo`
```rhai
print(`Pushing changes for ${app_repo.path()}...`);
app_repo.push(); // Halts on error
print("Push successful.");
```
## Chaining Operations
`GitRepo` methods like `pull`, `reset`, `commit`, and `push` return the `GitRepo` object, allowing for chained operations. If any operation in the chain fails, script execution halts.
```rhai
let git_tree = git_tree_new("./my_projects");
// Assumes "my_writable_app" exists and you have write access.
// get() will halt if not found.
let app_repo = git_tree.get("my_writable_app");
print(`Performing chained operations on ${app_repo.path()}`);
- **Rhai Example**:
```rhai
let git_tree = git_tree_new("./my_projects");
// Assumes "my_writable_app" exists and you have write access.
// get() will halt if not found.
let app_repo = git_tree.get("my_writable_app");
print(`Performing chained operations on ${app_repo.path()}`);
// This example demonstrates a common workflow.
// Ensure the repo state is suitable (e.g., changes exist for commit/push).
app_repo.pull()
.commit("Rhai: Chained operations - automated update") // Commits if pull results in changes or local changes existed and were staged.
.push();
print("Chained pull, commit, and push reported successful.");
// This example demonstrates a common workflow.
// Ensure the repo state is suitable (e.g., changes exist for commit/push).
app_repo.pull()
.commit("Rhai: Chained operations - automated update") // Commits if pull results in changes or local changes existed and were staged.
.push();
print("Chained pull, commit, and push reported successful.");
// Alternative:
// app_repo.pull();
// if app_repo.has_changes() {
// app_repo.commit("Updates").push();
// }
```
// Alternative:
// app_repo.pull();
// if app_repo.has_changes() {
// app_repo.commit("Updates").push();
// }
```

60
docs/os/download.md Normal file
View File

@ -0,0 +1,60 @@
# os.download Module
### `download(url, dest, min_size_kb)`
Download a file from URL to destination using the curl command.
- **Description**: Downloads a file from the given `url`. If `dest` is a directory, the filename is derived from the URL. If `dest` is a file path, it is used directly. Requires the `curl` command to be available. Halts script execution on download or file writing errors, or if the downloaded file size is less than `min_size_kb`. Returns the destination path.
- **Returns**: `String` - The path where the file was downloaded.
- **Arguments**:
- `url`: `String` - The URL of the file to download.
- `dest`: `String` - The destination path (directory or file).
- `min_size_kb`: `Integer` - The minimum expected size of the downloaded file in kilobytes.
```rhai
let download_url = "https://example.com/archive.zip";
let download_dest_dir = "/tmp/downloads";
print(`Downloading ${download_url} to ${download_dest_dir}...`);
let downloaded_file_path = os::download(download_url, download_dest_dir, 50); // Halts on error
print(`Downloaded to: ${downloaded_file_path}`);
```
---
### `download_file(url, dest, min_size_kb)`
Download a file from URL to a specific file destination using the curl command.
- **Description**: Downloads a file from the given `url` directly to the specified file `dest`. Requires the `curl` command. Halts script execution on download or file writing errors, or if the downloaded file size is less than `min_size_kb`. Returns the destination path.
- **Returns**: `String` - The path where the file was downloaded.
- **Arguments**:
- `url`: `String` - The URL of the file to download.
- `dest`: `String` - The full path where the file should be saved.
- `min_size_kb`: `Integer` - The minimum expected size of the downloaded file in kilobytes.
```rhai
let data_url = "https://example.com/dataset.tar.gz";
let local_path = "/opt/data/dataset.tar.gz";
print(`Downloading ${data_url} to ${local_path}...`);
os::download_file(data_url, local_path, 1024); // Halts on error
print(`Downloaded dataset to: ${local_path}`);
```
---
### `download_install(url, min_size_kb)`
Download a file and install it if it\'s a supported package format.
- **Description**: Downloads a file from the given `url` to a temporary location and then attempts to install it using the appropriate system package manager if the file format is supported (e.g., `.deb` on Ubuntu, `.pkg` or `.dmg` on MacOS). Requires the `curl` command and the system package manager. Halts script execution on download, installation, or file size errors. Returns a success message.
- **Returns**: `String` - A success message upon successful download and installation attempt.
- **Arguments**:
- `url`: `String` - The URL of the package file to download and install.
- `min_size_kb`: `Integer` - The minimum expected size of the downloaded file in kilobytes.
```rhai
let package_url = "https://example.com/mytool.deb";
print(`Downloading and installing ${package_url}...`);
os::download_install(package_url, 300); // Halts on error
print("Installation attempt finished.");
```

338
docs/os/fs.md Normal file
View File

@ -0,0 +1,338 @@
# os.fs Module
The `os` module provides functions for interacting with the operating system, including file system operations, command execution checks, downloads, and package management.
All functions that interact with the file system or external commands will halt the script execution if an error occurs, unless explicitly noted otherwise.
---
### `copy(src, dest)`
Recursively copy a file or directory from source to destination.
- **Description**: Performs a recursive copy operation. Halts script execution on failure.
- **Returns**: `String` - The destination path.
- **Arguments**:
- `src`: `String` - The path to the source file or directory.
- `dest`: `String` - The path to the destination file or directory.
```rhai
print("Copying directory...");
let source_dir = "/tmp/source_data";
let dest_dir = "/backup/source_data";
let copied_path = os::copy(source_dir, dest_dir); // Halts on error
print(`Copied ${source_dir} to ${copied_path}`);
```
---
### `exist(path)`
Check if a file or directory exists.
- **Description**: Checks for the presence of a file or directory at the given path. This function does NOT halt on error if the path is invalid or permissions prevent checking.
- **Returns**: `Boolean` - `true` if the path exists, `false` otherwise.
- **Arguments**:
- `path`: `String` - The path to check.
```rhai
if os::exist("config.json") {
print(`${file_path} exists.`);
} else {
print(`${file_path} does not exist.`);
}
```
---
### `find_file(dir, filename)`
Find a file in a directory (with support for wildcards).
- **Description**: Searches for a file matching `filename` within the specified `dir`. Supports simple wildcards like `*` and `?`. Halts script execution if the directory cannot be read or if no file is found.
- **Returns**: `String` - The path to the first file found that matches the pattern.
- **Arguments**:
- `dir`: `String` - The directory to search within.
- `filename`: `String` - The filename pattern to search for (e.g., `"*.log"`).
```rhai
let log_file = os::find_file("/var/log", "syslog*.log"); // Halts if not found or directory error
print(`Found log file: ${log_file}`);
```
---
### `find_files(dir, filename)`
Find multiple files in a directory (recursive, with support for wildcards).
- **Description**: Recursively searches for all files matching `filename` within the specified `dir` and its subdirectories. Supports simple wildcards. Halts script execution if the directory cannot be read.
- **Returns**: `Array` of `String` - An array containing paths to all matching files.
- **Arguments**:
- `dir`: `String` - The directory to start the recursive search from.
- `filename`: `String` - The filename pattern to search for (e.g., `"*.tmp"`).
```rhai
let temp_files = os::find_files("/tmp", "*.swp"); // Halts on directory error
print("Found temporary files:");
for file in temp_files {
print(`- ${file}`);
}
```
---
### `find_dir(dir, dirname)`
Find a directory in a parent directory (with support for wildcards).
- **Description**: Searches for a directory matching `dirname` within the specified `dir`. Supports simple wildcards. Halts script execution if the directory cannot be read or if no directory is found.
- **Returns**: `String` - The path to the first directory found that matches the pattern.
- **Arguments**:
- `dir`: `String` - The directory to search within.
- `dirname`: `String` - The directory name pattern to search for (e.g., `"backup_*"`).
```rhai
let latest_backup_dir = os::find_dir("/mnt/backups", "backup_20*"); // Halts if not found or directory error
print(`Found backup directory: ${latest_backup_dir}`);
```
---
### `find_dirs(dir, dirname)`
Find multiple directories in a parent directory (recursive, with support for wildcards).
- **Description**: Recursively searches for all directories matching `dirname` within the specified `dir` and its subdirectories. Supports simple wildcards. Halts script execution if the directory cannot be read.
- **Returns**: `Array` of `String` - An array containing paths to all matching directories.
- **Arguments**:
- `dir`: `String` - The directory to start the recursive search from.
- `dirname`: `String` - The directory name pattern to search for (e.g., `"project_*_v?"`).
```rhai
let project_versions = os::find_dirs("/home/user/dev", "project_*_v?"); // Halts on directory error
print("Found project version directories:");
for dir in project_versions {
print(`- ${dir}`);
}
```
---
### `delete(path)`
Delete a file or directory (defensive - doesn't error if file doesn't exist).
- **Description**: Deletes the file or directory at the given path. If the path does not exist, the function does nothing and does not halt. Halts script execution on other errors (e.g., permission denied, directory not empty). Returns the path that was attempted to be deleted.
- **Returns**: `String` - The path that was given as input.
- **Arguments**:
- `path`: `String` - The path to the file or directory to delete.
```rhai
let temp_path = "/tmp/temporary_item";
print(`Attempting to delete: ${temp_path}`);
os::delete(temp_path); // Halts on permissions or non-empty directory error
print("Deletion attempt finished.");
```
---
### `mkdir(path)`
Create a directory and all parent directories (defensive - doesn't error if directory exists).
- **Description**: Creates the directory at the given path, including any necessary parent directories. If the directory already exists, the function does nothing and does not halt. Halts script execution on other errors (e.g., permission denied). Returns the path that was created (or already existed).
- **Returns**: `String` - The path that was created or checked.
- **Arguments**:
- `path`: `String` - The path to the directory to create.
```rhai
let new_dir = "/data/processed/reports";
print(`Ensuring directory exists: ${new_dir}`);
os::mkdir(new_dir); // Halts on permission error
print("Directory check/creation finished.");
```
---
### `file_size(path)`
Get the size of a file in bytes.
- **Description**: Returns the size of the file at the given path. Halts script execution if the file does not exist or cannot be accessed.
- **Returns**: `Integer` - The size of the file in bytes (as i64).
- **Arguments**:
- `path`: `String` - The path to the file.
```rhai
let file_path = "important_document.pdf";
let size = os::file_size(file_path); // Halts if file not found or cannot read
print(`File size: ${size} bytes`);
```
---
### `rsync(src, dest)`
Sync directories using rsync (or platform equivalent).
- **Description**: Synchronizes the contents of the source directory (`src`) to the destination directory (`dest`) using the system's available rsync-like command. Halts script execution on any error during the sync process. Returns a success message string.
- **Returns**: `String` - A success message indicating the operation completed.
- **Arguments**:
- `src`: `String` - The source directory.
- `dest`: `String` - The destination directory.
```rhai
let source = "/local/project_files";
let destination = "/remote/backup/project_files";
print(`Syncing from ${source} to ${destination}...`);
let result_message = os::rsync(source, destination); // Halts on error
print(`Sync successful: ${result_message}`);
```
---
### `chdir(path)`
Change the current working directory.
- **Description**: Changes the current working directory of the script process. Halts script execution if the directory does not exist or cannot be accessed. Returns the new current working directory path.
- **Returns**: `String` - The absolute path of the directory the process changed into.
- **Arguments**:
- `path`: `String` - The path to change the working directory to.
```rhai
print(`Current directory: ${os::chdir(".")}`); // Use "." to get current path
let new_cwd = "/tmp";
os::chdir(new_cwd); // Halts if directory not found or access denied
print(`Changed directory to: ${os::chdir(".")}`);
```
---
### `file_read(path)`
Read the contents of a file.
- **Description**: Reads the entire content of the file at the given path into a string. Halts script execution if the file does not exist or cannot be read.
- **Returns**: `String` - The content of the file.
- **Arguments**:
- `path`: `String` - The path to the file.
```rhai
let config_content = os::file_read("settings.conf"); // Halts if file not found or cannot read
print("Config content:");
print(config_content);
```
---
### `file_write(path, content)`
Write content to a file (creates the file if it doesn\'t exist, overwrites if it does).
- **Description**: Writes the specified `content` to the file at the given `path`. If the file exists, its content is replaced. If it doesn't exist, it is created. Halts script execution on error (e.g., permission denied, invalid path). Returns the path written to.
- **Returns**: `String` - The path of the file written to.
- **Arguments**:
- `path`: `String` - The path to the file.
- `content`: `String` - The content to write to the file.
```rhai
let output_path = "/tmp/hello.txt";
let text_to_write = "Hello from Rhai!";
os::file_write(output_path, text_to_write); // Halts on error
print(`Wrote to ${output_path}`);
```
---
### `file_write_append(path, content)`
Append content to a file (creates the file if it doesn\'t exist).
- **Description**: Appends the specified `content` to the end of the file at the given `path`. If the file does not exist, it is created. Halts script execution on error (e.g., permission denied, invalid path). Returns the path written to.
- **Returns**: `String` - The path of the file written to.
- **Arguments**:
- `path`: `String` - The path to the file.
- `content`: `String` - The content to append to the file.
```rhai
let log_path = "application.log";
let log_entry = "User login failed.\n";
os::file_write_append(log_path, log_entry); // Halts on error
print(`Appended to ${log_path}`);
```
---
### `mv(src, dest)`
Move a file or directory from source to destination.
- **Description**: Moves the file or directory from `src` to `dest`. Halts script execution on error (e.g., permission denied, source not found, destination exists and cannot be overwritten). Returns the destination path.
- **Returns**: `String` - The path of the destination.
- **Arguments**:
- `src`: `String` - The path to the source file or directory.
- `dest`: `String` - The path to the destination.
```rhai
let old_path = "/tmp/report.csv";
let new_path = "/archive/reports/report_final.csv";
os::mv(old_path, new_path); // Halts on error
print(`Moved ${old_path} to ${new_path}`);
```
---
### `which(command)`
Check if a command exists in the system PATH.
- **Description**: Searches the system's PATH environment variable for the executable `command`. This function does NOT halt on error; it returns an empty string if the command is not found.
- **Returns**: `String` - The full path to the command executable if found, otherwise an empty string (`""`).
- **Arguments**:
- `command`: `String` - The name of the command to search for (e.g., `"git"`).
```rhai
let git_path = os::which("git");
if git_path != "" {
print(`Git executable found at: ${git_path}`);
} else {
print("Git executable not found in PATH.");
}
```
---
### `cmd_ensure_exists(commands)`
Ensure that one or more commands exist in the system PATH.
- **Description**: Checks if all command names specified in the `commands` string (space or comma separated) exist in the system's PATH. Halts script execution if any of the commands are not found. Returns a success message if all commands are found.
- **Returns**: `String` - A success message.
- **Arguments**:
- `commands`: `String` - A string containing one or more command names, separated by spaces or commas (e.g., `"curl,tar,unzip"`).
```rhai
print("Ensuring required commands are available...");
os::cmd_ensure_exists("git curl docker"); // Halts if any command is missing
print("All required commands found.");
```
---
### `chmod_exec(path)`
Make a file executable (equivalent to chmod +x).
- **Description**: Sets the executable permission for the file at the given `path` for the owner, group, and others. Halts script execution on error (e.g., file not found, permission denied). Returns the path modified.
- **Returns**: `String` - The path of the file whose permissions were modified.
- **Arguments**:
- `path`: `String` - The path to the file.
```rhai
let script_path = "/usr/local/bin/myscript";
print(`Making ${script_path} executable...`);
os::chmod_exec(script_path); // Halts on error
print("Permissions updated.");
```

157
docs/os/package.md Normal file
View File

@ -0,0 +1,157 @@
# os.package Module
### `package_install(package)`
Install a package using the system package manager.
- **Description**: Installs the specified `package` using the detected system package manager (e.g., `apt` on Ubuntu, `brew` on MacOS). Halts script execution if the package manager command fails. Returns a success message.
- **Returns**: `String` - A message indicating successful installation.
- **Arguments**:
- `package`: `String` - The name of the package to install (e.g., `"nano"`).
```rhai
print("Installing 'nano' package...");
os::package_install("nano"); // Halts on package manager error
print("'nano' installed successfully.");
```
---
### `package_remove(package)`
Remove a package using the system package manager.
- **Description**: Removes the specified `package` using the detected system package manager. Halts script execution if the package manager command fails. Returns a success message.
- **Returns**: `String` - A message indicating successful removal.
- **Arguments**:
- `package`: `String` - The name of the package to remove (e.g., `"htop"`).
```rhai
print("Removing 'htop' package...");
os::package_remove("htop"); // Halts on package manager error
print("'htop' removed successfully.");
```
---
### `package_update()`
Update package lists using the system package manager.
- **Description**: Updates the package lists that the system package manager uses (e.g., `apt update`, `brew update`). Halts script execution if the package manager command fails. Returns a success message.
- **Returns**: `String` - A message indicating successful update.
- **Arguments**: None.
```rhai
print("Updating package lists...");
os::package_update(); // Halts on package manager error
print("Package lists updated.");
```
---
### `package_upgrade()`
Upgrade installed packages using the system package manager.
- **Description**: Upgrades installed packages using the detected system package manager (e.g., `apt upgrade`, `brew upgrade`). Halts script execution if the package manager command fails. Returns a success message.
- **Returns**: `String` - A message indicating successful upgrade.
- **Arguments**: None.
```rhai
print("Upgrading installed packages...");
os::package_upgrade(); // Halts on package manager error
print("Packages upgraded.");
```
---
### `package_list()`
List installed packages using the system package manager.
- **Description**: Lists the names of packages installed on the system using the detected package manager. Halts script execution if the package manager command fails.
- **Returns**: `Array` of `String` - An array containing the names of installed packages.
- **Arguments**: None.
```rhai
print("Listing installed packages...");
let installed_packages = os::package_list(); // Halts on package manager error
for pkg in installed_packages {
print(`- ${pkg}`);
}
```
---
### `package_search(query)`
Search for packages using the system package manager.
- **Description**: Searches for packages matching the given `query` using the detected system package manager. Halts script execution if the package manager command fails.
- **Returns**: `Array` of `String` - An array containing the search results (package names and/or descriptions).
- **Arguments**:
- `query`: `String` - The search term.
```rhai
print("Searching for 'python' packages...");
let python_packages = os::package_search("python"); // Halts on package manager error
for pkg in python_packages {
print(`- ${pkg}`);
}
```
---
### `package_is_installed(package)`
Check if a package is installed using the system package manager.
- **Description**: Checks if the specified `package` is installed using the detected system package manager. Halts script execution if the package manager command itself fails (e.g., command not found), but does NOT halt if the package is simply not found.
- **Returns**: `Boolean` - `true` if the package is installed, `false` otherwise.
- **Arguments**:
- `package`: `String` - The name of the package to check (e.g., `"wget"`).
```rhai
let package_name = "wget";
if os::package_is_installed(package_name) { // Halts on package manager command error
print(`${package_name} is installed.`);
} else {
print(`${package_name} is not installed.`);
}
```
---
### `package_set_debug(debug)`
Set the debug mode for package management operations.
- **Description**: Enables or disables debug output for subsequent package management operations. This function does NOT halt on error and always returns the boolean value it was set to.
- **Returns**: `Boolean` - The boolean value that the debug flag was set to.
- **Arguments**:
- `debug`: `Boolean` - Set to `true` to enable debug output, `false` to disable.
```rhai
print("Enabling package debug output.");
os::package_set_debug(true);
// Subsequent package operations will print debug info
print("Disabling package debug output.");
os::package_set_debug(false);
```
---
### `package_platform()`
Get the current platform name for package management.
- **Description**: Returns the name of the operating system platform as detected by the package manager logic. This function does NOT halt on error; it returns `"Unknown"` if the platform cannot be determined.
- **Returns**: `String` - The platform name, one of `"Ubuntu"`, `"MacOS"`, or `"Unknown"`.
- **Arguments**: None.
```rhai
let platform = os::package_platform(); // Does not halt on error
print(`Detected package platform: ${platform}`);
```

223
docs/process/process.md Normal file
View File

@ -0,0 +1,223 @@
# Process Module
The `process` module provides functions for running external commands and managing system processes using a builder pattern for command execution.
For running commands, you start with the `run()` function which returns a `CommandBuilder` object. You can then chain configuration methods like `silent()`, `ignore_error()`, and `log()` before finally calling the `do()` method to execute the command.
By default, command execution using the builder (`.do()`) will halt the script execution if the command itself fails (returns a non-zero exit code) or if there's an operating system error preventing the command from running. You can change this behavior with `ignore_error()`.
Other process management functions (`which`, `kill`, `process_list`, `process_get`) have specific error handling behaviors described below.
---
### `CommandResult`
An object returned by command execution functions (`.do()`) containing the result of the command.
- **Properties**:
- `stdout`: `String` - The standard output of the command.
- `stderr`: `String` - The standard error of the command.
- `success`: `Boolean` - `true` if the command exited with code 0, `false` otherwise.
- `code`: `Integer` - The exit code of the command.
```rhai
let result = run("echo hi").do();
print(`Success: ${result.success}, Output: ${result.stdout}`);
```
---
### `ProcessInfo`
An object found by process listing/getting functions (`process_list`, `process_get`) containing information about a running process.
- **Properties**:
- `pid`: `Integer` - The process ID.
- `name`: `String` - The name of the process executable.
- `memory`: `Integer` - The memory usage of the process (unit depends on the operating system, typically KB or bytes).
- `cpu`: `Float` - The CPU usage percentage (value and meaning may vary by operating system).
```rhai
let processes = process_list("my_service");
if (processes.len() > 0) {
let first_proc = processes[0];
print(`Process ${first_proc.name} (PID: ${first_proc.pid}) is running.`);
}
```
---
### `run(command)`
Start building a command execution.
- **Description**: Initializes a `CommandBuilder` for the given command string. This is the entry point to configure and run a process.
- **Returns**: `CommandBuilder` - A builder object for configuring the command.
- **Arguments**:
- `command`: `String` - The command string to execute. Can include arguments and be a simple multiline script.
```rhai
let cmd_builder = run("ls -l");
// Now you can chain methods like .silent(), .ignore_error(), .log()
```
---
### `CommandBuilder:silent()`
Configure the command to run silently.
- **Description**: Suppresses real-time standard output and standard error from being printed to the script's console during command execution. The output is still captured in the resulting `CommandResult`.
- **Returns**: `CommandBuilder` - Returns `self` for chaining.
- **Arguments**: None.
```rhai
print("Running silent command...");
run("echo This won\'t show directly").silent().do();
print("Silent command finished.");
```
---
### `CommandBuilder:ignore_error()`
Configure the command to ignore non-zero exit codes.
- **Description**: By default, the `do()` method halts script execution if the command returns a non-zero exit code. Calling `ignore_error()` prevents this. The `CommandResult` will still indicate `success: false` and contain the non-zero `code`, allowing the script to handle the command failure explicitly. OS errors preventing the command from running will still cause a halt.
- **Returns**: `CommandBuilder` - Returns `self` for chaining.
- **Arguments**: None.
```rhai
print("Running command that will fail but not halt...");
let result = run("exit 1").ignore_error().do(); // Will not halt
if (!result.success) {
print(`Command failed as expected with code: ${result.code}`);
}
```
---
### `CommandBuilder:log()`
Configure the command to log the execution details.
- **Description**: Enables logging of the command string before execution.
- **Returns**: `CommandBuilder` - Returns `self` for chaining.
- **Arguments**: None.
```rhai
print("Running command with logging...");
run("ls /tmp").log().do(); // Will print the "ls /tmp" command before running
print("Command finished.");
```
---
### `CommandBuilder:do()`
Execute the configured command.
- **Description**: Runs the command with the options set by the builder methods. Waits for the command to complete and returns the `CommandResult`. This method is the final step in the command execution builder chain. Halts based on the `ignore_error()` setting and OS errors.
- **Returns**: `CommandResult` - An object containing the output and status of the command.
- **Arguments**: None.
```rhai
print("Running command using builder...");
let command_result = run("pwd")
.log() // Log the command
.silent() // Don't print output live
.do(); // Execute and get result (halts on error by default)
print(`Command output: ${command_result.stdout}`);
// Example with multiple options
let fail_result = run("command_that_does_not_exist")
.ignore_error() // Don't halt on non-zero exit (though OS error might still halt)
.silent() // Don't print error live
.do();
if (!fail_result.success) {
print(`Failed command exited with code: ${fail_result.code} and stderr: ${fail_result.stderr}`);
}
```
---
### `which(cmd)`
Check if a command exists in the system PATH.
- **Description**: Searches the system's PATH environment variable for the executable `cmd`. This function does NOT halt if the command is not found; it returns an empty string.
- **Returns**: `String` - The full path to the command executable if found, otherwise an empty string (`""`).
- **Arguments**:
- `cmd`: `String` - The name of the command to search for (e.g., `"node"`).
```rhai
let node_path = which("node"); // Does not halt if node is not found
if (node_path != "") {
print(`Node executable found at: ${node_path}`);
} else {
print("Node executable not found in PATH.");
}
```
---
### `kill(pattern)`
Kill processes matching a pattern.
- **Description**: Terminates running processes whose names match the provided `pattern`. Uses platform-specific commands (like `pkill` or equivalent). Halts script execution on error interacting with the system process list or kill command.
- **Returns**: `String` - A success message indicating the kill attempt finished.
- **Arguments**:
- `pattern`: `String` - A pattern to match against process names (e.g., `"nginx"`).
```rhai
print("Attempting to kill processes matching 'my_service'...");
// Use with caution!
kill("my_service"); // Halts on OS error during kill attempt
print("Kill command sent.");
```
---
### `process_list(pattern)`
List processes matching a pattern (or all if pattern is empty).
- **Description**: Lists information about running processes whose names match the provided `pattern`. If `pattern` is an empty string `""`, lists all processes. Halts script execution on error interacting with the system process list. Returns an empty array if no processes match the pattern.
- **Returns**: `Array` of `ProcessInfo` - An array of objects, each containing `pid` (Integer), `name` (String), `memory` (Integer), and `cpu` (Float).
- **Arguments**:
- `pattern`: `String` - A pattern to match against process names, or `""` for all processes.
```rhai
print("Listing processes matching 'bash'...");
let bash_processes = process_list("bash"); // Halts on OS error
if (bash_processes.len() > 0) {
print("Found bash processes:");
for proc in bash_processes {
print(`- PID: ${proc.pid}, Name: ${proc.name}, CPU: ${proc.cpu}%, Memory: ${proc.memory}`);
}
} else {
print("No bash processes found.");
}
```
---
### `process_get(pattern)`
Get a single process matching the pattern (error if 0 or more than 1 match).
- **Description**: Finds exactly one running process whose name matches the provided `pattern`. Halts script execution if zero or more than one process matches the pattern, or on error interacting with the system process list.
- **Returns**: `ProcessInfo` - An object containing `pid` (Integer), `name` (String), `memory` (Integer), and `cpu` (Float).
- **Arguments**:
- `pattern`: `String` - A pattern to match against process names, expected to match exactly one process.
```rhai
let expected_service_name = "my_critical_service";
print(`Getting process info for '${expected_service_name}'...`);
// This will halt if the service isn't running, or if multiple services have this name
let service_proc_info = process_get(expected_service_name);
print(`Found process: PID ${service_proc_info.pid}, Name: ${service_proc_info.name}`);
```

View File

@ -1,5 +1,3 @@
// 05_directory_operations.rhai
// Demonstrates directory operations using SAL, including the new chdir function
// Create a test directory structure
let base_dir = "rhai_dir_test";
@ -81,4 +79,4 @@ println(`Directory change result: ${chdir_parent_result}`);
// delete(base_dir);
// println("Cleanup complete");
"Directory operations script completed successfully!"
"Directory operations script completed successfully!"

View File

@ -0,0 +1,28 @@
print("Caution: Use the kill() function with extreme care as it can terminate running applications.");
print("Terminating essential system processes can make your system unstable or unusable.");
print("");
print("This example attempts to kill processes matching a specific name.");
print("Replace 'process_name_to_kill' with the actual name of a process you intend to stop.");
print("Make sure you know what the process does before attempting to kill it.");
print("");
let target_process_name = "process_name_to_kill"; // <--- CHANGE THIS TO A REAL PROCESS NAME (e.g., "sleep" if you start a sleep process)
print(`Attempting to kill processes matching pattern: '${target_process_name}'...`);
// To safely test this, you might want to start a simple process first, like 'sleep 60 &'.
// Then replace 'process_name_to_kill' with 'sleep'.
// Uncomment the line below to execute the kill command.
// let result_message = kill(target_process_name); // Halts on OS error during kill attempt
// if result_message != "" {
// print(`Kill command sent. Result: ${result_message}`);
// } else {
// print("Kill command finished, but no message returned (check for errors above).");
// }
print("");
print("kill() example finished (command was commented out for safety).");
print("Uncomment the 'kill(...)' line to make it active.");

View File

@ -0,0 +1,39 @@
print("Getting a single process using process_get()...\n");
// process_get expects *exactly one* process matching the pattern.
// If zero or more than one processes match, it will halt script execution.
// Example: Get information for a specific process name.
// Replace "my_critical_service" with a name that is likely to match
// exactly one running process on your system.
// Common examples might be "Dock" or "Finder" on macOS,
// "explorer.exe" on Windows, or a specific service name on Linux.
let target_process_name = "process_name_to_get"; // <--- CHANGE THIS TO A REAL, UNIQUE PROCESS NAME
print(`Attempting to get info for process matching pattern: '${target_process_name}'...`);
// This line will halt if the process is not found OR if multiple processes match the name.
// It will only proceed if exactly one process is found.
let service_proc_info = process_get(target_process_name); // Halts on 0 or >1 matches, or OS error
print(`Successfully found exactly one process matching '${target_process_name}':`);
// Access properties of the ProcessInfo object
print(`- PID: ${service_proc_info.pid}`);
print(`- Name: ${service_proc_info.name}`);
print(`- CPU: ${service_proc_info.cpu}%`);
print(`- Memory: ${service_proc_info.memory}`);
// To demonstrate the halting behavior, you could uncomment one of these:
// Example that will halt if "nonexistent_process_xyz" is not running:
// print("\nAttempting to get a nonexistent process (will halt if not found)...");
// let nonexistent_proc = process_get("nonexistent_process_xyz"); // This line likely halts
// Example that might halt if "sh" matches multiple processes:
// print("\nAttempting to get 'sh' (might halt if multiple shell processes exist)...");
// let sh_proc = process_get("sh"); // This line might halt depending on your system processes
print("\nprocess_get() example finished (if the script did not halt above).");

View File

@ -0,0 +1,29 @@
print("Listing processes using process_list()...\n");
// Example: List all processes (use empty string as pattern)
// print("Listing all running processes (this might be a long list!)...\n");
// let all_processes = process_list("");
// print(`Found ${all_processes.len()} total processes.`);
// // Optional: print details for a few processes
// for i in 0..min(all_processes.len(), 5) {
// let proc = all_processes[i];
// print(`- PID: ${proc.pid}, Name: ${proc.name}, CPU: ${proc.cpu}%, Memory: ${proc.memory}`);
// }
print("Listing processes matching 'bash'...\n");
// Example: List processes matching a pattern
let pattern_to_list = "bash"; // Or another common process like "SystemSettings" or "Finder" on macOS, "explorer.exe" on Windows, "systemd" on Linux
let matching_processes = process_list(pattern_to_list); // Halts on OS error during list attempt
if (matching_processes.len() > 0) {
print(`Found ${matching_processes.len()} processes matching '${pattern_to_list}':`);
for proc in matching_processes {
// Access properties of the ProcessInfo object
print(`- PID: ${proc.pid}, Name: ${proc.name}, CPU: ${proc.cpu}%, Memory: ${proc.memory}`);
}
} else {
print(`No processes found matching '${pattern_to_list}'.`);
}
print("\nprocess_list() example finished.");

View File

@ -0,0 +1,36 @@
print("Running a command using multiple builder options...");
// Example combining log, silent, and ignore_error
// This command will:
// 1. Be logged before execution (.log())
// 2. Have its output suppressed during execution (.silent())
// 3. Exit with a non-zero code (fail)
// 4. NOT halt the script execution because .ignore_error() is used
let result = run("echo 'This is logged and silent stdout'; echo 'This is logged and silent stderr' >&2; exit 5")
.log() // Log the command string
.silent() // Suppress real-time output
.ignore_error() // Prevent script halt on non-zero exit code
.execute(); // Execute the command
print("Command execution finished.");
// Print the captured result
print(`Success: ${result.success}`); // Should be false
print(`Exit Code: ${result.code}`); // Should be 5
print(`Captured Stdout:\n${result.stdout}`); // Should contain the stdout string
// The script continues execution because ignore_error() was used
print("Script continues after handling the failed command.");
// Another example with a successful command, still silent and logged
print("\nRunning another command (successful)...");
let success_result = run("echo 'Success message'").log().silent().execute();
print(`Command finished.`);
print(`Success: ${success_result.success}`); // Should be true
print(`Exit Code: ${success_result.code}`); // Should be 0
print(`Captured Stdout:\n${success_result.stdout}`);
print("\nrun().execute() all options example finished.");

View File

@ -0,0 +1,18 @@
print("Running a basic command using run().do()...");
// Execute a simple command
let result = run("echo Hello from run_basic!").do();
// Print the command result
print(`Command: echo Hello from run_basic!`);
print(`Success: ${result.success}`);
print(`Exit Code: ${result.code}`);
print(`Stdout:\n${result.stdout}`);
print(`Stderr:\n${result.stderr}`);
// Example of a command that might fail (if 'nonexistent_command' doesn't exist)
// This will halt execution by default because ignore_error() is not used.
// print("Running a command that will fail (and should halt)...");
// let fail_result = run("nonexistent_command").do(); // This line will cause the script to halt if the command doesn't exist
print("Basic run() example finished.");

View File

@ -0,0 +1,29 @@
print("Running a command that will fail, but ignoring the error...");
// Run a command that exits with a non-zero code (will fail)
// Using .ignore_error() prevents the script from halting
let result = run("exit 1").ignore_error().do();
print(`Command finished.`);
print(`Success: ${result.success}`); // This should be false
print(`Exit Code: ${result.code}`); // This should be 1
// We can now handle the failure in the script
if (!result.success) {
print("Command failed, but we handled it because ignore_error() was used.");
// Optionally print stderr if needed
// print(`Stderr:\\n${result.stderr}`);
} else {
print("Command unexpectedly succeeded.");
}
print("\nScript continued execution after the potentially failing command.");
// Example of a command that might fail due to OS error (e.g., command not found)
// This *might* still halt depending on how the underlying Rust function handles it,
// as ignore_error() primarily prevents halting on *command* non-zero exit codes.
// let os_error_result = run("nonexistent_command_123").ignore_error().do();
// print(`OS Error Command Success: ${os_error_result.success}`);
// print(`OS Error Command Exit Code: ${os_error_result.code}`);
print("ignore_error() example finished.");

View File

@ -0,0 +1,13 @@
print("Running a command using run().log().do()...");
// The .log() method will print the command string to the console before execution.
// This is useful for debugging or tracing which commands are being run.
let result = run("echo This command is logged").log().do();
print(`Command finished.`);
print(`Success: ${result.success}`);
print(`Exit Code: ${result.code}`);
print(`Stdout:\n${result.stdout}`);
print(`Stderr:\n${result.stderr}`);
print("run().log() example finished.");

View File

@ -0,0 +1,22 @@
print("Running a command using run().silent().do()...\n");
// This command will print to standard output and standard error
// However, because .silent() is used, the output will not appear in the console directly
let result = run("echo 'This should be silent stdout.'; echo 'This should be silent stderr.' >&2; exit 0").silent().do();
// The output is still captured in the CommandResult
print(`Command finished.`);
print(`Success: ${result.success}`);
print(`Exit Code: ${result.code}`);
print(`Captured Stdout:\\n${result.stdout}`);
print(`Captured Stderr:\\n${result.stderr}`);
// Example of a silent command that fails (but won't halt because we only suppress output)
// let fail_result = run("echo 'This is silent failure stderr.' >&2; exit 1").silent().do();
// print(`Failed command finished (silent):`);
// print(`Success: ${fail_result.success}`);
// print(`Exit Code: ${fail_result.code}`);
// print(`Captured Stdout:\\n${fail_result.stdout}`);
// print(`Captured Stderr:\\n${fail_result.stderr}`);
print("\nrun().silent() example finished.");

View File

@ -0,0 +1,25 @@
print("Checking if a command exists in the system PATH using which()...\n");
// Check for a command that likely exists (e.g., 'node' or 'git')
let command_name_exists = "node";
let command_path_exists = which(command_name_exists);
if (command_path_exists != "") {
print(`'${command_name_exists}' executable found at: ${command_path_exists}`);
} else {
print(`'${command_name_exists}' executable not found in PATH.`);
}
print("\nChecking for a command that likely does NOT exist...");
// Check for a command that likely does not exist
let command_name_nonexistent = "nonexistent_command_abc_123";
let command_path_nonexistent = which(command_name_nonexistent);
if (command_path_nonexistent != "") {
print(`'${command_name_nonexistent}' executable found at: ${command_path_nonexistent}`);
} else {
print(`'${command_name_nonexistent}' executable not found in PATH.`);
}
print("\nwhich() example finished.");

View File

@ -1,5 +0,0 @@
Initial content - line 1
Initial content - line 2
Appended content - line 3
Appended content - line 4
Log entry #1 at \nLog entry #2 at \nLog entry #3 at \n

View File

@ -1,2 +0,0 @@
This is the first line of text.
This is the second line of text.

View File

@ -1,474 +0,0 @@
# RFS Wrapper Implementation Plan
## Overview
We'll create a Rust wrapper for the RFS (Remote File System) tool that follows the builder pattern, similar to the existing implementations for buildah and nerdctl in the codebase. This wrapper will provide a fluent API for mounting, unmounting, listing mounts, configuring mount options, and packing directories into filesystem layers.
## Module Structure
```
src/virt/rfs/
├── mod.rs # Module exports and common types
├── cmd.rs # Command execution functions
├── mount.rs # Mount operations
├── pack.rs # Packing operations
├── builder.rs # Builder pattern implementation
├── types.rs # Type definitions
└── error.rs # Error handling
```
## Implementation Details
### 1. Error Handling
```rust
// error.rs
#[derive(Debug)]
pub enum RfsError {
CommandFailed(String),
InvalidArgument(String),
MountFailed(String),
UnmountFailed(String),
ListFailed(String),
PackFailed(String),
Other(String),
}
impl std::fmt::Display for RfsError {
// Implementation
}
impl std::error::Error for RfsError {
// Implementation
}
```
### 2. Command Execution
```rust
// cmd.rs
use crate::process::{run_command, CommandResult};
use super::error::RfsError;
pub fn execute_rfs_command(args: &[&str]) -> Result<CommandResult, RfsError> {
// Implementation similar to buildah and nerdctl
}
```
### 3. Types
```rust
// types.rs
#[derive(Debug, Clone)]
pub struct Mount {
pub id: String,
pub source: String,
pub target: String,
pub fs_type: String,
pub options: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum MountType {
Local,
SSH,
S3,
WebDAV,
// Other mount types
}
#[derive(Debug, Clone)]
pub struct StoreSpec {
pub spec_type: String,
pub options: std::collections::HashMap<String, String>,
}
impl StoreSpec {
pub fn new(spec_type: &str) -> Self {
Self {
spec_type: spec_type.to_string(),
options: std::collections::HashMap::new(),
}
}
pub fn with_option(mut self, key: &str, value: &str) -> Self {
self.options.insert(key.to_string(), value.to_string());
self
}
pub fn to_string(&self) -> String {
let mut result = self.spec_type.clone();
if !self.options.is_empty() {
result.push_str(":");
let options: Vec<String> = self.options
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect();
result.push_str(&options.join(","));
}
result
}
}
```
### 4. Builder Pattern
```rust
// builder.rs
use std::collections::HashMap;
use super::{Mount, MountType, RfsError, execute_rfs_command, StoreSpec};
#[derive(Clone)]
pub struct RfsBuilder {
source: String,
target: String,
mount_type: MountType,
options: HashMap<String, String>,
mount_id: Option<String>,
debug: bool,
}
impl RfsBuilder {
pub fn new(source: &str, target: &str, mount_type: MountType) -> Self {
Self {
source: source.to_string(),
target: target.to_string(),
mount_type,
options: HashMap::new(),
mount_id: None,
debug: false,
}
}
pub fn with_option(mut self, key: &str, value: &str) -> Self {
self.options.insert(key.to_string(), value.to_string());
self
}
pub fn with_options(mut self, options: HashMap<&str, &str>) -> Self {
for (key, value) in options {
self.options.insert(key.to_string(), value.to_string());
}
self
}
pub fn with_debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
pub fn mount(self) -> Result<Mount, RfsError> {
// Implementation
}
pub fn unmount(&self) -> Result<(), RfsError> {
// Implementation
}
// Other methods
}
// Packing functionality
pub struct PackBuilder {
directory: String,
output: String,
store_specs: Vec<StoreSpec>,
debug: bool,
}
impl PackBuilder {
pub fn new(directory: &str, output: &str) -> Self {
Self {
directory: directory.to_string(),
output: output.to_string(),
store_specs: Vec::new(),
debug: false,
}
}
pub fn with_store_spec(mut self, store_spec: StoreSpec) -> Self {
self.store_specs.push(store_spec);
self
}
pub fn with_store_specs(mut self, store_specs: Vec<StoreSpec>) -> Self {
self.store_specs.extend(store_specs);
self
}
pub fn with_debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
pub fn pack(self) -> Result<(), RfsError> {
// Implementation for packing a directory into a filesystem layer
let mut args = vec!["pack"];
// Add output file
args.push("-m");
args.push(&self.output);
// Add store specs
if !self.store_specs.is_empty() {
args.push("-s");
let specs: Vec<String> = self.store_specs
.iter()
.map(|spec| spec.to_string())
.collect();
args.push(&specs.join(","));
}
// Add directory
args.push(&self.directory);
// Convert to string slices for the command
let args_str: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
// Execute the command
let result = execute_rfs_command(&args_str)?;
// Check for errors
if !result.success {
return Err(RfsError::PackFailed(result.stderr));
}
Ok(())
}
}
```
### 5. Mount Operations
```rust
// mount.rs
use super::{RfsBuilder, Mount, RfsError, execute_rfs_command};
pub fn list_mounts() -> Result<Vec<Mount>, RfsError> {
// Implementation
}
pub fn unmount_all() -> Result<(), RfsError> {
// Implementation
}
// Other mount-related functions
```
### 6. Pack Operations
```rust
// pack.rs
use super::{PackBuilder, StoreSpec, RfsError, execute_rfs_command};
pub fn pack_directory(directory: &str, output: &str, store_specs: &[StoreSpec]) -> Result<(), RfsError> {
PackBuilder::new(directory, output)
.with_store_specs(store_specs.to_vec())
.pack()
}
// Other pack-related functions
```
### 7. Module Exports
```rust
// mod.rs
mod cmd;
mod error;
mod mount;
mod pack;
mod builder;
mod types;
pub use error::RfsError;
pub use builder::{RfsBuilder, PackBuilder};
pub use types::{Mount, MountType, StoreSpec};
pub use mount::{list_mounts, unmount_all};
pub use pack::pack_directory;
// Re-export the execute_rfs_command function for use in other modules
pub(crate) use cmd::execute_rfs_command;
```
## Usage Examples
### Mounting Example
```rust
use crate::virt::rfs::{RfsBuilder, MountType};
// Create a new RFS mount with builder pattern
let mount = RfsBuilder::new("user@example.com:/remote/path", "/local/mount/point", MountType::SSH)
.with_option("port", "2222")
.with_option("identity_file", "/path/to/key")
.with_debug(true)
.mount()?;
// List all mounts
let mounts = list_mounts()?;
for mount in mounts {
println!("Mount ID: {}, Source: {}, Target: {}", mount.id, mount.source, mount.target);
}
// Unmount
mount.unmount()?;
```
### Packing Example
```rust
use crate::virt::rfs::{PackBuilder, StoreSpec};
// Create store specifications
let store_spec1 = StoreSpec::new("file")
.with_option("path", "/path/to/store");
let store_spec2 = StoreSpec::new("s3")
.with_option("bucket", "my-bucket")
.with_option("region", "us-east-1");
// Pack a directory with builder pattern
let result = PackBuilder::new("/path/to/directory", "output.fl")
.with_store_spec(store_spec1)
.with_store_spec(store_spec2)
.with_debug(true)
.pack()?;
// Or use the convenience function
pack_directory("/path/to/directory", "output.fl", &[store_spec1, store_spec2])?;
```
## Rhai Integration
We'll also need to create a Rhai module to expose the RFS functionality to Rhai scripts:
```rust
// src/rhai/rfs.rs
use rhai::{Engine, EvalAltResult, RegisterFn};
use crate::virt::rfs::{RfsBuilder, MountType, list_mounts, unmount_all, PackBuilder, StoreSpec};
pub fn register(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register RFS functions
engine.register_fn("rfs_mount", rfs_mount);
engine.register_fn("rfs_unmount", rfs_unmount);
engine.register_fn("rfs_list_mounts", rfs_list_mounts);
engine.register_fn("rfs_unmount_all", rfs_unmount_all);
engine.register_fn("rfs_pack", rfs_pack);
Ok(())
}
// Function implementations
fn rfs_mount(source: &str, target: &str, mount_type: &str, options_map: rhai::Map) -> Result<(), Box<EvalAltResult>> {
// Implementation
}
fn rfs_unmount(target: &str) -> Result<(), Box<EvalAltResult>> {
// Implementation
}
fn rfs_list_mounts() -> Result<rhai::Array, Box<EvalAltResult>> {
// Implementation
}
fn rfs_unmount_all() -> Result<(), Box<EvalAltResult>> {
// Implementation
}
fn rfs_pack(directory: &str, output: &str, store_specs: &str) -> Result<(), Box<EvalAltResult>> {
// Implementation
}
```
## Implementation Flow
Here's a diagram showing the flow of the implementation:
```mermaid
classDiagram
class RfsBuilder {
+String source
+String target
+MountType mount_type
+HashMap options
+Option~String~ mount_id
+bool debug
+new(source, target, mount_type)
+with_option(key, value)
+with_options(options)
+with_debug(debug)
+mount()
+unmount()
}
class PackBuilder {
+String directory
+String output
+Vec~StoreSpec~ store_specs
+bool debug
+new(directory, output)
+with_store_spec(store_spec)
+with_store_specs(store_specs)
+with_debug(debug)
+pack()
}
class Mount {
+String id
+String source
+String target
+String fs_type
+Vec~String~ options
}
class MountType {
<<enumeration>>
Local
SSH
S3
WebDAV
}
class StoreSpec {
+String spec_type
+HashMap options
+new(spec_type)
+with_option(key, value)
+to_string()
}
class RfsError {
<<enumeration>>
CommandFailed
InvalidArgument
MountFailed
UnmountFailed
ListFailed
PackFailed
Other
}
RfsBuilder --> Mount : creates
RfsBuilder --> RfsError : may throw
RfsBuilder --> MountType : uses
PackBuilder --> RfsError : may throw
PackBuilder --> StoreSpec : uses
Mount --> RfsError : may throw
```
## Implementation Steps
1. Create the directory structure for the RFS module
2. Implement the error handling module
3. Implement the command execution module
4. Define the types for mounts, mount operations, and store specifications
5. Implement the builder pattern for RFS operations (mount and pack)
6. Implement the mount operations
7. Implement the pack operations
8. Create the module exports
9. Add Rhai integration
10. Write tests for the implementation
11. Update documentation

View File

@ -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

View File

@ -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

View File

@ -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
};

View File

@ -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
//

View File

@ -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"

View File

@ -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(())
}

View File

@ -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.