Compare commits

..

No commits in common. "main" and "development_maxime" have entirely different histories.

269 changed files with 1727 additions and 38209 deletions

View File

@ -1,73 +0,0 @@
name: Rhai Tests
on:
push:
branches: [ '*' ]
paths:
- 'src/rhai_tests/**'
- 'src/rhai/**'
- 'src/git/**'
- 'src/os/**'
- 'run_rhai_tests.sh'
- '.github/workflows/rhai-tests.yml'
pull_request:
branches: [ '*' ]
paths:
- 'src/rhai_tests/**'
- 'src/rhai/**'
- 'src/git/**'
- 'src/os/**'
- 'run_rhai_tests.sh'
- '.github/workflows/rhai-tests.yml'
workflow_dispatch: # Allow manual triggering
jobs:
rhai-tests:
name: Run Rhai Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Cache Rust dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Build herodo
run: |
cargo build --bin herodo
echo "${{ github.workspace }}/target/debug" >> $GITHUB_PATH
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y git curl
- name: Run Rhai tests
run: |
chmod +x run_rhai_tests.sh
./run_rhai_tests.sh
- name: Check for test failures
run: |
if grep -q "Some tests failed" run_rhai_tests.log; then
echo "::error::Some Rhai tests failed. Check the logs for details."
exit 1
else
echo "All Rhai tests passed!"
fi
if: always()

43
.gitignore vendored
View File

@ -19,46 +19,3 @@ Cargo.lock
# Added by cargo
/target
/rhai_test_template
/rhai_test_download
/rhai_test_fs
run_rhai_tests.log
new_location
log.txt
file.txt
fix_doc*
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
bun.lockb
bun.lock
yarn.lock
build.sh
build_dev.sh
develop.sh
docusaurus.config.ts
sidebars.ts
tsconfig.json

View File

@ -10,72 +10,26 @@ keywords = ["system", "os", "abstraction", "platform", "filesystem"]
categories = ["os", "filesystem", "api-bindings"]
readme = "README.md"
[workspace]
members = [".", "vault"]
[dependencies]
hex = "0.4"
anyhow = "1.0.98"
base64 = "0.22.1" # Base64 encoding/decoding
cfg-if = "1.0"
chacha20poly1305 = "0.10.1" # ChaCha20Poly1305 AEAD cipher
clap = "2.34.0" # Command-line argument parsing
dirs = "6.0.0" # Directory paths
env_logger = "0.11.8" # Logger implementation
ethers = { version = "2.0.7", features = ["legacy"] } # Ethereum library
glob = "0.3.1" # For file pattern matching
jsonrpsee = "0.25.1"
k256 = { version = "0.13.4", features = [
"ecdsa",
"ecdh",
] } # Elliptic curve cryptography
lazy_static = "1.4.0" # For lazy initialization of static variables
# Cross-platform functionality
libc = "0.2"
log = "0.4" # Logging facade
once_cell = "1.18.0" # Lazy static initialization
postgres = "0.19.4" # PostgreSQL client
postgres-types = "0.2.5" # PostgreSQL type conversions
r2d2 = "0.8.10"
r2d2_postgres = "0.18.2"
rand = "0.8.5" # Random number generation
redis = "0.31.0" # Redis client
regex = "1.8.1" # For regex pattern matching
rhai = { version = "1.12.0", features = ["sync"] } # Embedded scripting language
serde = { version = "1.0", features = [
"derive",
] } # For serialization/deserialization
cfg-if = "1.0"
thiserror = "1.0" # For error handling
redis = "0.22.0" # Redis client
lazy_static = "1.4.0" # For lazy initialization of static variables
regex = "1.8.1" # For regex pattern matching
serde = { version = "1.0", features = ["derive"] } # For serialization/deserialization
serde_json = "1.0" # For JSON handling
sha2 = "0.10.7" # SHA-2 hash functions
tempfile = "3.5" # For temporary file operations
tera = "1.19.0" # Template engine for text rendering
thiserror = "2.0.12" # For error handling
tokio = "1.45.0"
tokio-postgres = "0.7.8" # Async PostgreSQL client
tokio-test = "0.4.4"
uuid = { version = "1.16.0", features = ["v4"] }
zinit-client = { path = "/Users/timurgordon/code/github/threefoldtech/zinit/zinit-client" }
reqwest = { version = "0.12.15", features = ["json"] }
urlencoding = "2.1.3"
glob = "0.3.1" # For file pattern matching
tempfile = "3.5" # For temporary file operations
log = "0.4" # Logging facade
# Optional features for specific OS functionality
[target.'cfg(unix)'.dependencies]
nix = "0.30.1" # Unix-specific functionality
nix = "0.26" # Unix-specific functionality
[target.'cfg(windows)'.dependencies]
windows = { version = "0.61.1", features = [
"Win32_Foundation",
"Win32_System_Threading",
"Win32_Storage_FileSystem",
] }
windows = { version = "0.48", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_Storage_FileSystem"] }
[dev-dependencies]
mockall = "0.13.1" # For mocking in tests
tempfile = "3.5" # For tests that need temporary files/directories
tokio = { version = "1.28", features = [
"full",
"test-util",
] } # For async testing
[[bin]]
name = "herodo"
path = "src/bin/herodo.rs"
tempfile = "3.5" # For tests that need temporary files/directories

193
README.md
View File

@ -1,184 +1,73 @@
# SAL (System Abstraction Layer)
**Version: 0.1.0**
A Rust library that provides a unified interface for interacting with operating system features across different platforms. It abstracts away platform-specific details, allowing developers to write cross-platform code with ease.
SAL is a comprehensive Rust library designed to provide a unified and simplified interface for a wide array of system-level operations and interactions. It abstracts platform-specific details, enabling developers to write robust, cross-platform code with greater ease. SAL also includes `herodo`, a powerful command-line tool for executing Rhai scripts that leverage SAL's capabilities for automation and system management tasks.
## Features
## Core Features
- **File System Operations**: Simplified file and directory management
- **Process Management**: Create, monitor, and control processes
- **System Information**: Access system details and metrics
- **Git Integration**: Interface with Git repositories
- **Redis Client**: Robust Redis connection management and command execution
- **Text Processing**: Utilities for text manipulation and formatting
SAL offers a broad spectrum of functionalities, including:
## Modules
- **System Operations**: File and directory management, environment variable access, system information retrieval, and OS-specific commands.
- **Process Management**: Create, monitor, control, and interact with system processes.
- **Containerization Tools**:
- Integration with **Buildah** for building OCI/Docker-compatible container images.
- Integration with **nerdctl** for managing containers (run, stop, list, build, etc.).
- **Version Control**: Programmatic interaction with Git repositories (clone, commit, push, pull, status, etc.).
- **Database Clients**:
- **Redis**: Robust client for interacting with Redis servers.
- **PostgreSQL**: Client for executing queries and managing PostgreSQL databases.
- **Scripting Engine**: In-built support for the **Rhai** scripting language, allowing SAL functionalities to be scripted and automated, primarily through the `herodo` tool.
- **Networking & Services**:
- **Mycelium**: Tools for Mycelium network peer management and message passing.
- **Zinit**: Client for interacting with the Zinit process supervision system.
- **RFS (Remote/Virtual Filesystem)**: Mount, manage, pack, and unpack various types of filesystems (local, SSH, S3, WebDAV).
- **Text Processing**: A suite of utilities for text manipulation, formatting, and regular expressions.
- **Cryptography (`vault`)**: Functions for common cryptographic operations.
### Redis Client
## `herodo`: The SAL Scripting Tool
The Redis client module provides a robust wrapper around the Redis client library for Rust, offering:
`herodo` is a command-line utility bundled with SAL that executes Rhai scripts. It empowers users to automate tasks and orchestrate complex workflows by leveraging SAL's diverse modules directly from scripts.
- Automatic connection management and reconnection
- Support for both Unix socket and TCP connections
- Database selection via environment variables
- Thread-safe global client instance
- Simple command execution interface
### Usage
[View Redis Client Documentation](src/redisclient/README.md)
```bash
herodo -p <path_to_script.rhai>
# or
herodo -p <path_to_directory_with_scripts/>
```
### OS Module
If a directory is provided, `herodo` will execute all `.rhai` scripts within that directory (and its subdirectories) in alphabetical order.
Provides platform-independent interfaces for operating system functionality.
### Scriptable SAL Modules via `herodo`
### Git Module
The following SAL modules and functionalities are exposed to the Rhai scripting environment through `herodo`:
Tools for interacting with Git repositories programmatically.
- **OS (`os`)**: Comprehensive file system operations, file downloading & installation, and system package management. [Detailed OS Module Documentation](src/os/README.md)
- **Process (`process`)**: Robust command and script execution, plus process management (listing, finding, killing, checking command existence). [Detailed Process Module Documentation](src/process/README.md)
- **Buildah (`buildah`)**: OCI/Docker image building functions. [Detailed Buildah Module Documentation](src/virt/buildah/README.md)
- **nerdctl (`nerdctl`)**: Container lifecycle management (`nerdctl_run`, `nerdctl_stop`, `nerdctl_images`, `nerdctl_image_build`, etc.). [Detailed Nerdctl Module Documentation](src/virt/nerdctl/README.md)
- **Git (`git`)**: High-level repository management and generic Git command execution with Redis-backed authentication (clone, pull, push, commit, etc.). [Detailed Git Module Documentation](src/git/README.md)
- **Zinit (`zinit_client`)**: Client for Zinit process supervisor (service management, logs). [Detailed Zinit Client Module Documentation](src/zinit_client/README.md)
- **Mycelium (`mycelium`)**: Client for Mycelium decentralized networking API (node info, peer management, messaging). [Detailed Mycelium Module Documentation](src/mycelium/README.md)
- **Text (`text`)**: String manipulation, prefixing, path/name fixing, text replacement, and templating. [Detailed Text Module Documentation](src/text/README.md)
- **RFS (`rfs`)**: Mount various filesystems (local, SSH, S3, etc.), pack/unpack filesystem layers. [Detailed RFS Module Documentation](src/virt/rfs/README.md)
- **Cryptography (`crypto` from `vault`)**: Encryption, decryption, hashing, etc.
- **Redis Client (`redis`)**: Execute Redis commands (`redis_get`, `redis_set`, `redis_execute`, etc.).
- **PostgreSQL Client (`postgres`)**: Execute SQL queries against PostgreSQL databases.
### Process Module
### Example `herodo` Rhai Script
Utilities for process creation, monitoring, and management.
```rhai
// file: /opt/scripts/example_task.rhai
### Text Module
// OS operations
println("Checking for /tmp/my_app_data...");
if !exist("/tmp/my_app_data") {
mkdir("/tmp/my_app_data");
println("Created directory /tmp/my_app_data");
}
Text processing utilities for common operations.
// Redis operations
println("Setting Redis key 'app_status' to 'running'");
redis_set("app_status", "running");
let status = redis_get("app_status");
println("Current app_status from Redis: " + status);
## Usage
// Process execution
println("Listing files in /tmp:");
let output = run("ls -la /tmp");
println(output.stdout);
println("Script finished.");
```
Run with: `herodo -p /opt/scripts/example_task.rhai`
For more examples, check the `examples/` and `rhai_tests/` directories in this repository.
## Using SAL as a Rust Library
Add SAL as a dependency to your `Cargo.toml`:
Add this to your `Cargo.toml`:
```toml
[dependencies]
sal = "0.1.0" # Or the latest version
sal = "0.1.0"
```
### Rust Example: Using Redis Client
Basic example:
```rust
use sal::redisclient::{get_global_client, execute_cmd_with_args};
use redis::RedisResult;
use sal::redisclient::execute;
use redis::cmd;
async fn example_redis_interaction() -> RedisResult<()> {
// Get a connection from the global pool
let mut conn = get_global_client().await?.get_async_connection().await?;
fn main() -> redis::RedisResult<()> {
// Execute a Redis command
let mut cmd = redis::cmd("SET");
cmd.arg("example_key").arg("example_value");
execute(&mut cmd)?;
// Set a value
execute_cmd_with_args(&mut conn, "SET", vec!["my_key", "my_value"]).await?;
println!("Set 'my_key' to 'my_value'");
// Get a value
let value: String = execute_cmd_with_args(&mut conn, "GET", vec!["my_key"]).await?;
println!("Retrieved value for 'my_key': {}", value);
// Retrieve the value
let mut get_cmd = redis::cmd("GET");
get_cmd.arg("example_key");
let value: String = execute(&mut get_cmd)?;
println!("Value: {}", value);
Ok(())
}
#[tokio::main]
asynchronous fn main() {
if let Err(e) = example_redis_interaction().await {
eprintln!("Redis Error: {}", e);
}
}
```
*(Note: The Redis client API might have evolved; please refer to `src/redisclient/mod.rs` and its documentation for the most current usage.)*
## Modules Overview (Rust Library)
SAL is organized into several modules, each providing specific functionalities:
- **`sal::os`**: Core OS interactions, file system operations, environment access.
- **`sal::process`**: Process creation, management, and control.
- **`sal::git`**: Git repository management.
- **`sal::redisclient`**: Client for Redis database interactions. (See also `src/redisclient/README.md`)
- **`sal::postgresclient`**: Client for PostgreSQL database interactions.
- **`sal::rhai`**: Integration layer for the Rhai scripting engine, used by `herodo`.
- **`sal::text`**: Utilities for text processing and manipulation.
- **`sal::vault`**: Cryptographic functions.
- **`sal::virt`**: Virtualization-related utilities, including `rfs` for remote/virtual filesystems.
- **`sal::mycelium`**: Client for Mycelium network operations.
- **`sal::zinit_client`**: Client for Zinit process supervisor.
- **`sal::cmd`**: Implements the command logic for `herodo`.
- **(Internal integrations for `buildah`, `nerdctl` primarily exposed via Rhai)**
## Building SAL
Build the library and the `herodo` binary using Cargo:
```bash
cargo build
```
For a release build:
```bash
cargo build --release
```
The `herodo` executable will be located at `target/debug/herodo` or `target/release/herodo`.
The `build_herodo.sh` script is also available for building `herodo`.
## Running Tests
Run Rust unit and integration tests:
```bash
cargo test
```
Run Rhai script tests (which exercise `herodo` and SAL's scripted functionalities):
```bash
./run_rhai_tests.sh
```
## License
SAL is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file for details.
## Contributing
Contributions are welcome! Please feel free to submit pull requests or open issues.

View File

@ -1,156 +0,0 @@
please refactor each of the objects in the the chosen folder to use builder paradigm, see below for an example
we always start from root object, each file e.g. product.rs corresponds to a root object, the rootobject is what is stored in the DB, the rest are sub objects which are children of the root object
---
### ✅ Step 1: Define your struct
```rust
#[derive(Debug)]
pub enum ProductType {
Service,
// Other variants...
}
#[derive(Debug)]
pub enum ProductStatus {
Available,
Unavailable,
// Other variants...
}
#[derive(Debug)]
pub struct Product {
id: u32,
name: String,
description: String,
price: f64,
product_type: ProductType,
category: String,
status: ProductStatus,
max_amount: u32,
validity_days: u32,
}
```
---
### ✅ Step 2: Create a builder
```rust
pub struct ProductBuilder {
id: Option<u32>,
name: Option<String>,
description: Option<String>,
price: Option<f64>,
product_type: Option<ProductType>,
category: Option<String>,
status: Option<ProductStatus>,
max_amount: Option<u32>,
validity_days: Option<u32>,
}
impl ProductBuilder {
pub fn new() -> Self {
Self {
id: None,
name: None,
description: None,
price: None,
product_type: None,
category: None,
status: None,
max_amount: None,
validity_days: None,
}
}
pub fn id(mut self, id: u32) -> Self {
self.id = Some(id);
self
}
pub fn name<S: Into<String>>(mut self, name: S) -> Self {
self.name = Some(name.into());
self
}
pub fn description<S: Into<String>>(mut self, description: S) -> Self {
self.description = Some(description.into());
self
}
pub fn price(mut self, price: f64) -> Self {
self.price = Some(price);
self
}
pub fn product_type(mut self, product_type: ProductType) -> Self {
self.product_type = Some(product_type);
self
}
pub fn category<S: Into<String>>(mut self, category: S) -> Self {
self.category = Some(category.into());
self
}
pub fn status(mut self, status: ProductStatus) -> Self {
self.status = Some(status);
self
}
pub fn max_amount(mut self, max_amount: u32) -> Self {
self.max_amount = Some(max_amount);
self
}
pub fn validity_days(mut self, validity_days: u32) -> Self {
self.validity_days = Some(validity_days);
self
}
pub fn build(self) -> Result<Product, &'static str> {
Ok(Product {
id: self.id.ok_or("id is required")?,
name: self.name.ok_or("name is required")?,
description: self.description.ok_or("description is required")?,
price: self.price.ok_or("price is required")?,
product_type: self.product_type.ok_or("type is required")?,
category: self.category.ok_or("category is required")?,
status: self.status.ok_or("status is required")?,
max_amount: self.max_amount.ok_or("max_amount is required")?,
validity_days: self.validity_days.ok_or("validity_days is required")?,
})
}
}
```
---
### ✅ Step 3: Use it like this
```rust
let product = ProductBuilder::new()
.id(1)
.name("Premium Service")
.description("Our premium service offering")
.price(99.99)
.product_type(ProductType::Service)
.category("Services")
.status(ProductStatus::Available)
.max_amount(100)
.validity_days(30)
.build()
.expect("Failed to build product");
```
---
This way:
- You dont need to remember the order of parameters.
- You get readable, self-documenting code.
- Its easier to provide defaults or optional values if you want later.
Want help generating this automatically via a macro or just want it shorter? I can show you a derive macro to do that too.

View File

@ -1,994 +0,0 @@
# Best Practices for Wrapping Rust Functions with Rhai
This document provides comprehensive guidance on how to effectively wrap Rust functions with different standard arguments, pass structs, and handle various return types including errors when using the Rhai scripting language.
## Table of Contents
1. [Introduction](#introduction)
2. [Basic Function Registration](#basic-function-registration)
3. [Working with Different Argument Types](#working-with-different-argument-types)
4. [Passing and Working with Structs](#passing-and-working-with-structs)
5. [Error Handling](#error-handling)
6. [Returning Different Types](#returning-different-types)
7. [Native Function Handling](#native-function-handling)
8. [Advanced Patterns](#advanced-patterns)
9. [Complete Examples](#complete-examples)
## Introduction
Rhai is an embedded scripting language for Rust that allows you to expose Rust functions to scripts and vice versa. This document focuses on the best practices for wrapping Rust functions so they can be called from Rhai scripts, with special attention to handling different argument types, structs, and error conditions.
## Basic Function Registration
### Simple Function Registration
The most basic way to register a Rust function with Rhai is using the `register_fn` method:
```rust
fn add(x: i64, y: i64) -> i64 {
x + y
}
fn main() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
// Register the function with Rhai
engine.register_fn("add", add);
// Now the function can be called from Rhai scripts
let result = engine.eval::<i64>("add(40, 2)")?;
println!("Result: {}", result); // prints 42
Ok(())
}
```
### Function Naming Conventions
When registering functions, follow these naming conventions:
1. Use snake_case for function names to maintain consistency with Rhai's style
2. Choose descriptive names that clearly indicate the function's purpose
3. For functions that operate on specific types, consider prefixing with the type name (e.g., `string_length`)
## Working with Different Argument Types
### Primitive Types
Rhai supports the following primitive types that can be directly used as function arguments:
- `i64` (integer)
- `f64` (float)
- `bool` (boolean)
- `String` or `&str` (string)
- `char` (character)
- `()` (unit type)
Example:
```rust
fn calculate(num: i64, factor: f64, enabled: bool) -> f64 {
if enabled {
num as f64 * factor
} else {
0.0
}
}
engine.register_fn("calculate", calculate);
```
### Arrays and Collections
For array arguments:
```rust
fn sum_array(arr: Array) -> i64 {
arr.iter()
.filter_map(|v| v.as_int().ok())
.sum()
}
engine.register_fn("sum_array", sum_array);
```
### Optional Arguments and Function Overloading
Rhai supports function overloading, which allows you to register multiple functions with the same name but different parameter types or counts:
```rust
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
fn greet_with_title(title: &str, name: &str) -> String {
format!("Hello, {} {}!", title, name)
}
engine.register_fn("greet", greet);
engine.register_fn("greet", greet_with_title);
// In Rhai:
// greet("World") -> "Hello, World!"
// greet("Mr.", "Smith") -> "Hello, Mr. Smith!"
```
## Passing and Working with Structs
### Registering Custom Types
To use Rust structs in Rhai, you need to register them:
#### Method 1: Using the CustomType Trait (Recommended)
```rust
#[derive(Debug, Clone, CustomType)]
#[rhai_type(extra = Self::build_extra)]
struct TestStruct {
x: i64,
}
impl TestStruct {
pub fn new() -> Self {
Self { x: 1 }
}
pub fn update(&mut self) {
self.x += 1000;
}
pub fn calculate(&mut self, data: i64) -> i64 {
self.x * data
}
fn build_extra(builder: &mut TypeBuilder<Self>) {
builder
.with_name("TestStruct")
.with_fn("new_ts", Self::new)
.with_fn("update", Self::update)
.with_fn("calc", Self::calculate);
}
}
// In your main function:
let mut engine = Engine::new();
engine.build_type::<TestStruct>();
```
#### Method 2: Manual Registration
```rust
#[derive(Debug, Clone)]
struct TestStruct {
x: i64,
}
impl TestStruct {
pub fn new() -> Self {
Self { x: 1 }
}
pub fn update(&mut self) {
self.x += 1000;
}
}
let mut engine = Engine::new();
engine
.register_type_with_name::<TestStruct>("TestStruct")
.register_fn("new_ts", TestStruct::new)
.register_fn("update", TestStruct::update);
```
### Accessing Struct Fields
By default, Rhai can access public fields of registered structs:
```rust
// In Rhai script:
let x = new_ts();
x.x = 42; // Direct field access
```
### Passing Structs as Arguments
When passing structs as arguments to functions, ensure they implement the `Clone` trait:
```rust
fn process_struct(test: TestStruct) -> i64 {
test.x * 2
}
engine.register_fn("process_struct", process_struct);
```
### Returning Structs from Functions
You can return custom structs from functions:
```rust
fn create_struct(value: i64) -> TestStruct {
TestStruct { x: value }
}
engine.register_fn("create_struct", create_struct);
```
## Error Handling
Error handling is a critical aspect of integrating Rust functions with Rhai. Proper error handling ensures that script execution fails gracefully with meaningful error messages.
### Basic Error Handling
The most basic way to handle errors is to return a `Result` type:
```rust
fn divide(a: i64, b: i64) -> Result<i64, Box<EvalAltResult>> {
if b == 0 {
// Return an error if division by zero
Err("Division by zero".into())
} else {
Ok(a / b)
}
}
engine.register_fn("divide", divide);
```
### EvalAltResult Types
Rhai provides several error types through the `EvalAltResult` enum:
```rust
use rhai::EvalAltResult;
use rhai::Position;
fn my_function() -> Result<i64, Box<EvalAltResult>> {
// Different error types
// Runtime error - general purpose error
return Err(Box::new(EvalAltResult::ErrorRuntime(
"Something went wrong".into(),
Position::NONE
)));
// Type error - when a type mismatch occurs
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
"expected i64, got string".into(),
Position::NONE,
"i64".into()
)));
// Function not found error
return Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
"function_name".into(),
Position::NONE
)));
}
```
### Custom Error Types
For more structured error handling, you can create custom error types:
```rust
use thiserror::Error;
use rhai::{EvalAltResult, Position};
#[derive(Error, Debug)]
enum MyError {
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Calculation error: {0}")]
CalculationError(String),
#[error("Database error: {0}")]
DatabaseError(String),
}
// Convert your custom error to EvalAltResult
fn process_data(input: i64) -> Result<i64, Box<EvalAltResult>> {
// Your logic here that might return a custom error
let result = validate_input(input)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
format!("Validation failed: {}", e),
Position::NONE
)))?;
let processed = calculate(result)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
format!("Calculation failed: {}", e),
Position::NONE
)))?;
if processed < 0 {
return Err(Box::new(EvalAltResult::ErrorRuntime(
"Negative result not allowed".into(),
Position::NONE
)));
}
Ok(processed)
}
// Helper functions that return our custom error type
fn validate_input(input: i64) -> Result<i64, MyError> {
if input <= 0 {
return Err(MyError::InvalidInput("Input must be positive".into()));
}
Ok(input)
}
fn calculate(value: i64) -> Result<i64, MyError> {
if value > 1000 {
return Err(MyError::CalculationError("Value too large".into()));
}
Ok(value * 2)
}
```
### Error Propagation
When calling Rhai functions from Rust, errors are propagated through the `?` operator:
```rust
let result = engine.eval::<i64>("divide(10, 0)")?; // This will propagate the error
```
### Error Context and Position Information
For better debugging, include position information in your errors:
```rust
fn parse_config(config: &str) -> Result<Map, Box<EvalAltResult>> {
// Get the call position from the context
let pos = Position::NONE; // In a real function, you'd get this from NativeCallContext
match serde_json::from_str::<serde_json::Value>(config) {
Ok(json) => {
// Convert JSON to Rhai Map
let mut map = Map::new();
// ... conversion logic ...
Ok(map)
},
Err(e) => {
Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to parse config: {}", e),
pos
)))
}
}
}
```
### Best Practices for Error Handling
1. **Be Specific**: Provide clear, specific error messages that help script writers understand what went wrong
2. **Include Context**: When possible, include relevant context in error messages (e.g., variable values, expected types)
3. **Consistent Error Types**: Use consistent error types for similar issues
4. **Validate Early**: Validate inputs at the beginning of functions to fail fast
5. **Document Error Conditions**: Document possible error conditions for functions exposed to Rhai
## Returning Different Types
Properly handling return types is crucial for creating a seamless integration between Rust and Rhai. This section covers various approaches to returning different types of data from Rust functions to Rhai scripts.
### Simple Return Types
For simple return types, specify the type when registering the function:
```rust
fn get_number() -> i64 { 42 }
fn get_string() -> String { "hello".to_string() }
fn get_boolean() -> bool { true }
fn get_float() -> f64 { 3.14159 }
fn get_char() -> char { 'A' }
fn get_unit() -> () { () }
engine.register_fn("get_number", get_number);
engine.register_fn("get_string", get_string);
engine.register_fn("get_boolean", get_boolean);
engine.register_fn("get_float", get_float);
engine.register_fn("get_char", get_char);
engine.register_fn("get_unit", get_unit);
```
### Dynamic Return Types
WE SHOULD TRY NOT TO DO THIS
For functions that may return different types based on conditions, use the `Dynamic` type:
```rust
fn get_value(which: i64) -> Dynamic {
match which {
0 => Dynamic::from(42),
1 => Dynamic::from("hello"),
2 => Dynamic::from(true),
3 => Dynamic::from(3.14159),
4 => {
let mut array = Array::new();
array.push(Dynamic::from(1));
array.push(Dynamic::from(2));
Dynamic::from_array(array)
},
5 => {
let mut map = Map::new();
map.insert("key".into(), "value".into());
Dynamic::from_map(map)
},
_ => Dynamic::UNIT,
}
}
engine.register_fn("get_value", get_value);
```
### Returning Collections
Rhai supports various collection types:
```rust
// Returning an array
fn get_array() -> Array {
let mut array = Array::new();
array.push(Dynamic::from(1));
array.push(Dynamic::from("hello"));
array.push(Dynamic::from(true));
array
}
// Returning a map
fn get_map() -> Map {
let mut map = Map::new();
map.insert("number".into(), 42.into());
map.insert("string".into(), "hello".into());
map.insert("boolean".into(), true.into());
map
}
// Returning a typed Vec (will be converted to Rhai Array)
fn get_numbers() -> Vec<i64> {
vec![1, 2, 3, 4, 5]
}
// Returning a HashMap (will be converted to Rhai Map)
fn get_config() -> HashMap<String, String> {
let mut map = HashMap::new();
map.insert("host".to_string(), "localhost".to_string());
map.insert("port".to_string(), "8080".to_string());
map
}
engine.register_fn("get_array", get_array);
engine.register_fn("get_map", get_map);
engine.register_fn("get_numbers", get_numbers);
engine.register_fn("get_config", get_config);
```
### Returning Custom Structs
For returning custom structs, ensure they implement the `Clone` trait:
```rust
#[derive(Debug, Clone)]
struct TestStruct {
x: i64,
name: String,
active: bool,
}
fn create_struct(value: i64, name: &str, active: bool) -> TestStruct {
TestStruct {
x: value,
name: name.to_string(),
active
}
}
fn get_struct_array() -> Vec<TestStruct> {
vec![
TestStruct { x: 1, name: "one".to_string(), active: true },
TestStruct { x: 2, name: "two".to_string(), active: false },
]
}
engine.register_type_with_name::<TestStruct>("TestStruct")
.register_fn("create_struct", create_struct)
.register_fn("get_struct_array", get_struct_array);
```
### Returning Results and Options
For functions that might fail or return optional values:
```rust
// Returning a Result
fn divide(a: i64, b: i64) -> Result<i64, Box<EvalAltResult>> {
if b == 0 {
Err("Division by zero".into())
} else {
Ok(a / b)
}
}
// Returning an Option (converted to Dynamic)
fn find_item(id: i64) -> Dynamic {
let item = lookup_item(id);
match item {
Some(value) => value.into(),
None => Dynamic::UNIT, // Rhai has no null, so use () for None
}
}
// Helper function returning Option
fn lookup_item(id: i64) -> Option<TestStruct> {
match id {
1 => Some(TestStruct { x: 1, name: "one".to_string(), active: true }),
2 => Some(TestStruct { x: 2, name: "two".to_string(), active: false }),
_ => None,
}
}
engine.register_fn("divide", divide);
engine.register_fn("find_item", find_item);
```
### Serialization and Deserialization
When working with JSON or other serialized formats:
```rust
use serde_json::{Value as JsonValue, json};
// Return JSON data as a Rhai Map
fn get_json_data() -> Result<Map, Box<EvalAltResult>> {
// Simulate fetching JSON data
let json_data = json!({
"name": "John Doe",
"age": 30,
"address": {
"street": "123 Main St",
"city": "Anytown"
},
"phones": ["+1-555-1234", "+1-555-5678"]
});
// Convert JSON to Rhai Map
json_to_rhai_value(json_data)
.and_then(|v| v.try_cast::<Map>().map_err(|_| "Expected a map".into()))
}
// Helper function to convert JSON Value to Rhai Dynamic
fn json_to_rhai_value(json: JsonValue) -> Result<Dynamic, Box<EvalAltResult>> {
match json {
JsonValue::Null => Ok(Dynamic::UNIT),
JsonValue::Bool(b) => Ok(b.into()),
JsonValue::Number(n) => {
if n.is_i64() {
Ok(n.as_i64().unwrap().into())
} else {
Ok(n.as_f64().unwrap().into())
}
},
JsonValue::String(s) => Ok(s.into()),
JsonValue::Array(arr) => {
let mut rhai_array = Array::new();
for item in arr {
rhai_array.push(json_to_rhai_value(item)?);
}
Ok(Dynamic::from_array(rhai_array))
},
JsonValue::Object(obj) => {
let mut rhai_map = Map::new();
for (k, v) in obj {
rhai_map.insert(k.into(), json_to_rhai_value(v)?);
}
Ok(Dynamic::from_map(rhai_map))
}
}
}
engine.register_fn("get_json_data", get_json_data);
```
### Working with Dynamic Type System
Understanding how to work with Rhai's Dynamic type system is essential:
```rust
// Function that examines a Dynamic value and returns information about it
fn inspect_value(value: Dynamic) -> Map {
let mut info = Map::new();
// Store the type name
info.insert("type".into(), value.type_name().into());
// Store specific type information
if value.is_int() {
info.insert("category".into(), "number".into());
info.insert("value".into(), value.clone());
} else if value.is_float() {
info.insert("category".into(), "number".into());
info.insert("value".into(), value.clone());
} else if value.is_string() {
info.insert("category".into(), "string".into());
info.insert("length".into(), value.clone_cast::<String>().len().into());
info.insert("value".into(), value.clone());
} else if value.is_array() {
info.insert("category".into(), "array".into());
info.insert("length".into(), value.clone_cast::<Array>().len().into());
} else if value.is_map() {
info.insert("category".into(), "map".into());
info.insert("keys".into(), value.clone_cast::<Map>().keys().len().into());
} else if value.is_bool() {
info.insert("category".into(), "boolean".into());
info.insert("value".into(), value.clone());
} else {
info.insert("category".into(), "other".into());
}
info
}
engine.register_fn("inspect", inspect_value);
```
## Native Function Handling
When working with native Rust functions in Rhai, there are several important considerations for handling different argument types, especially when dealing with complex data structures and error cases.
### Native Function Signature
Native Rust functions registered with Rhai can have one of two signatures:
1. **Standard Function Signature**: Functions with typed parameters
```rust
fn my_function(param1: Type1, param2: Type2, ...) -> ReturnType { ... }
```
2. **Dynamic Function Signature**: Functions that handle raw Dynamic values
```rust
fn my_dynamic_function(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> { ... }
```
### Working with Raw Dynamic Arguments
The dynamic function signature gives you more control but requires manual type checking and conversion:
```rust
fn process_dynamic_args(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> {
// Check number of arguments
if args.len() != 2 {
return Err("Expected exactly 2 arguments".into());
}
// Extract and convert the first argument to an integer
let arg1 = args[0].as_int().map_err(|_| "First argument must be an integer".into())?;
// Extract and convert the second argument to a string
let arg2 = args[1].as_str().map_err(|_| "Second argument must be a string".into())?;
// Process the arguments
let result = format!("{}: {}", arg2, arg1);
// Return the result as a Dynamic value
Ok(result.into())
}
// Register the function
engine.register_fn("process", process_dynamic_args);
```
### Handling Complex Struct Arguments
When working with complex struct arguments, you have several options:
#### Option 1: Use typed parameters (recommended for simple cases)
```rust
#[derive(Clone)]
struct ComplexData {
id: i64,
values: Vec<f64>,
}
fn process_complex(data: &mut ComplexData, factor: f64) -> f64 {
let sum: f64 = data.values.iter().sum();
data.values.push(sum * factor);
sum * factor
}
engine.register_fn("process_complex", process_complex);
```
#### Option 2: Use Dynamic parameters for more flexibility
```rust
fn process_complex_dynamic(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> {
// Check arguments
if args.len() != 2 {
return Err("Expected exactly 2 arguments".into());
}
// Get mutable reference to the complex data
let data = args[0].write_lock::<ComplexData>()
.ok_or_else(|| "First argument must be ComplexData".into())?;
// Get the factor
let factor = args[1].as_float().map_err(|_| "Second argument must be a number".into())?;
// Process the data
let sum: f64 = data.values.iter().sum();
data.values.push(sum * factor);
Ok((sum * factor).into())
}
```
### Handling Variable Arguments
For functions that accept a variable number of arguments:
```rust
fn sum_all(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> {
let mut total: i64 = 0;
for arg in args.iter() {
total += arg.as_int().map_err(|_| "All arguments must be integers".into())?;
}
Ok(total.into())
}
engine.register_fn("sum_all", sum_all);
// In Rhai:
// sum_all(1, 2, 3, 4, 5) -> 15
// sum_all(10, 20) -> 30
```
### Handling Optional Arguments
For functions with optional arguments, use function overloading:
```rust
fn create_person(name: &str) -> Person {
Person { name: name.to_string(), age: 30 } // Default age
}
fn create_person_with_age(name: &str, age: i64) -> Person {
Person { name: name.to_string(), age }
}
engine.register_fn("create_person", create_person);
engine.register_fn("create_person", create_person_with_age);
// In Rhai:
// create_person("John") -> Person with name "John" and age 30
// create_person("John", 25) -> Person with name "John" and age 25
```
### Handling Default Arguments
Rhai doesn't directly support default arguments, but you can simulate them:
```rust
fn configure(options: &mut Map) -> Result<(), Box<EvalAltResult>> {
// Check if certain options exist, if not, set defaults
if !options.contains_key("timeout") {
options.insert("timeout".into(), 30_i64.into());
}
if !options.contains_key("retry") {
options.insert("retry".into(), true.into());
}
Ok(())
}
engine.register_fn("configure", configure);
// In Rhai:
// let options = #{};
// configure(options);
// print(options.timeout); // Prints 30
```
### Handling Mutable and Immutable References
Rhai supports both mutable and immutable references:
```rust
// Function taking an immutable reference
fn get_name(person: &Person) -> String {
person.name.clone()
}
// Function taking a mutable reference
fn increment_age(person: &mut Person) {
person.age += 1;
}
engine.register_fn("get_name", get_name);
engine.register_fn("increment_age", increment_age);
```
### Converting Between Rust and Rhai Types
When you need to convert between Rust and Rhai types:
```rust
// Convert a Rust HashMap to a Rhai Map
fn create_config() -> Map {
let mut rust_map = HashMap::new();
rust_map.insert("server".to_string(), "localhost".to_string());
rust_map.insert("port".to_string(), "8080".to_string());
// Convert to Rhai Map
let mut rhai_map = Map::new();
for (k, v) in rust_map {
rhai_map.insert(k.into(), v.into());
}
rhai_map
}
// Convert a Rhai Array to a Rust Vec
fn process_array(arr: Array) -> Result<i64, Box<EvalAltResult>> {
// Convert to Rust Vec<i64>
let rust_vec: Result<Vec<i64>, _> = arr.iter()
.map(|v| v.as_int().map_err(|_| "Array must contain only integers".into()))
.collect();
let numbers = rust_vec?;
Ok(numbers.iter().sum())
}
```
## Complete Examples
### Example 1: Basic Function Registration and Struct Handling
```rust
use rhai::{Engine, EvalAltResult, RegisterFn};
#[derive(Debug, Clone)]
struct Person {
name: String,
age: i64,
}
impl Person {
fn new(name: &str, age: i64) -> Self {
Self {
name: name.to_string(),
age,
}
}
fn greet(&self) -> String {
format!("Hello, my name is {} and I am {} years old.", self.name, self.age)
}
fn have_birthday(&mut self) {
self.age += 1;
}
}
fn is_adult(person: &Person) -> bool {
person.age >= 18
}
fn main() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
// Register the Person type
engine
.register_type_with_name::<Person>("Person")
.register_fn("new_person", Person::new)
.register_fn("greet", Person::greet)
.register_fn("have_birthday", Person::have_birthday)
.register_fn("is_adult", is_adult);
// Run a script that uses the Person type
let result = engine.eval::<String>(r#"
let p = new_person("John", 17);
let greeting = p.greet();
if !is_adult(p) {
p.have_birthday();
}
greeting + " Now I am " + p.age.to_string() + " years old."
"#)?;
println!("{}", result);
Ok(())
}
```
### Example 2: Error Handling and Complex Return Types
```rust
use rhai::{Engine, EvalAltResult, Map, Dynamic};
use std::collections::HashMap;
#[derive(Debug, Clone)]
struct Product {
id: i64,
name: String,
price: f64,
}
fn get_product(id: i64) -> Result<Product, Box<EvalAltResult>> {
match id {
1 => Ok(Product { id: 1, name: "Laptop".to_string(), price: 999.99 }),
2 => Ok(Product { id: 2, name: "Phone".to_string(), price: 499.99 }),
_ => Err("Product not found".into())
}
}
fn calculate_total(products: Array) -> Result<f64, Box<EvalAltResult>> {
let mut total = 0.0;
for product_dynamic in products.iter() {
let product = product_dynamic.clone().try_cast::<Product>()
.map_err(|_| "Invalid product in array".into())?;
total += product.price;
}
Ok(total)
}
fn get_product_map() -> Map {
let mut map = Map::new();
map.insert("laptop".into(),
Dynamic::from(Product { id: 1, name: "Laptop".to_string(), price: 999.99 }));
map.insert("phone".into(),
Dynamic::from(Product { id: 2, name: "Phone".to_string(), price: 499.99 }));
map
}
fn main() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine
.register_type_with_name::<Product>("Product")
.register_fn("get_product", get_product)
.register_fn("calculate_total", calculate_total)
.register_fn("get_product_map", get_product_map);
let result = engine.eval::<f64>(r#"
let products = [];
// Try to get products
try {
products.push(get_product(1));
products.push(get_product(2));
products.push(get_product(3)); // This will throw an error
} catch(err) {
print(`Error: ${err}`);
}
// Get products from map
let product_map = get_product_map();
products.push(product_map.laptop);
calculate_total(products)
"#)?;
println!("Total: ${:.2}", result);
Ok(())
}
```

View File

@ -1,134 +0,0 @@
### Error Handling in Dynamic Functions
When working with the dynamic function signature, error handling is slightly different:
```rust
fn dynamic_function(ctx: NativeCallContext, args: &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> {
// Get the position information from the context
let pos = ctx.position();
// Validate arguments
if args.len() < 2 {
return Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Expected at least 2 arguments, got {}", args.len()),
pos
)));
}
// Try to convert arguments with proper error handling
let arg1 = match args[0].as_int() {
Ok(val) => val,
Err(_) => return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
"Expected first argument to be an integer".into(),
pos,
"i64".into()
)))
};
// Process with error handling
if arg1 <= 0 {
return Err(Box::new(EvalAltResult::ErrorRuntime(
"First argument must be positive".into(),
pos
)));
}
// Return success
Ok(Dynamic::from(arg1 * 2))
}
```
## Advanced Patterns
### Working with Function Pointers
You can create function pointers that bind to Rust functions:
```rust
fn my_awesome_fn(ctx: NativeCallContext, args: &mut[&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> {
// Check number of arguments
if args.len() != 2 {
return Err("one argument is required, plus the object".into());
}
// Get call arguments
let x = args[1].try_cast::<i64>().map_err(|_| "argument must be an integer".into())?;
// Get mutable reference to the object map, which is passed as the first argument
let map = &mut *args[0].as_map_mut().map_err(|_| "object must be a map".into())?;
// Do something awesome here ...
let result = x * 2;
Ok(result.into())
}
// Register a function to create a pre-defined object
engine.register_fn("create_awesome_object", || {
// Use an object map as base
let mut map = Map::new();
// Create a function pointer that binds to 'my_awesome_fn'
let fp = FnPtr::from_fn("awesome", my_awesome_fn)?;
// ^ name of method
// ^ native function
// Store the function pointer in the object map
map.insert("awesome".into(), fp.into());
Ok(Dynamic::from_map(map))
});
```
### Creating Rust Closures from Rhai Functions
You can encapsulate a Rhai script as a Rust closure:
```rust
use rhai::{Engine, Func};
let engine = Engine::new();
let script = "fn calc(x, y) { x + y.len < 42 }";
// Create a Rust closure from a Rhai function
let func = Func::<(i64, &str), bool>::create_from_script(
engine, // the 'Engine' is consumed into the closure
script, // the script
"calc" // the entry-point function name
)?;
// Call the closure
let result = func(123, "hello")?;
// Pass it as a callback to another function
schedule_callback(func);
```
### Calling Rhai Functions from Rust
You can call Rhai functions from Rust:
```rust
// Compile the script to AST
let ast = engine.compile(script)?;
// Create a custom 'Scope'
let mut scope = Scope::new();
// Add variables to the scope
scope.push("my_var", 42_i64);
scope.push("my_string", "hello, world!");
scope.push_constant("MY_CONST", true);
// Call a function defined in the script
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", ("abc", 123_i64))?;
// For a function with one parameter, use a tuple with a trailing comma
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", (123_i64,))?;
// For a function with no parameters
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", ())?;
```

View File

@ -1,187 +0,0 @@
## Best Practices and Optimization
When wrapping Rust functions for use with Rhai, following these best practices will help you create efficient, maintainable, and robust code.
### Performance Considerations
1. **Minimize Cloning**: Rhai often requires cloning data, but you can minimize this overhead:
```rust
// Prefer immutable references when possible
fn process_data(data: &MyStruct) -> i64 {
// Work with data without cloning
data.value * 2
}
// Use mutable references for in-place modifications
fn update_data(data: &mut MyStruct) {
data.value += 1;
}
```
2. **Avoid Excessive Type Conversions**: Converting between Rhai's Dynamic type and Rust types has overhead:
```rust
// Inefficient - multiple conversions
fn process_inefficient(ctx: NativeCallContext, args: &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> {
let value = args[0].as_int()?;
let result = value * 2;
Ok(Dynamic::from(result))
}
// More efficient - use typed parameters when possible
fn process_efficient(value: i64) -> i64 {
value * 2
}
```
3. **Batch Operations**: For operations on collections, batch processing is more efficient:
```rust
// Process an entire array at once rather than element by element
fn sum_array(arr: Array) -> Result<i64, Box<EvalAltResult>> {
arr.iter()
.map(|v| v.as_int())
.collect::<Result<Vec<i64>, _>>()
.map(|nums| nums.iter().sum())
.map_err(|_| "Array must contain only integers".into())
}
```
4. **Compile Scripts Once**: Reuse compiled ASTs for scripts that are executed multiple times:
```rust
// Compile once
let ast = engine.compile(script)?;
// Execute multiple times with different parameters
for i in 0..10 {
let result = engine.eval_ast::<i64>(&ast)?;
println!("Result {}: {}", i, result);
}
```
### Thread Safety
1. **Use Sync Mode When Needed**: If you need thread safety, use the `sync` feature:
```rust
// In Cargo.toml
// rhai = { version = "1.x", features = ["sync"] }
// This creates a thread-safe engine
let engine = Engine::new();
// Now you can safely share the engine between threads
std::thread::spawn(move || {
let result = engine.eval::<i64>("40 + 2")?;
println!("Result: {}", result);
});
```
2. **Clone the Engine for Multiple Threads**: When not using `sync`, clone the engine for each thread:
```rust
let engine = Engine::new();
let handles: Vec<_> = (0..5).map(|i| {
let engine_clone = engine.clone();
std::thread::spawn(move || {
let result = engine_clone.eval::<i64>(&format!("{} + 2", i * 10))?;
println!("Thread {}: {}", i, result);
})
}).collect();
for handle in handles {
handle.join().unwrap();
}
```
### Memory Management
1. **Control Scope Size**: Be mindful of the size of your scopes:
```rust
// Create a new scope for each operation to avoid memory buildup
for item in items {
let mut scope = Scope::new();
scope.push("item", item);
engine.eval_with_scope::<()>(&mut scope, "process(item)")?;
}
```
2. **Limit Script Complexity**: Use engine options to limit script complexity:
```rust
let mut engine = Engine::new();
// Set limits to prevent scripts from consuming too many resources
engine.set_max_expr_depths(64, 64) // Max expression/statement depth
.set_max_function_expr_depth(64) // Max function depth
.set_max_array_size(10000) // Max array size
.set_max_map_size(10000) // Max map size
.set_max_string_size(10000) // Max string size
.set_max_call_levels(64); // Max call stack depth
```
3. **Use Shared Values Carefully**: Shared values (via closures) have reference-counting overhead:
```rust
// Avoid unnecessary capturing in closures when possible
engine.register_fn("process", |x: i64| x * 2);
// Instead of capturing large data structures
let large_data = vec![1, 2, 3, /* ... thousands of items ... */];
engine.register_fn("process_data", move |idx: i64| {
if idx >= 0 && (idx as usize) < large_data.len() {
large_data[idx as usize]
} else {
0
}
});
// Consider registering a lookup function instead
let large_data = std::sync::Arc::new(vec![1, 2, 3, /* ... thousands of items ... */]);
let data_ref = large_data.clone();
engine.register_fn("lookup", move |idx: i64| {
if idx >= 0 && (idx as usize) < data_ref.len() {
data_ref[idx as usize]
} else {
0
}
});
```
### API Design
1. **Consistent Naming**: Use consistent naming conventions:
```rust
// Good: Consistent naming pattern
engine.register_fn("create_user", create_user)
.register_fn("update_user", update_user)
.register_fn("delete_user", delete_user);
// Bad: Inconsistent naming
engine.register_fn("create_user", create_user)
.register_fn("user_update", update_user)
.register_fn("remove", delete_user);
```
2. **Logical Function Grouping**: Group related functions together:
```rust
// Register all string-related functions together
engine.register_fn("str_length", |s: &str| s.len() as i64)
.register_fn("str_uppercase", |s: &str| s.to_uppercase())
.register_fn("str_lowercase", |s: &str| s.to_lowercase());
// Register all math-related functions together
engine.register_fn("math_sin", |x: f64| x.sin())
.register_fn("math_cos", |x: f64| x.cos())
.register_fn("math_tan", |x: f64| x.tan());
```
3. **Comprehensive Documentation**: Document your API thoroughly:
```rust
// Add documentation for script writers
let mut engine = Engine::new();
#[cfg(feature = "metadata")]
{
// Add function documentation
engine.register_fn("calculate_tax", calculate_tax)
.register_fn_metadata("calculate_tax", |metadata| {
metadata.set_doc_comment("Calculates tax based on income and rate.\n\nParameters:\n- income: Annual income\n- rate: Tax rate (0.0-1.0)\n\nReturns: Calculated tax amount");
});
}
```

View File

@ -1,42 +0,0 @@
#!/bin/bash
set -e
# Change to directory where this script is located
cd "$(dirname "${BASH_SOURCE[0]}")"
rm -f ./target/debug/herodo
# Build the herodo project
echo "Building herodo..."
cargo build --bin herodo
# cargo build --release --bin herodo
# Check if the build was successful
if [ $? -ne 0 ]; then
echo "Build failed. Please check the error messages."
exit 1
fi
# Echo a success message
echo "Build successful!"
mkdir -p ~/hero/bin/
cp target/debug/herodo ~/hero/bin/herodo
# Check if a script name was provided
if [ $# -eq 1 ]; then
echo "Running specified test: $1"
# Check if the script exists in src/rhaiexamples/
if [ -f "src/rhaiexamples/$1.rhai" ]; then
herodo "src/rhaiexamples/$1.rhai"
# Check if the script exists in src/herodo/scripts/
elif [ -f "src/herodo/scripts/$1.rhai" ]; then
herodo "src/herodo/scripts/$1.rhai"
else
echo "Error: Script $1.rhai not found in src/rhaiexamples/ or src/herodo/scripts/"
exit 1
fi
exit 0
fi

View File

@ -1,22 +0,0 @@
{
"style": "dark",
"links": [
{
"title": "Web",
"items": [
{
"label": "ThreeFold.io",
"href": "https://threefold.io"
},
{
"href": "https://mycelium.threefold.io/",
"label": "Mycelium Network"
},
{
"href": "https://aibox.threefold.io/",
"label": "AI Box"
}
]
}
]
}

View File

@ -1,17 +0,0 @@
{
"title": "ThreeFold HeroScript",
"tagline": "ThreeFold HeroScript",
"favicon": "img/favicon.png",
"url": "https://threefold.info",
"url_home": "docs/intro",
"baseUrl": "/heroscript/",
"image": "img/tf_graph.png",
"metadata": {
"description": "Internet Infrastructur for Everyone by Everyone, Everywhere.",
"image": "https://threefold.info/tfgrid4/img/tf_graph.png",
"title": "ThreeFold"
},
"buildDest":["root@info.ourworld.tf:/root/hero/www/info/heroscript"],
"buildDestDev":["root@info.ourworld.tf:/root/hero/www/infodev/heroscript"],
"copyright": "ThreeFold"
}

View File

@ -1,25 +0,0 @@
{
"title": "",
"logo": {
"alt": "ThreeFold Logo",
"src": "img/logo.svg",
"srcDark": "img/new_logo_tft.png"
},
"items": [
{
"href": "https://threefold.io",
"label": "ThreeFold.io",
"position": "right"
},
{
"href": "https://mycelium.threefold.io/",
"label": "Mycelium Network",
"position": "right"
},
{
"href": "https://aibox.threefold.io/",
"label": "AI Box",
"position": "right"
}
]
}

View File

@ -1,7 +0,0 @@
---
title: "intro"
sidebar_position: 1
---
# HeroScript

View File

@ -1,105 +0,0 @@
# Buildah Module Tests
This document describes the test scripts for the Buildah module in the SAL library. These tests verify the functionality of the Buildah module's container and image operations.
## Test Structure
The tests are organized into three main scripts:
1. **Builder Pattern** (`01_builder_pattern.rhai`): Tests for the Builder pattern, including creating containers, running commands, and working with container content.
2. **Image Operations** (`02_image_operations.rhai`): Tests for image-related operations like pulling, tagging, listing, and removing images.
3. **Container Operations** (`03_container_operations.rhai`): Tests for container-related operations like configuration, isolation, and content management.
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues.
## Running the Tests
To run all tests, execute the following command from the project root:
```bash
herodo --path src/rhai_tests/buildah/run_all_tests.rhai
```
To run individual test scripts:
```bash
herodo --path src/rhai_tests/buildah/01_builder_pattern.rhai
```
## Test Details
### Builder Pattern Test
The Builder Pattern test (`01_builder_pattern.rhai`) verifies the following functions:
- `bah_new`: Creating a new Builder with a container from a specified image
- Builder properties: `container_id`, `name`, `image`, `debug_mode`
- `run`: Running commands in the container
- `write_content`: Writing content to files in the container
- `read_content`: Reading content from files in the container
- `set_entrypoint`: Setting the container's entrypoint
- `set_cmd`: Setting the container's command
- `add`: Adding files to the container
- `copy`: Copying files to the container
- `commit`: Committing the container to an image
- `remove`: Removing the container
- `images`: Listing images
- `image_remove`: Removing images
### Image Operations Test
The Image Operations test (`02_image_operations.rhai`) verifies the following functions:
- `image_pull`: Pulling images from registries
- `image_tag`: Tagging images
- `images`: Listing images
- `build`: Building images from Dockerfiles
- `image_remove`: Removing images
The test creates a temporary directory with a Dockerfile for testing the build functionality.
### Container Operations Test
The Container Operations test (`03_container_operations.rhai`) verifies the following functions:
- `reset`: Resetting a Builder by removing its container
- `config`: Configuring container properties
- `run_with_isolation`: Running commands with isolation
- Content operations: Creating and executing scripts in the container
- `commit` with options: Committing a container with additional configuration
## Test Runner
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
1. Checks if Buildah is available before running tests
2. Skips tests if Buildah is not available
3. Contains simplified versions of each test
4. Runs each test in a try/catch block to handle errors
5. Catches and reports any errors
6. Provides a summary of passed, failed, and skipped tests
## Buildah Requirements
These tests require the Buildah tool to be installed and available in the system's PATH. The tests will check for Buildah's availability and skip the tests if it's not found, rather than failing.
## Adding New Tests
To add a new test:
1. Create a new Rhai script in the `src/rhai_tests/buildah` directory
2. Add a new test section to the `run_all_tests.rhai` script
3. Update this documentation to include information about the new test
## Best Practices for Writing Tests
When writing tests for the Buildah module:
1. Always check if Buildah is available before running tests
2. Use unique names for containers and images to avoid conflicts
3. Clean up any containers, images, or files created during testing
4. Use assertions to verify expected behavior
5. Print clear messages about what's being tested
6. Handle errors gracefully
7. Make tests independent of each other
8. Keep tests focused on specific functionality

View File

@ -1,71 +0,0 @@
# Continuous Integration for Rhai Tests
This document describes the continuous integration (CI) workflow for running Rhai tests in the SAL library.
## GitHub Actions Workflow
The SAL project includes a GitHub Actions workflow that automatically runs all Rhai tests whenever changes are made to relevant files. This ensures that the Rhai integration continues to work correctly as the codebase evolves.
### Workflow File
The workflow is defined in `.github/workflows/rhai-tests.yml`.
### Trigger Events
The workflow runs automatically when:
1. Changes are pushed to the `main` or `master` branch that affect:
- Rhai test scripts (`src/rhai_tests/**`)
- Rhai module code (`src/rhai/**`)
- Git module code (`src/git/**`)
- OS module code (`src/os/**`)
- The test runner script (`run_rhai_tests.sh`)
- The workflow file itself (`.github/workflows/rhai-tests.yml`)
2. A pull request is opened or updated that affects the same files.
3. The workflow is manually triggered using the GitHub Actions interface.
### Workflow Steps
The workflow performs the following steps:
1. **Checkout Code**: Checks out the repository code.
2. **Set up Rust**: Installs the Rust toolchain.
3. **Cache Dependencies**: Caches Rust dependencies to speed up builds.
4. **Build herodo**: Builds the `herodo` binary used to run Rhai scripts.
5. **Install Dependencies**: Installs system dependencies like Git and curl.
6. **Run Rhai Tests**: Runs the `run_rhai_tests.sh` script to execute all Rhai tests.
7. **Check for Failures**: Verifies that all tests passed.
### Test Results
The workflow will fail if any Rhai test fails. This prevents changes that break the Rhai integration from being merged.
## Local Testing
Before pushing changes, you can run the same tests locally using the `run_rhai_tests.sh` script:
```bash
./run_rhai_tests.sh
```
This will produce the same test results as the CI workflow, allowing you to catch and fix issues before pushing your changes.
## Logs
The test runner script creates a log file (`run_rhai_tests.log`) that contains the output of all tests. This log is used by the CI workflow to check for test failures.
## Adding New Tests
When adding new tests, make sure they are included in the appropriate module's test runner script (`run_all_tests.rhai`). The CI workflow will automatically run the new tests.
## Troubleshooting
If the CI workflow fails, check the GitHub Actions logs for details. Common issues include:
1. **Missing Dependencies**: Ensure all required dependencies are installed.
2. **Test Failures**: Fix any failing tests.
3. **Build Errors**: Fix any errors in the Rust code.
If you need to modify the workflow, edit the `.github/workflows/rhai-tests.yml` file.

View File

@ -1,81 +0,0 @@
# Git Module Tests
This document describes the test scripts for the Git module in the SAL library. These tests verify the functionality of the Git module's repository management and Git operations.
## Test Structure
The tests are organized into two main scripts:
1. **Basic Git Operations** (`01_git_basic.rhai`): Tests basic Git functionality like creating a GitTree, listing repositories, finding repositories, and cloning repositories.
2. **Git Repository Operations** (`02_git_operations.rhai`): Tests Git operations like pull, reset, commit, and push.
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues.
## Running the Tests
To run all tests, execute the following command from the project root:
```bash
herodo --path src/rhai_tests/git/run_all_tests.rhai
```
To run individual test scripts:
```bash
herodo --path src/rhai_tests/git/01_git_basic.rhai
```
## Test Details
### Basic Git Operations Test
The basic Git operations test (`01_git_basic.rhai`) verifies the following functions:
- `git_tree_new`: Creating a GitTree
- `list`: Listing repositories in a GitTree
- `find`: Finding repositories matching a pattern
- `get`: Getting or cloning a repository
- `path`: Getting the path of a repository
- `has_changes`: Checking if a repository has changes
The test creates a temporary directory, performs operations on it, and then cleans up after itself.
### Git Repository Operations Test
The Git repository operations test (`02_git_operations.rhai`) verifies the following functions:
- `pull`: Pulling changes from a remote repository
- `reset`: Resetting local changes
- `commit`: Committing changes (method existence only)
- `push`: Pushing changes to a remote repository (method existence only)
Note: The test does not actually commit or push changes to avoid modifying remote repositories. It only verifies that the methods exist and can be called.
## Test Runner
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
1. Contains simplified versions of each test
2. Runs each test in a try/catch block to handle errors
3. Catches and reports any errors
4. Provides a summary of passed and failed tests
## Adding New Tests
To add a new test:
1. Create a new Rhai script in the `src/rhai_tests/git` directory
2. Add a new test section to the `run_all_tests.rhai` script
3. Update this documentation to include information about the new test
## Best Practices for Writing Tests
When writing tests for the Git module:
1. Always clean up temporary files and directories
2. Use assertions to verify expected behavior
3. Print clear messages about what's being tested
4. Handle errors gracefully
5. Make tests independent of each other
6. Avoid tests that modify remote repositories
7. Keep tests focused on specific functionality

View File

@ -1,85 +0,0 @@
# Rhai Scripting in SAL
This documentation covers the Rhai scripting integration in the SAL (System Abstraction Layer) library.
## Overview
SAL provides integration with the [Rhai scripting language](https://rhai.rs/), allowing you to use SAL's functionality in scripts. This enables automation of system tasks, testing, and more complex operations without having to write Rust code.
## Modules
SAL exposes the following modules to Rhai scripts:
- [OS Module](os_module_tests.md): File system operations, downloads, and package management
- Process Module: Process management and command execution
- Git Module: Git repository operations
- Text Module: Text processing utilities
- Buildah Module: Container image building
- Nerdctl Module: Container runtime operations
- RFS Module: Remote file system operations
- Redis Client Module: Redis database connection and operations
- PostgreSQL Client Module: PostgreSQL database connection and operations
## Running Rhai Scripts
You can run Rhai scripts using the `herodo` binary:
```bash
herodo --path path/to/script.rhai
```
## Testing
SAL includes test scripts for verifying the functionality of its Rhai integration. These tests are located in the `src/rhai_tests` directory and are organized by module.
- [OS Module Tests](os_module_tests.md): Tests for file system, download, and package management operations
- [Git Module Tests](git_module_tests.md): Tests for Git repository management and operations
- [Process Module Tests](process_module_tests.md): Tests for command execution and process management
- [Redis Client Module Tests](redisclient_module_tests.md): Tests for Redis connection and operations
- [PostgreSQL Client Module Tests](postgresclient_module_tests.md): Tests for PostgreSQL connection and operations
- [Text Module Tests](text_module_tests.md): Tests for text manipulation, normalization, replacement, and template rendering
- [Buildah Module Tests](buildah_module_tests.md): Tests for container and image operations
- [Nerdctl Module Tests](nerdctl_module_tests.md): Tests for container and image operations using nerdctl
- [RFS Module Tests](rfs_module_tests.md): Tests for remote filesystem operations and filesystem layers
- [Running Tests](running_tests.md): Instructions for running all Rhai tests
- [CI Workflow](ci_workflow.md): Continuous integration workflow for Rhai tests
## Examples
For examples of how to use SAL's Rhai integration, see the `examples` directory in the project root. These examples demonstrate various features and use cases.
## Writing Your Own Scripts
When writing Rhai scripts that use SAL:
1. Import the necessary modules (they're automatically registered)
2. Use the functions provided by each module
3. Handle errors appropriately
4. Clean up resources when done
Example:
```rhai
// Simple example of using the OS module
let test_dir = "my_test_dir";
mkdir(test_dir);
if exist(test_dir) {
print(`Directory ${test_dir} created successfully`);
// Create a file
let test_file = test_dir + "/test.txt";
file_write(test_file, "Hello, world!");
// Read the file
let content = file_read(test_file);
print(`File content: ${content}`);
// Clean up
delete(test_dir);
}
```
## API Reference
For detailed information about the functions available in each module, refer to the module-specific documentation.

View File

@ -1,386 +0,0 @@
# Mycelium Tutorial for Rhai
This tutorial explains how to use the Mycelium networking functionality in Rhai scripts. Mycelium is a peer-to-peer networking system that allows nodes to communicate with each other, and the Rhai bindings provide an easy way to interact with Mycelium from your scripts.
## Introduction
The Mycelium module for Rhai provides the following capabilities:
- Getting node information
- Managing peers (listing, adding, removing)
- Viewing routing information
- Sending and receiving messages between nodes
This tutorial will walk you through using these features with example scripts.
## Prerequisites
Before using the Mycelium functionality in Rhai, you need:
1. A running Mycelium node accessible via HTTP
> See https://github.com/threefoldtech/mycelium
2. The Rhai runtime with Mycelium module enabled
## Basic Mycelium Operations
Let's start by exploring the basic operations available in Mycelium using the `mycelium_basic.rhai` example.
### Getting Node Information
To get information about your Mycelium node:
```rhai
// API URL for Mycelium
let api_url = "http://localhost:8989";
// Get node information
print("Getting node information:");
try {
let node_info = mycelium_get_node_info(api_url);
print(`Node subnet: ${node_info.nodeSubnet}`);
print(`Node public key: ${node_info.nodePubkey}`);
} catch(err) {
print(`Error getting node info: ${err}`);
}
```
This code:
1. Sets the API URL for your Mycelium node
2. Calls `mycelium_get_node_info()` to retrieve information about the node
3. Prints the node's subnet and public key
### Managing Peers
#### Listing Peers
To list all peers connected to your Mycelium node:
```rhai
// List all peers
print("\nListing all peers:");
try {
let peers = mycelium_list_peers(api_url);
if peers.is_empty() {
print("No peers connected.");
} else {
for peer in peers {
print(`Peer Endpoint: ${peer.endpoint.proto}://${peer.endpoint.socketAddr}`);
print(` Type: ${peer.type}`);
print(` Connection State: ${peer.connectionState}`);
print(` Bytes sent: ${peer.txBytes}`);
print(` Bytes received: ${peer.rxBytes}`);
}
}
} catch(err) {
print(`Error listing peers: ${err}`);
}
```
This code:
1. Calls `mycelium_list_peers()` to get all connected peers
2. Iterates through the peers and prints their details
#### Adding a Peer
To add a new peer to your Mycelium node:
```rhai
// Add a new peer
print("\nAdding a new peer:");
let new_peer_address = "tcp://65.21.231.58:9651";
try {
let result = mycelium_add_peer(api_url, new_peer_address);
print(`Peer added: ${result.success}`);
} catch(err) {
print(`Error adding peer: ${err}`);
}
```
This code:
1. Specifies a peer address to add
2. Calls `mycelium_add_peer()` to add the peer to your node
3. Prints whether the operation was successful
#### Removing a Peer
To remove a peer from your Mycelium node:
```rhai
// Remove a peer
print("\nRemoving a peer:");
let peer_id = "tcp://65.21.231.58:9651"; // This is the peer we added earlier
try {
let result = mycelium_remove_peer(api_url, peer_id);
print(`Peer removed: ${result.success}`);
} catch(err) {
print(`Error removing peer: ${err}`);
}
```
This code:
1. Specifies the peer ID to remove
2. Calls `mycelium_remove_peer()` to remove the peer
3. Prints whether the operation was successful
### Viewing Routing Information
#### Listing Selected Routes
To list the selected routes in your Mycelium node:
```rhai
// List selected routes
print("\nListing selected routes:");
try {
let routes = mycelium_list_selected_routes(api_url);
if routes.is_empty() {
print("No selected routes.");
} else {
for route in routes {
print(`Subnet: ${route.subnet}`);
print(` Next hop: ${route.nextHop}`);
print(` Metric: ${route.metric}`);
}
}
} catch(err) {
print(`Error listing routes: ${err}`);
}
```
This code:
1. Calls `mycelium_list_selected_routes()` to get all selected routes
2. Iterates through the routes and prints their details
#### Listing Fallback Routes
To list the fallback routes in your Mycelium node:
```rhai
// List fallback routes
print("\nListing fallback routes:");
try {
let routes = mycelium_list_fallback_routes(api_url);
if routes.is_empty() {
print("No fallback routes.");
} else {
for route in routes {
print(`Subnet: ${route.subnet}`);
print(` Next hop: ${route.nextHop}`);
print(` Metric: ${route.metric}`);
}
}
} catch(err) {
print(`Error listing fallback routes: ${err}`);
}
```
This code:
1. Calls `mycelium_list_fallback_routes()` to get all fallback routes
2. Iterates through the routes and prints their details
## Sending Messages
Now let's look at how to send messages using the `mycelium_send_message.rhai` example.
```rhai
// API URL for Mycelium
let api_url = "http://localhost:1111";
// Send a message
print("\nSending a message:");
let destination = "5af:ae6b:dcd8:ffdb:b71:7dde:d3:1033"; // Replace with the actual destination IP address
let topic = "test_topic";
let message = "Hello from Rhai sender!";
let deadline_secs = -10; // Seconds we wait for a reply
try {
print(`Attempting to send message to ${destination} on topic '${topic}'`);
let result = mycelium_send_message(api_url, destination, topic, message, deadline_secs);
print(`result: ${result}`);
print(`Message sent: ${result.success}`);
if result.id != "" {
print(`Message ID: ${result.id}`);
}
} catch(err) {
print(`Error sending message: ${err}`);
}
```
This code:
1. Sets the API URL for your Mycelium node
2. Specifies the destination IP address, topic, message content, and deadline
3. Calls `mycelium_send_message()` to send the message
4. Prints the result, including the message ID if successful
### Important Parameters for Sending Messages
- `api_url`: The URL of your Mycelium node's API
- `destination`: The IP address of the destination node
- `topic`: The topic to send the message on (must match what the receiver is listening for)
- `message`: The content of the message
- `deadline_secs`: Time in seconds to wait for a reply. Use a negative value if you don't want to wait for a reply.
## Receiving Messages
Now let's look at how to receive messages using the `mycelium_receive_message.rhai` example.
```rhai
// API URL for Mycelium
let api_url = "http://localhost:2222";
// Receive messages
print("\nReceiving messages:");
let receive_topic = "test_topic";
let wait_deadline_secs = 100;
print(`Listening for messages on topic '${receive_topic}'...`);
try {
let messages = mycelium_receive_messages(api_url, receive_topic, wait_deadline_secs);
if messages.is_empty() {
// print("No new messages received in this poll.");
} else {
print("Received a message:");
print(` Message id: ${messages.id}`);
print(` Message from: ${messages.srcIp}`);
print(` Topic: ${messages.topic}`);
print(` Payload: ${messages.payload}`);
}
} catch(err) {
print(`Error receiving messages: ${err}`);
}
print("Finished attempting to receive messages.");
```
This code:
1. Sets the API URL for your Mycelium node
2. Specifies the topic to listen on and how long to wait for messages
3. Calls `mycelium_receive_messages()` to receive messages
4. Processes and prints any received messages
### Important Parameters for Receiving Messages
- `api_url`: The URL of your Mycelium node's API
- `receive_topic`: The topic to listen for messages on (must match what the sender is using)
- `wait_deadline_secs`: Time in seconds to wait for messages to arrive. The function will block for this duration if no messages are immediately available.
## Complete Messaging Example
To set up a complete messaging system, you would typically run two instances of Mycelium (node A sender, node B receiver).
1. Run the `mycelium_receive_message.rhai` script to listen for messages. **Fill in the API address of node B**.
2. Run the `mycelium_send_message.rhai` script to send messages. **Fill in the API address of node A, and fill in the overlay address of node B as destination**.
### Setting Up the Receiver
First, start a Mycelium node and run the receiver script:
```rhai
// API URL for Mycelium
let api_url = "http://localhost:2222"; // Your receiver node's API URL
// Receive messages
let receive_topic = "test_topic";
let wait_deadline_secs = 100; // Wait up to 100 seconds for messages
print(`Listening for messages on topic '${receive_topic}'...`);
try {
let messages = mycelium_receive_messages(api_url, receive_topic, wait_deadline_secs);
if messages.is_empty() {
print("No new messages received in this poll.");
} else {
print("Received a message:");
print(` Message id: ${messages.id}`);
print(` Message from: ${messages.srcIp}`);
print(` Topic: ${messages.topic}`);
print(` Payload: ${messages.payload}`);
}
} catch(err) {
print(`Error receiving messages: ${err}`);
}
```
### Setting Up the Sender
Then, on another Mycelium node, run the sender script:
```rhai
// API URL for Mycelium
let api_url = "http://localhost:1111"; // Your sender node's API URL
// Send a message
let destination = "5af:ae6b:dcd8:ffdb:b71:7dde:d3:1033"; // The receiver node's IP address
let topic = "test_topic"; // Must match the receiver's topic
let message = "Hello from Rhai sender!";
let deadline_secs = -10; // Don't wait for a reply
try {
print(`Attempting to send message to ${destination} on topic '${topic}'`);
let result = mycelium_send_message(api_url, destination, topic, message, deadline_secs);
print(`Message sent: ${result.success}`);
if result.id != "" {
print(`Message ID: ${result.id}`);
}
} catch(err) {
print(`Error sending message: ${err}`);
}
```
### Example: setting up 2 different Mycelium peers on same the host and sending/receiving a message
#### Obtain Mycelium
- Download the latest Mycelium binary from https://github.com/threefoldtech/mycelium/releases/
- Or compile from source
#### Setup
- Create two different private key files. Each key file should contain exactely 32 bytes. In this example we'll save these files as `sender.bin` and `receiver.bin`. Note: generate your own 32-byte key files, the values below are just used as examples.
> `echo '9f3d72c1a84be6f027bba94cde015ee839cedb2ac4f2822bfc94449e3e2a1c6a' > sender.bin`
> `echo 'e81c5a76f42bd9a3c73fe0bb2196acdfb6348e99d0b01763a2e57ce3a4e8f5dd' > receiver.bin`
#### Start the nodes
- **Sender**: this node will have the API server hosted on `127.0.0.1:1111` and the JSON-RPC server on `127.0.0.1:8991`.
> `sudo ./mycelium --key-file sender.bin --disable-peer-discovery --disable-quic --no-tun --api-addr 127.0.0.1:1111 --jsonrpc-addr 127.0.0.1:8991`
- **Receiver**: this node will have the API server hosted on `127.0.0.1:2222` and the JSON-RPC server on `127.0.0.1:8992`.
> `sudo ./mycelium --key-file receiver.bin --disable-peer-discovery --disable-quic --no-tun --api-addr 127.0.0.1:2222 --jsonrpc-addr 127.0.0.1:8992 --peers tcp://<UNDERLAY_IP_SENDER>:9651`
- Obtain the Mycelium overlay IP by running `./mycelium --key-file receiver.bin --api-addr 127.0.0.1:2222 inspect`. **Replace this IP as destination in the [mycelium_send_message.rhai](../../../examples/mycelium/mycelium_send_message.rhai) example**.
#### Execute the examples
- First build by executing `./build_herdo.sh` from the SAL root directory
- `cd target/debug`
- Run the sender script: `sudo ./herodo --path ../../examples/mycelium/mycelium_send_message.rhai`
```
Executing: ../../examples/mycelium/mycelium_send_message.rhai
Sending a message:
Attempting to send message to 50e:6d75:4568:366e:f75:2ac3:bbb1:3fdd on topic 'test_topic'
result: #{"id": "bfd47dc689a7b826"}
Message sent:
Message ID: bfd47dc689a7b826
Script executed successfull
```
- Run the receiver script: `sudo ./herodo --path ../../examples/mycelium/mycelium_receive_message.rhai`
```
Executing: ../../examples/mycelium/mycelium_receive_message.rhai
Receiving messages:
Listening for messages on topic 'test_topic'...
Received a message:
Message id: bfd47dc689a7b826
Message from: 45d:26e1:a413:9d08:80ce:71c6:a931:4315
Topic: dGVzdF90b3BpYw==
Payload: SGVsbG8gZnJvbSBSaGFpIHNlbmRlciE=
Finished attempting to receive messages.
Script executed successfully
```
> Decoding the payload `SGVsbG8gZnJvbSBSaGFpIHNlbmRlciE=` results in the expected `Hello from Rhai sender!` message. Mission succesful!

View File

@ -1,116 +0,0 @@
# Nerdctl Module Tests
This document describes the test scripts for the Nerdctl module in the SAL library. These tests verify the functionality of the Nerdctl module's container and image operations.
## Test Structure
The tests are organized into three main scripts:
1. **Container Operations** (`01_container_operations.rhai`): Tests for basic container operations like creating, running, executing commands, and removing containers.
2. **Image Operations** (`02_image_operations.rhai`): Tests for image-related operations like pulling, tagging, listing, building, and removing images.
3. **Container Builder Pattern** (`03_container_builder.rhai`): Tests for the Container Builder pattern, which provides a fluent interface for configuring and running containers.
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues.
## Running the Tests
To run all tests, execute the following command from the project root:
```bash
herodo --path src/rhai_tests/nerdctl/run_all_tests.rhai
```
To run individual test scripts:
```bash
herodo --path src/rhai_tests/nerdctl/01_container_operations.rhai
```
## Test Details
### Container Operations Test
The Container Operations test (`01_container_operations.rhai`) verifies the following functions:
- `nerdctl_container_new`: Creating a new Container
- Container properties: `name`, `container_id`, `image`, `detach`
- `with_image`: Setting the container image
- `with_detach`: Setting detach mode
- `with_env` and `with_envs`: Setting environment variables
- `with_port` and `with_ports`: Setting port mappings
- `with_volume`: Setting volume mounts
- `with_cpu_limit` and `with_memory_limit`: Setting resource limits
- `run`: Running the container
- `exec`: Executing commands in the container
- `logs`: Getting container logs
- `stop`: Stopping the container
- `remove`: Removing the container
### Image Operations Test
The Image Operations test (`02_image_operations.rhai`) verifies the following functions:
- `nerdctl_image_pull`: Pulling images from registries
- `nerdctl_images`: Listing images
- `nerdctl_image_tag`: Tagging images
- `nerdctl_image_build`: Building images from Dockerfiles
- `nerdctl_run_with_name`: Running containers from images
- `nerdctl_stop` and `nerdctl_remove`: Stopping and removing containers
- `nerdctl_image_remove`: Removing images
The test creates a temporary directory with a Dockerfile for testing the build functionality.
### Container Builder Pattern Test
The Container Builder Pattern test (`03_container_builder.rhai`) verifies the following functions:
- `nerdctl_container_from_image`: Creating a container from an image
- `reset`: Resetting container configuration
- `with_detach`: Setting detach mode
- `with_ports`: Setting multiple port mappings
- `with_volumes`: Setting multiple volume mounts
- `with_envs`: Setting multiple environment variables
- `with_network`: Setting network
- `with_cpu_limit` and `with_memory_limit`: Setting resource limits
- `run`: Running the container
- `exec`: Executing commands in the container
- `stop`: Stopping the container
- `remove`: Removing the container
The test also verifies that environment variables and volume mounts work correctly by writing and reading files between the container and the host.
## Test Runner
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
1. Checks if nerdctl is available before running tests
2. Skips tests if nerdctl is not available
3. Contains simplified versions of each test
4. Runs each test in a try/catch block to handle errors
5. Catches and reports any errors
6. Provides a summary of passed, failed, and skipped tests
## Nerdctl Requirements
These tests require the nerdctl tool to be installed and available in the system's PATH. The tests will check for nerdctl's availability and skip the tests if it's not found, rather than failing.
## Adding New Tests
To add a new test:
1. Create a new Rhai script in the `src/rhai_tests/nerdctl` directory
2. Add a new test section to the `run_all_tests.rhai` script
3. Update this documentation to include information about the new test
## Best Practices for Writing Tests
When writing tests for the Nerdctl module:
1. Always check if nerdctl is available before running tests
2. Use unique names for containers and images to avoid conflicts
3. Clean up any containers, images, or files created during testing
4. Use assertions to verify expected behavior
5. Print clear messages about what's being tested
6. Handle errors gracefully
7. Make tests independent of each other
8. Keep tests focused on specific functionality

View File

@ -1,105 +0,0 @@
# OS Module Tests
This document describes the test scripts for the OS module in the SAL library. These tests verify the functionality of the OS module's file system operations, download capabilities, and package management features.
## Test Structure
The tests are organized into three main scripts:
1. **File Operations** (`01_file_operations.rhai`): Tests file system operations like creating, reading, writing, and manipulating files and directories.
2. **Download Operations** (`02_download_operations.rhai`): Tests downloading files from the internet and related operations.
3. **Package Operations** (`03_package_operations.rhai`): Tests package management functionality.
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency on the `run_script` function.
## Running the Tests
To run all tests, execute the following command from the project root:
```bash
# Assume that you have the herodo binary/built into your system
herodo --path src/rhai_tests/os/run_all_tests.rhai
```
To run individual test scripts:
```bash
# Assume that you have the herodo binary/built into your system
herodo --path src/rhai_tests/os/01_file_operations.rhai
```
## Test Details
### File Operations Test
The file operations test (`01_file_operations.rhai`) verifies the following functions:
- `mkdir`: Creating directories
- `file_write`: Writing content to files
- `file_read`: Reading content from files
- `file_size`: Getting file size
- `file_write_append`: Appending content to files
- `copy`: Copying files
- `mv`: Moving files
- `find_file`: Finding a single file matching a pattern
- `find_files`: Finding multiple files matching a pattern
- `find_dir`: Finding a single directory matching a pattern
- `find_dirs`: Finding multiple directories matching a pattern
- `chdir`: Changing the current working directory
- `rsync`: Synchronizing directories
- `delete`: Deleting files and directories
- `exist`: Checking if files or directories exist
The test creates a temporary directory structure, performs operations on it, and then cleans up after itself.
### Download Operations Test
The download operations test (`02_download_operations.rhai`) verifies the following functions:
- `which`: Checking if a command exists in the system PATH
- `cmd_ensure_exists`: Ensuring commands exist
- `download_file`: Downloading a file from a URL
- `chmod_exec`: Making a file executable
The test downloads a small file from GitHub, verifies its content, and then cleans up.
### Package Operations Test
The package operations test (`03_package_operations.rhai`) verifies the following functions:
- `package_platform`: Getting the current platform
- `package_set_debug`: Setting debug mode for package operations
- `package_is_installed`: Checking if a package is installed
- `package_search`: Searching for packages
- `package_list`: Listing installed packages
Note: The test does not verify `package_install`, `package_remove`, `package_update`, or `package_upgrade` as these require root privileges and could modify the system state.
## Test Runner
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
1. Contains simplified versions of each test
2. Runs each test in a try/catch block to handle errors
3. Catches and reports any errors
4. Provides a summary of passed and failed tests
## Adding New Tests
To add a new test:
1. Create a new Rhai script in the `src/rhai_tests/os` directory
2. Add a new test section to the `run_all_tests.rhai` script
3. Update this documentation to include information about the new test
## Best Practices for Writing Tests
When writing tests for the OS module:
1. Always clean up temporary files and directories
2. Use assertions to verify expected behavior
3. Print clear messages about what's being tested
4. Handle errors gracefully
5. Make tests independent of each other
6. Avoid tests that require root privileges when possible
7. Keep tests focused on specific functionality

View File

@ -1,188 +0,0 @@
# PostgreSQL Client Module Tests
The PostgreSQL client module provides functions for connecting to and interacting with PostgreSQL databases. These tests verify the functionality of the module.
## PostgreSQL Client Features
The PostgreSQL client module provides the following features:
1. **Basic PostgreSQL Operations**: Execute queries, fetch results, etc.
2. **Connection Management**: Automatic connection handling and reconnection
3. **Builder Pattern for Configuration**: Flexible configuration with authentication support
4. **PostgreSQL Installer**: Install and configure PostgreSQL using nerdctl
5. **Database Management**: Create databases and execute SQL scripts
## Prerequisites
For basic PostgreSQL operations:
- PostgreSQL server must be running and accessible
- Environment variables should be set for connection details:
- `POSTGRES_HOST`: PostgreSQL server host (default: localhost)
- `POSTGRES_PORT`: PostgreSQL server port (default: 5432)
- `POSTGRES_USER`: PostgreSQL username (default: postgres)
- `POSTGRES_PASSWORD`: PostgreSQL password
- `POSTGRES_DB`: PostgreSQL database name (default: postgres)
For PostgreSQL installer:
- nerdctl must be installed and working
- Docker images must be accessible
- Sufficient permissions to create and manage containers
## Test Files
### 01_postgres_connection.rhai
Tests basic PostgreSQL connection and operations:
- Connecting to PostgreSQL
- Pinging the server
- Creating a table
- Inserting data
- Querying data
- Dropping a table
- Resetting the connection
### 02_postgres_installer.rhai
Tests PostgreSQL installer functionality:
- Installing PostgreSQL using nerdctl
- Creating a database
- Executing SQL scripts
- Checking if PostgreSQL is running
### run_all_tests.rhai
Runs all PostgreSQL client module tests and provides a summary of the results.
## Running the Tests
You can run the tests using the `herodo` command:
```bash
herodo --path src/rhai_tests/postgresclient/run_all_tests.rhai
```
Or run individual tests:
```bash
herodo --path src/rhai_tests/postgresclient/01_postgres_connection.rhai
```
## Available Functions
### Connection Functions
- `pg_connect()`: Connect to PostgreSQL using environment variables
- `pg_ping()`: Ping the PostgreSQL server to check if it's available
- `pg_reset()`: Reset the PostgreSQL client connection
### Query Functions
- `pg_execute(query)`: Execute a query and return the number of affected rows
- `pg_query(query)`: Execute a query and return the results as an array of maps
- `pg_query_one(query)`: Execute a query and return a single row as a map
### Installer Functions
- `pg_install(container_name, version, port, username, password)`: Install PostgreSQL using nerdctl
- `pg_create_database(container_name, db_name)`: Create a new database in PostgreSQL
- `pg_execute_sql(container_name, db_name, sql)`: Execute a SQL script in PostgreSQL
- `pg_is_running(container_name)`: Check if PostgreSQL is running
## Authentication Support
The PostgreSQL client module will support authentication using the builder pattern in a future update.
The backend implementation is ready, but the Rhai bindings are still in development.
When implemented, the builder pattern will support the following configuration options:
- Host: Set the PostgreSQL host
- Port: Set the PostgreSQL port
- User: Set the PostgreSQL username
- Password: Set the PostgreSQL password
- Database: Set the PostgreSQL database name
- Application name: Set the application name
- Connection timeout: Set the connection timeout in seconds
- SSL mode: Set the SSL mode
## Example Usage
### Basic PostgreSQL Operations
```rust
// Connect to PostgreSQL
if (pg_connect()) {
print("Connected to PostgreSQL!");
// Create a table
let create_table_query = "CREATE TABLE IF NOT EXISTS test_table (id SERIAL PRIMARY KEY, name TEXT)";
pg_execute(create_table_query);
// Insert data
let insert_query = "INSERT INTO test_table (name) VALUES ('test')";
pg_execute(insert_query);
// Query data
let select_query = "SELECT * FROM test_table";
let results = pg_query(select_query);
// Process results
for (result in results) {
print(`ID: ${result.id}, Name: ${result.name}`);
}
// Clean up
let drop_query = "DROP TABLE test_table";
pg_execute(drop_query);
}
```
### PostgreSQL Installer
```rust
// Install PostgreSQL
let container_name = "my-postgres";
let postgres_version = "15";
let postgres_port = 5432;
let postgres_user = "myuser";
let postgres_password = "mypassword";
if (pg_install(container_name, postgres_version, postgres_port, postgres_user, postgres_password)) {
print("PostgreSQL installed successfully!");
// Create a database
let db_name = "mydb";
if (pg_create_database(container_name, db_name)) {
print(`Database '${db_name}' created successfully!`);
// Execute a SQL script
let create_table_sql = `
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
);
`;
let result = pg_execute_sql(container_name, db_name, create_table_sql);
print("Table created successfully!");
// Insert data
let insert_sql = "#
INSERT INTO users (name, email) VALUES
('John Doe', 'john@example.com'),
('Jane Smith', 'jane@example.com');
#";
result = pg_execute_sql(container_name, db_name, insert_sql);
print("Data inserted successfully!");
// Query data
let query_sql = "SELECT * FROM users;";
result = pg_execute_sql(container_name, db_name, query_sql);
print(`Query result: ${result}`);
}
}
```

View File

@ -1,79 +0,0 @@
# Process Module Tests
This document describes the test scripts for the Process module in the SAL library. These tests verify the functionality of the Process module's command execution and process management features.
## Test Structure
The tests are organized into two main scripts:
1. **Command Execution** (`01_command_execution.rhai`): Tests command execution functions like `run()` and `which()`.
2. **Process Management** (`02_process_management.rhai`): Tests process management functions like `process_list()` and `process_get()`.
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues.
## Running the Tests
To run all tests, execute the following command from the project root:
```bash
herodo --path src/rhai_tests/process/run_all_tests.rhai
```
To run individual test scripts:
```bash
herodo --path src/rhai_tests/process/01_command_execution.rhai
```
## Test Details
### Command Execution Test
The command execution test (`01_command_execution.rhai`) verifies the following functions:
- `run()`: Running shell commands
- `run().do()`: Executing commands and capturing output
- `run().silent()`: Running commands without displaying output
- `run().ignore_error()`: Running commands that might fail without throwing errors
- `which()`: Finding the path of an executable
The test runs various commands and verifies their output and exit status.
### Process Management Test
The process management test (`02_process_management.rhai`) verifies the following functions:
- `process_list()`: Listing running processes
- `process_get()`: Getting information about a specific process
- Process properties: Accessing process information like PID, name, CPU usage, and memory usage
The test lists running processes and verifies that their properties are accessible.
## Test Runner
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
1. Contains simplified versions of each test
2. Runs each test in a try/catch block to handle errors
3. Catches and reports any errors
4. Provides a summary of passed and failed tests
## Adding New Tests
To add a new test:
1. Create a new Rhai script in the `src/rhai_tests/process` directory
2. Add a new test section to the `run_all_tests.rhai` script
3. Update this documentation to include information about the new test
## Best Practices for Writing Tests
When writing tests for the Process module:
1. Use assertions to verify expected behavior
2. Print clear messages about what's being tested
3. Handle errors gracefully
4. Make tests independent of each other
5. Avoid tests that could disrupt the system (e.g., killing important processes)
6. Keep tests focused on specific functionality
7. Clean up any resources created during testing

View File

@ -1,125 +0,0 @@
# Redis Client Module Tests
This document describes the test scripts for the Redis client module in the SAL library. These tests verify the functionality of the Redis client module's connection management and Redis operations.
## Redis Client Features
The Redis client module provides the following features:
1. **Basic Redis Operations**: SET, GET, DEL, etc.
2. **Hash Operations**: HSET, HGET, HGETALL, HDEL
3. **List Operations**: RPUSH, LPUSH, LLEN, LRANGE
4. **Connection Management**: Automatic connection handling and reconnection
5. **Builder Pattern for Configuration**: Flexible configuration with authentication support
## Test Structure
The tests are organized into two main scripts:
1. **Redis Connection** (`01_redis_connection.rhai`): Tests basic Redis connection and simple operations like PING, SET, GET, and DEL.
2. **Redis Operations** (`02_redis_operations.rhai`): Tests more advanced Redis operations like hash operations (HSET, HGET, HGETALL, HDEL) and list operations (RPUSH, LLEN, LRANGE).
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues.
## Running the Tests
To run all tests, execute the following command from the project root:
```bash
herodo --path src/rhai_tests/redisclient/run_all_tests.rhai
```
To run individual test scripts:
```bash
herodo --path src/rhai_tests/redisclient/01_redis_connection.rhai
```
## Test Details
### Redis Connection Test
The Redis connection test (`01_redis_connection.rhai`) verifies the following functions:
- `redis_ping`: Checking if the Redis server is available
- `redis_set`: Setting a key-value pair
- `redis_get`: Getting a value by key
- `redis_del`: Deleting a key
The test creates a temporary key, performs operations on it, and then cleans up after itself.
### Redis Operations Test
The Redis operations test (`02_redis_operations.rhai`) verifies the following functions:
- Hash operations:
- `redis_hset`: Setting a field in a hash
- `redis_hget`: Getting a field from a hash
- `redis_hgetall`: Getting all fields and values from a hash
- `redis_hdel`: Deleting a field from a hash
- List operations:
- `redis_rpush`: Adding elements to a list
- `redis_llen`: Getting the length of a list
- `redis_lrange`: Getting a range of elements from a list
The test creates temporary keys with a unique prefix, performs operations on them, and then cleans up after itself.
## Test Runner
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
1. Checks if Redis is available before running tests
2. Skips tests if Redis is not available
3. Contains simplified versions of each test
4. Runs each test in a try/catch block to handle errors
5. Catches and reports any errors
6. Provides a summary of passed, failed, and skipped tests
## Redis Server Requirements
These tests require a Redis server to be running and accessible. The tests will attempt to connect to Redis using the following strategy:
1. First, try to connect via Unix socket at `$HOME/hero/var/myredis.sock`
2. If that fails, try to connect via TCP to `127.0.0.1` on the default Redis port (6379)
If no Redis server is available, the tests will be skipped rather than failing.
## Authentication Support
The Redis client module will support authentication using the builder pattern in a future update.
The backend implementation is ready, but the Rhai bindings are still in development.
When implemented, the builder pattern will support the following configuration options:
- Host: Set the Redis host
- Port: Set the Redis port
- Database: Set the Redis database number
- Username: Set the Redis username (Redis 6.0+)
- Password: Set the Redis password
- TLS: Enable/disable TLS
- Unix socket: Enable/disable Unix socket
- Socket path: Set the Unix socket path
- Connection timeout: Set the connection timeout in seconds
## Adding New Tests
To add a new test:
1. Create a new Rhai script in the `src/rhai_tests/redisclient` directory
2. Add a new test section to the `run_all_tests.rhai` script
3. Update this documentation to include information about the new test
## Best Practices for Writing Tests
When writing tests for the Redis client module:
1. Always check if Redis is available before running tests
2. Use a unique prefix for test keys to avoid conflicts
3. Clean up any keys created during testing
4. Use assertions to verify expected behavior
5. Print clear messages about what's being tested
6. Handle errors gracefully
7. Make tests independent of each other
8. Keep tests focused on specific functionality

View File

@ -1,113 +0,0 @@
# RFS Module Tests
This document describes the test scripts for the RFS (Remote File System) module in the SAL library. These tests verify the functionality of the RFS module's mount operations and filesystem layer management.
## Test Structure
The tests are organized into two main scripts:
1. **Mount Operations** (`01_mount_operations.rhai`): Tests for mounting, listing, and unmounting filesystems.
2. **Filesystem Layer Operations** (`02_filesystem_layer_operations.rhai`): Tests for packing, unpacking, listing, and verifying filesystem layers.
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues.
## Running the Tests
To run all tests, execute the following command from the project root:
```bash
herodo --path src/rhai_tests/rfs/run_all_tests.rhai
```
To run individual test scripts:
```bash
herodo --path src/rhai_tests/rfs/01_mount_operations.rhai
```
## Test Details
### Mount Operations Test
The Mount Operations test (`01_mount_operations.rhai`) verifies the following functions:
- `rfs_mount`: Mounting a filesystem
- Tests mounting a local directory with options
- Verifies mount properties (ID, source, target, type)
- `rfs_list_mounts`: Listing mounted filesystems
- Tests listing all mounts
- Verifies that the mounted filesystem is in the list
- `rfs_get_mount_info`: Getting information about a mounted filesystem
- Tests getting information about a specific mount
- Verifies that the mount information is correct
- `rfs_unmount`: Unmounting a specific filesystem
- Tests unmounting a specific mount
- Verifies that the mount is no longer available
- `rfs_unmount_all`: Unmounting all filesystems
- Tests unmounting all mounts
- Verifies that no mounts remain after the operation
The test also verifies that files in the mounted filesystem are accessible and have the correct content.
### Filesystem Layer Operations Test
The Filesystem Layer Operations test (`02_filesystem_layer_operations.rhai`) verifies the following functions:
- `rfs_pack`: Packing a directory into a filesystem layer
- Tests packing a directory with files and subdirectories
- Verifies that the output file is created
- `rfs_list_contents`: Listing the contents of a filesystem layer
- Tests listing the contents of a packed filesystem layer
- Verifies that the list includes all expected files
- `rfs_verify`: Verifying a filesystem layer
- Tests verifying a packed filesystem layer
- Verifies that the layer is valid
- `rfs_unpack`: Unpacking a filesystem layer
- Tests unpacking a filesystem layer to a directory
- Verifies that all files are unpacked correctly with the right content
The test creates a directory structure with files, packs it into a filesystem layer, and then unpacks it to verify the integrity of the process.
## Test Runner
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
1. Checks if RFS is available before running tests
2. Skips tests if RFS is not available
3. Contains simplified versions of each test
4. Runs each test in a try/catch block to handle errors
5. Catches and reports any errors
6. Provides a summary of passed, failed, and skipped tests
## RFS Requirements
These tests require the RFS tool to be installed and available in the system's PATH. The tests will check for RFS's availability and skip the tests if it's not found, rather than failing.
## Adding New Tests
To add a new test:
1. Create a new Rhai script in the `src/rhai_tests/rfs` directory
2. Add a new test section to the `run_all_tests.rhai` script
3. Update this documentation to include information about the new test
## Best Practices for Writing Tests
When writing tests for the RFS module:
1. Always check if RFS is available before running tests
2. Clean up any mounts before and after testing
3. Use unique names for test directories and files to avoid conflicts
4. Clean up any files or directories created during testing
5. Use assertions to verify expected behavior
6. Print clear messages about what's being tested
7. Handle errors gracefully
8. Make tests independent of each other
9. Keep tests focused on specific functionality

View File

@ -1,76 +0,0 @@
# Running Rhai Tests
This document describes how to run the Rhai tests for the SAL library.
## Test Structure
The Rhai tests are organized by module in the `src/rhai_tests` directory:
- `src/rhai_tests/os/`: Tests for the OS module
- `src/rhai_tests/git/`: Tests for the Git module
Each module directory contains:
- Individual test scripts (e.g., `01_file_operations.rhai`)
- A test runner script (`run_all_tests.rhai`) that runs all tests for that module
## Running Tests
### Running All Tests
To run all Rhai tests across all modules, use the provided shell script:
```bash
./run_rhai_tests.sh
```
This script:
1. Finds all test runner scripts in the `src/rhai_tests` directory
2. Runs each test runner
3. Reports the results for each module
4. Provides a summary of all test results
The script will exit with code 0 if all tests pass, or code 1 if any tests fail.
### Running Tests for a Specific Module
To run tests for a specific module, use the `herodo` command with the module's test runner:
```bash
herodo --path src/rhai_tests/os/run_all_tests.rhai
```
### Running Individual Tests
To run a specific test, use the `herodo` command with the test script:
```bash
herodo --path src/rhai_tests/os/01_file_operations.rhai
```
## Test Output
The test output includes:
- Information about what's being tested
- Success or failure messages for each test
- A summary of test results
Successful tests are indicated with a checkmark (✓), while failed tests show an error message.
## Adding New Tests
When adding new tests:
1. Create a new test script in the appropriate module directory
2. Update the module's test runner script to include the new test
3. Update the module's documentation to describe the new test
The `run_rhai_tests.sh` script will automatically find and run the new tests as long as they're included in a module's test runner script.
## Troubleshooting
If tests fail, check the following:
1. Make sure the `herodo` binary is in your PATH
2. Verify that the test scripts have the correct permissions
3. Check for any dependencies required by the tests (e.g., `git` for Git module tests)
4. Look for specific error messages in the test output

View File

@ -1,129 +0,0 @@
# Text Module Tests
This document describes the test scripts for the Text module in the SAL library. These tests verify the functionality of the Text module's text manipulation, normalization, replacement, and template rendering capabilities.
## Test Structure
The tests are organized into four main scripts:
1. **Text Indentation** (`01_text_indentation.rhai`): Tests for the `dedent` and `prefix` functions.
2. **Filename and Path Normalization** (`02_name_path_fix.rhai`): Tests for the `name_fix` and `path_fix` functions.
3. **Text Replacement** (`03_text_replacer.rhai`): Tests for the `TextReplacer` class and its methods.
4. **Template Rendering** (`04_template_builder.rhai`): Tests for the `TemplateBuilder` class and its methods.
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues.
## Running the Tests
To run all tests, execute the following command from the project root:
```bash
herodo --path src/rhai_tests/text/run_all_tests.rhai
```
To run individual test scripts:
```bash
herodo --path src/rhai_tests/text/01_text_indentation.rhai
```
## Test Details
### Text Indentation Test
The text indentation test (`01_text_indentation.rhai`) verifies the following functions:
- `dedent`: Removes common leading whitespace from multiline strings
- Tests basic indentation removal
- Tests mixed indentation handling
- Tests preservation of empty lines
- Tests handling of text without indentation
- Tests single line indentation removal
- `prefix`: Adds a specified prefix to each line of a multiline string
- Tests basic prefix addition
- Tests empty prefix handling
- Tests prefix addition to empty lines
- Tests prefix addition to single line
- Tests non-space prefix addition
- Combination of `dedent` and `prefix` functions
### Filename and Path Normalization Test
The filename and path normalization test (`02_name_path_fix.rhai`) verifies the following functions:
- `name_fix`: Normalizes filenames
- Tests basic name fixing (spaces to underscores, lowercase conversion)
- Tests special character handling
- Tests multiple special character handling
- Tests non-ASCII character removal
- Tests uppercase conversion
- `path_fix`: Applies `name_fix` to the filename portion of a path
- Tests paths ending with `/` (directories)
- Tests single filename handling
- Tests path with filename handling
- Tests relative path handling
- Tests path with special characters in filename
### Text Replacement Test
The text replacement test (`03_text_replacer.rhai`) verifies the following functions:
- `TextReplacer` with simple replacements
- Tests basic replacement
- Tests multiple replacements
- `TextReplacer` with regex replacements
- Tests basic regex replacement
- Tests case-insensitive regex replacement
- `TextReplacer` with file operations
- Tests `replace_file` (read file, apply replacements, return result)
- Tests `replace_file_to` (read file, apply replacements, write to new file)
- Tests `replace_file_in_place` (read file, apply replacements, write back to same file)
### Template Rendering Test
The template rendering test (`04_template_builder.rhai`) verifies the following functions:
- `TemplateBuilder` with file template
- Tests basic template with string variable
- Tests template with multiple variables of different types
- Tests template with array variable
- Tests template with map variable
- `TemplateBuilder` with file operations
- Tests template from file
- Tests `render_to_file` (render template, write to file)
Note: The `template_builder_open` function expects a file path, not a string template. The test creates template files on disk for testing.
## Test Runner
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
1. Contains simplified versions of each test
2. Runs each test in a try/catch block to handle errors
3. Catches and reports any errors
4. Provides a summary of passed and failed tests
## Adding New Tests
To add a new test:
1. Create a new Rhai script in the `src/rhai_tests/text` directory
2. Add a new test section to the `run_all_tests.rhai` script
3. Update this documentation to include information about the new test
## Best Practices for Writing Tests
When writing tests for the Text module:
1. Use the `assert_true` and `assert_eq` functions to verify expected behavior
2. Print clear messages about what's being tested
3. Clean up any temporary files or directories created during testing
4. Handle errors gracefully
5. Make tests independent of each other
6. Keep tests focused on specific functionality

View File

@ -1,8 +0,0 @@
{
"label": "SAL",
"position": 6,
"link": {
"type": "generated-index",
"description": "Tools to work with operating system."
}
}

View File

@ -1,239 +0,0 @@
---
title: "build containers"
sidebar_position: 20
hide_title: true
---
# Container Builder
The Buildah module provides functions for working with containers and images using the Buildah tool. Buildah helps you create and manage container images.
## Builder Pattern
The Buildah module now supports a Builder pattern, which provides a more intuitive and flexible way to work with containers and images.
### Creating a Builder
```js
// Create a builder with a name and base image
let builder = bah_new("my-container", "alpine:latest");
// Access builder properties
let container_id = builder.container_id;
let name = builder.name;
let image = builder.image;
```
### Builder Methods
The Builder object provides the following methods:
- `run(command)`: Run a command in the container
- `run_with_isolation(command, isolation)`: Run a command with specified isolation
- `copy(source, dest)`: Copy files into the container
- `add(source, dest)`: Add files into the container
- `commit(image_name)`: Commit the container to an image
- `remove()`: Remove the container
- `reset()`: Remove the container and clear the container_id
- `config(options)`: Configure container metadata
- `set_entrypoint(entrypoint)`: Set the entrypoint for the container
- `set_cmd(cmd)`: Set the default command for the container
- `debug_mode`: Get or set the debug flag (true/false)
- `write_content(content, dest_path)`: Write content to a file in the container
- `read_content(source_path)`: Read content from a file in the container
- `images()`: List images in local storage
- `image_remove(image)`: Remove an image
- `image_pull(image, tls_verify)`: Pull an image from a registry
- `image_push(image, destination, tls_verify)`: Push an image to a registry
- `image_tag(image, new_name)`: Add a tag to an image
- `build(tag, context_dir, file, isolation)`: Build an image from a Dockerfile
- `build(tag, context_dir, file, isolation)`: Build an image from a Dockerfile
### Example
```js
// Create a builder
let builder = bah_new("my-container", "alpine:latest");
// Enable debug mode to see command output
builder.debug_mode = true;
// Reset the builder to remove any existing container
builder.reset();
// Create a new container
builder = bah_new("my-container", "alpine:latest");
// Run a command
let result = builder.run("echo 'Hello from container'");
println(`Command output: ${result.stdout}`);
// Write content directly to a file in the container
let script_content = `#!/bin/sh
echo "Hello from startup script"
`;
builder.write_content(script_content, "/start.sh");
builder.run("chmod +x /start.sh");
// Set the entrypoint for the container
builder.set_entrypoint("/start.sh");
// Add a file
file_write("test_file.txt", "Test content");
builder.add("test_file.txt", "/");
// Commit to an image
builder.commit("my-custom-image:latest");
// Clean up
builder.remove();
delete("test_file.txt");
```
## Image Information
### Image Properties
When working with images, you can access the following information:
- `id`: The unique identifier for the image
- `names`: A list of names/tags for the image
- `name`: The primary name of the image, or `<none>` if the image has no names
- `size`: The size of the image
- `created`: When the image was created
## Builder Methods
### `bah_new(name, image)`
Creates a new Builder object for working with a container.
**Parameters:**
- `name` (string): The name to give the container
- `image` (string): The name or ID of the image to create the container from
**Returns:** A Builder object if successful.
**Example:**
```js
// Create a new Builder
let builder = bah_new("my-container", "alpine:latest");
```
**Notes:**
- If a container with the given name already exists, it will be reused instead of creating a new one
- The Builder object provides methods for working with the container
### `reset()`
Resets a Builder by removing the container and clearing the container_id. This allows you to start fresh with the same Builder object.
**Returns:** Nothing.
**Example:**
```js
// Create a Builder
let builder = bah_new("my-container", "alpine:latest");
// Reset the Builder to remove the container
builder.reset();
// Create a new container with the same name
builder = bah_new("my-container", "alpine:latest");
```
### `debug_mode`
Get or set the debug flag for the Builder. When debug mode is enabled, all buildah commands will output their stdout/stderr, making it easier to debug issues.
**Example:**
```js
// Create a Builder
let builder = bah_new("my-container", "alpine:latest");
// Enable debug mode
builder.debug_mode = true;
// Run a command with debug output
builder.run("echo 'Hello with debug'");
// Disable debug mode
builder.debug_mode = false;
```
### `set_entrypoint(entrypoint)`
Sets the entrypoint for the container. The entrypoint is the command that will be executed when the container starts.
**Parameters:**
- `entrypoint` (string): The entrypoint command
**Returns:** Command result if successful.
**Example:**
```js
// Create a Builder
let builder = bah_new("my-container", "alpine:latest");
// Set the entrypoint
builder.set_entrypoint("/start.sh");
```
### `set_cmd(cmd)`
Sets the default command for the container. This is used as arguments to the entrypoint.
**Parameters:**
- `cmd` (string): The default command
**Returns:** Command result if successful.
**Example:**
```js
// Create a Builder
let builder = bah_new("my-container", "alpine:latest");
// Set the default command
builder.set_cmd("--verbose");
```
### `write_content(content, dest_path)`
Writes content to a file in the container.
**Parameters:**
- `content` (string): The content to write
- `dest_path` (string): The destination path in the container
**Returns:** Command result if successful.
**Example:**
```js
// Create a Builder
let builder = bah_new("my-container", "alpine:latest");
// Write content to a file
let content = "Hello, world!";
builder.write_content(content, "/hello.txt");
```
### `read_content(source_path)`
Reads content from a file in the container.
**Parameters:**
- `source_path` (string): The source path in the container
**Returns:** The file content as a string if successful.
**Example:**
```js
// Create a Builder
let builder = bah_new("my-container", "alpine:latest");
// Write content to a file
builder.write_content("Hello, world!", "/hello.txt");
// Read content from the file
let content = builder.read_content("/hello.txt");
println(content); // Outputs: Hello, world!
```

View File

@ -1,210 +0,0 @@
---
title: "git"
sidebar_position: 5
hide_title: true
---
# Git
This module provides HeroScript wrappers for the Git functionality in SAL.
> **Note:** The constructor for GitTree has been renamed from `new()` to `gittree_new()` to avoid confusion with other constructors. This makes the interface more explicit and less likely to cause naming conflicts.
## Object-Oriented Design
The Git module follows an object-oriented design with two main classes:
1. **GitTree** - Represents a collection of git repositories under a base path
- Created with `gittree_new(base_path)`
- Methods for listing, finding, and getting repositories
2. **GitRepo** - Represents a single git repository
- Obtained from GitTree's `get()` method
- Methods for common git operations: pull, reset, push, commit
This design allows for a more intuitive and flexible interface, with method chaining for complex operations.
## Creating a GitTree
The GitTree object is the main entry point for git operations. It represents a collection of git repositories under a base path.
```js
// Create a new GitTree with a base path
let git_tree = gittree_new("/root/code");
print(`Created GitTree with base path: /home/user/code`);
```
## Finding Repositories
### List All Repositories
```js
// List all git repositories under the base path
let repos = git_tree.list();
print(`Found ${repos.len()} repositories`);
// Print the repositories
for repo in repos {
print(` - ${repo}`);
}
```
### Find Repositories Matching a Pattern
```js
// Find repositories matching a pattern
// Use a wildcard (*) suffix to find multiple matches
let matching_repos = git_tree.find("my-project*");
print("Matching repositories:");
for repo in matching_repos {
print(` - ${repo}`);
}
// Find a specific repository (must match exactly one)
let specific_repo = git_tree.find("unique-project")[0];
print(`Found specific repository: ${specific_repo}`);
```
## Working with Repositories
### Get Repository Objects
```js
// Get GitRepo objects for repositories matching a pattern
let repos = git_tree.get("my-project*");
print(`Found ${repos.len()} repositories`);
// Get a specific repository
let repo = git_tree.get("unique-project")[0];
print(`Working with repository: ${repo.path()}`);
```
### Clone a Repository
```js
// Clone a repository by URL
// This will clone the repository to the base path of the GitTree
let repos = git_tree.get("https://github.com/username/repo.git");
let repo = repos[0];
print(`Repository cloned to: ${repo.path()}`);
```
### Check for Changes
```js
// Check if a repository has uncommitted changes
let repo = git_tree.get("my-project")[0];
if repo.has_changes() {
print("Repository has uncommitted changes");
} else {
print("Repository is clean");
}
```
## Repository Operations
### Pull Changes
```js
// Pull the latest changes from the remote
// This will fail if there are uncommitted changes
let repo = git_tree.get("my-project")[0];
let result = repo.pull();
print("Repository updated successfully");
```
### Reset Local Changes
```js
// Reset any local changes in the repository
let repo = git_tree.get("my-project")[0];
let result = repo.reset();
print("Repository reset successfully");
```
### Commit Changes
```js
// Commit changes in the repository
let repo = git_tree.get("my-project")[0];
let result = repo.commit("Fix bug in login form");
print("Changes committed successfully");
```
### Push Changes
```js
// Push changes to the remote
let repo = git_tree.get("my-project")[0];
let result = repo.push();
print("Changes pushed successfully");
```
## Method Chaining
The GitRepo methods can be chained together for more complex operations:
```js
// Commit changes and push them to the remote
let repo = git_tree.get("my-project")[0];
let result = repo.commit("Add new feature").push();
print("Changes committed and pushed successfully");
// Reset local changes, pull the latest changes, and commit new changes
let repo = git_tree.get("my-project")[0];
let result = repo.reset().pull().commit("Update dependencies");
print("Repository updated successfully");
```
## Complete Example
```js
// Create a new GitTree
let home_dir = env("HOME");
let git_tree = gittree_new(`${home_dir}/code`);
// Clone a repository
let repos = git_tree.get("https://github.com/username/example-repo.git");
let repo = repos[0];
print(`Cloned repository to: ${repo.path()}`);
// Make some changes (using OS module functions)
let file_path = `${repo.path()}/README.md`;
let content = "# Example Repository\n\nThis is an example repository.";
write_file(file_path, content);
// Commit and push the changes
let result = repo.commit("Update README.md").push();
print("Changes committed and pushed successfully");
// List all repositories
let all_repos = git_tree.list();
print("All repositories:");
for repo_path in all_repos {
print(` - ${repo_path}`);
}
```
## Error Handling
All methods in the Git module return a Result type, which means they can either succeed or fail with an error. If an error occurs, it will be propagated to the HeroScript script as a runtime error.
For example, if you try to clone a repository that doesn't exist:
```js
// Try to clone a non-existent repository
try {
let git_tree = gittree_new("/root/code");
let repos = git_tree.get("https://github.com/nonexistent/repo.git");
print("This will not be executed if the repository doesn't exist");
} catch(err) {
print(`Error: ${err}`); // Will print the error message from git
}
```
Common errors include:
- Invalid URL
- Repository not found
- Authentication failure
- Network issues
- Local changes exist when trying to pull

View File

@ -1,215 +0,0 @@
# Rhai Git Module Manual
## Core Concepts
The Git module in Rhai allows interaction with Git repositories through two main objects:
- **`GitTree`**: Represents a collection of Git repositories under a specified base directory. Use it to manage, find, and access these repositories.
- **`GitRepo`**: Represents a single Git repository. Use it to perform common Git operations like `pull`, `commit`, and `push`.
## Error Handling
Methods performing Git operations (e.g., `pull`, `GitTree.get` when cloning) return a `Result`. If an operation fails and the error is not handled within the Rhai script, the script execution will halt, and Rhai will report the error. The examples below show direct usage, relying on this default error-halting behavior.
## `GitTree` Object
The `GitTree` object is the entry point for working with Git repositories.
### `git_tree_new(base_path: String) -> GitTree`
Creates a `GitTree` instance.
- **Description**: Initializes a `GitTree` to operate within the `base_path`. This directory is where repositories are located or will be cloned. It's created if it doesn't exist.
- **Parameters**:
- `base_path: String` - Path to the directory for Git repositories.
- **Returns**: `GitTree` - A new `GitTree` object. Halts on error (e.g., invalid path).
- **Rhai Example**:
```rhai
let git_tree = git_tree_new("./my_projects");
print("GitTree created.");
// To access the base path from Rhai, a `base_path()` getter would need to be exposed.
// // print(`GitTree base path: ${git_tree.base_path()}`);
```
### `list() -> Array`
Lists names of all Git repositories in the `GitTree`'s `base_path`.
- **Description**: Scans `base_path` for immediate subdirectories that are Git repositories and returns their names.
- **Returns**: `Array` - An array of strings (repository names). Returns an empty array if no repositories are found. Halts on other errors.
- **Rhai Example**:
```rhai
let git_tree = git_tree_new("./my_projects");
let repo_names = git_tree.list();
print(`Found ${repo_names.len()} repositories: ${repo_names}`);
```
### `find(pattern: String) -> Array`
Finds Git repositories matching `pattern` and returns them as `GitRepo` objects.
- **Description**: Searches `base_path` for Git repository subdirectories whose names match the `pattern` (e.g., `*`, `service-*`).
- **Parameters**:
- `pattern: String` - A pattern to match repository names.
- **Returns**: `Array` - An array of `GitRepo` objects. Returns an empty array if no repositories match. Halts on other errors (e.g. invalid pattern).
- **Rhai Example**:
```rhai
let git_tree = git_tree_new("./my_projects");
let api_repos = git_tree.find("api-*");
print(`Found ${api_repos.len()} API repositories.`);
for repo in api_repos {
print(`- Path: ${repo.path()}, Has Changes: ${repo.has_changes()}`);
}
```
### `get(name_or_url: String) -> GitRepo`
Retrieves a single `GitRepo` object by its exact local name or by a remote URL.
- **Description**:
- **Local Name**: If `name_or_url` is an exact subdirectory name in `base_path` (e.g., `"myrepo"`), opens that repository.
- **Remote URL**: If `name_or_url` is a Git URL (e.g., `"https://github.com/user/repo.git"`), it clones the repository (if not present) into `base_path` or opens it if it already exists.
- **Note**: Does not support wildcards for local names. Use `find()` for pattern matching.
- **Parameters**:
- `name_or_url: String` - The exact local repository name or a full Git URL.
- **Returns**: `GitRepo` - A single `GitRepo` object.
- **Halts on error if**:
- The local `name` is not found or is ambiguous.
- The `url` is invalid, or the clone/access operation fails.
- The target is not a valid Git repository.
- **Rhai Examples**:
*Get specific local repository by name:*
```rhai
let git_tree = git_tree_new("./my_projects");
// Assumes "my_service_a" is a git repo in "./my_projects/my_service_a"
// Script halts if "my_service_a" is not found or not a git repo.
let service_a_repo = git_tree.get("my_service_a");
print(`Opened repo: ${service_a_repo.path()}`);
service_a_repo.pull(); // Example operation
```
*Clone or get repository by URL:*
```rhai
let git_tree = git_tree_new("./cloned_repos_dest");
let url = "https://github.com/rhai-script/rhai.git";
// Clones if not present, otherwise opens. Halts on error.
let rhai_repo = git_tree.get(url);
print(`Rhai repository path: ${rhai_repo.path()}`);
print(`Rhai repo has changes: ${rhai_repo.has_changes()}`);
```
## `GitRepo` Object
Represents a single Git repository. Obtained from `GitTree.get()` or `GitTree.find()`.
### `path() -> String`
Returns the full file system path of the repository.
- **Returns**: `String` - The absolute path to the repository's root directory.
- **Rhai Example**:
```rhai
let git_tree = git_tree_new("./my_projects");
// Assumes "my_app" exists and is a Git repository.
// get() will halt if "my_app" is not found.
let app_repo = git_tree.get("my_app");
print(`App repository is at: ${app_repo.path()}`);
```
### `has_changes() -> bool`
Checks if the repository has any uncommitted local changes.
- **Description**: Checks for uncommitted modifications in the working directory or staged changes.
- **Returns**: `bool` - `true` if uncommitted changes exist, `false` otherwise. Halts on error.
- **Rhai Example** (assuming `app_repo` is a `GitRepo` object):
```rhai
if app_repo.has_changes() {
print(`Repository ${app_repo.path()} has uncommitted changes.`);
} else {
print(`Repository ${app_repo.path()} is clean.`);
}
```
### `pull() -> GitRepo`
Pulls latest changes from the remote.
- **Description**: Fetches changes from the default remote and merges them into the current local branch (`git pull`).
- **Returns**: `GitRepo` - The same `GitRepo` object for chaining. Halts on error (e.g., network issues, merge conflicts).
- **Rhai Example** (assuming `app_repo` is a `GitRepo` object):
```rhai
print(`Pulling latest changes for ${app_repo.path()}...`);
app_repo.pull(); // Halts on error
print("Pull successful.");
```
### `reset() -> GitRepo`
Resets local changes. **Caution: Discards uncommitted work.**
- **Description**: Discards local modifications and staged changes, resetting the working directory to match the last commit (`git reset --hard HEAD` or similar).
- **Returns**: `GitRepo` - The same `GitRepo` object for chaining. Halts on error.
- **Rhai Example** (assuming `app_repo` is a `GitRepo` object):
```rhai
print(`Resetting local changes in ${app_repo.path()}...`);
app_repo.reset(); // Halts on error
print("Reset successful.");
```
### `commit(message: String) -> GitRepo`
Commits staged changes.
- **Description**: Performs `git commit -m "message"`. Assumes changes are staged. Behavior regarding auto-staging of tracked files depends on the underlying Rust implementation.
- **Parameters**:
- `message: String` - The commit message.
- **Returns**: `GitRepo` - The same `GitRepo` object for chaining. Halts on error (e.g., nothing to commit).
- **Rhai Example** (assuming `app_repo` is a `GitRepo` object):
```rhai
// Ensure there are changes to commit.
if app_repo.has_changes() {
print(`Committing changes in ${app_repo.path()}...`);
app_repo.commit("Automated commit via Rhai script"); // Halts on error
print("Commit successful.");
} else {
print("No changes to commit.");
}
```
### `push() -> GitRepo`
Pushes committed changes to the remote.
- **Description**: Performs `git push` to the default remote and branch.
- **Returns**: `GitRepo`
```rhai
print(`Pushing changes for ${app_repo.path()}...`);
app_repo.push(); // Halts on error
print("Push successful.");
```
## Chaining Operations
```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.");
// Alternative:
// app_repo.pull();
// if app_repo.has_changes() {
// app_repo.commit("Updates").push();
// }
```

View File

@ -1,10 +0,0 @@
---
title: "intro"
sidebar_position: 1
hide_title: true
---
## HeroScript Script Commands Documentation
The SAL library provides integration with the HeroScript scripting language, allowing you to use powerful system functions within your HeroScript scripts. These functions are organized into modules that provide related functionality.

View File

@ -1,239 +0,0 @@
---
title: "run containers"
sidebar_position: 21
hide_title: true
---
# Container Manager
The Container Manager module provides a comprehensive API for working with containers using nerdctl. It offers a modern builder pattern approach for container management.
## Container Builder Pattern
The Container Builder Pattern allows for fluent, chainable configuration of containers. This pattern makes container creation more readable and maintainable by allowing you to build complex container configurations step by step.
### Creating a Container
Start by creating a new container instance:
```rhai
// Create an empty container with just a name
let container = nerdctl_container_new("my-container");
// Or create a container from an image
let container = nerdctl_container_from_image("my-container", "nginx:latest");
```
### Configuring the Container
Once you have a container instance, you can configure it using the various builder methods:
```rhai
// Configure the container with various options
let container = nerdctl_container_from_image("web-server", "nginx:latest")
.with_port("8080:80") // Map port 8080 to container port 80
.with_volume("/host/path:/container/path") // Mount a volume
.with_env("NGINX_HOST", "localhost") // Set an environment variable
.with_network("bridge") // Set the network
.with_detach(true); // Run in detached mode
```
### Resetting Container Configuration
If you need to reset the container configuration to its default state while keeping the name and image:
```rhai
// Reset the container configuration
let container = nerdctl_container_from_image("web-server", "nginx:latest")
.reset() // Reset all configuration to defaults
.with_port("8080:80") // Start configuring again
.with_detach(true);
```
### Building and Starting the Container
After configuring the container, you can build and start it:
```rhai
// Build the container (creates it but doesn't start it)
let built_container = container.build();
// Start the container
let start_result = built_container.start();
// Check if the container started successfully
if (start_result.success) {
println("Container started successfully!");
} else {
println(`Failed to start container: ${start_result.stderr}`);
}
```
### Container Lifecycle Operations
Once your container is running, you can perform various operations:
```rhai
// Execute a command in the container
let exec_result = container.exec("ls -la");
// Get container logs
let logs = container.logs();
// Stop the container
let stop_result = container.stop();
// Remove the container
let remove_result = container.remove();
```
## Available Builder Methods
The Container Builder Pattern provides the following methods for configuring containers:
| Method | Description | Example |
| -------------------------------------------------------------------------- | ---------------------------------- | --------------------------------------------------------------------------------- |
| `reset()` | Reset configuration to defaults | `.reset()` |
| `with_port(port)` | Add a port mapping | `.with_port("8080:80")` |
| `with_ports(ports_array)` | Add multiple port mappings | `.with_ports(["8080:80", "443:443"])` |
| `with_volume(volume)` | Add a volume mount | `.with_volume("/host/path:/container/path")` |
| `with_volumes(volumes_array)` | Add multiple volume mounts | `.with_volumes(["/host/path1:/container/path1", "/host/path2:/container/path2"])` |
| `with_env(key, value)` | Add an environment variable | `.with_env("NGINX_HOST", "localhost")` |
| `with_envs(env_map)` | Add multiple environment variables | `.with_envs(#{"KEY1": "value1", "KEY2": "value2"})` |
| `with_network(network)` | Set the network | `.with_network("bridge")` |
| `with_network_alias(alias)` | Add a network alias | `.with_network_alias("web-server")` |
| `with_network_aliases(aliases_array)` | Add multiple network aliases | `.with_network_aliases(["web-server", "http-service"])` |
| `with_cpu_limit(cpus)` | Set CPU limit | `.with_cpu_limit("1.0")` |
| `with_cpu_shares(shares)` | Set CPU shares | `.with_cpu_shares("1024")` |
| `with_memory_limit(memory)` | Set memory limit | `.with_memory_limit("512m")` |
| `with_memory_swap_limit(memory_swap)` | Set memory swap limit | `.with_memory_swap_limit("1g")` |
| `with_restart_policy(policy)` | Set restart policy | `.with_restart_policy("unless-stopped")` |
| `with_health_check(cmd)` | Set health check command | `.with_health_check("curl -f http://localhost/ | | exit 1")` |
| `with_health_check_options(cmd, interval, timeout, retries, start_period)` | Set health check with options | `.with_health_check_options("curl -f http://localhost/ | | exit 1", "5s", "3s", 3, "10s")` |
| `with_snapshotter(snapshotter)` | Set snapshotter | `.with_snapshotter("native")` |
| `with_detach(detach)` | Set detach mode | `.with_detach(true)` |
## Complete Example: Web Server
Here's a complete example that demonstrates setting up an Nginx web server using the Container Builder Pattern:
```rhai
// Create a temporary directory for our files
let work_dir = "/tmp/nerdctl";
mkdir(work_dir);
chdir(work_dir);
// Create a custom index.html file
let html_content = `
<!DOCTYPE html>
<html>
<head>
<title>Rhai Nerdctl Demo</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
line-height: 1.6;
color: #333;
}
h1 {
color: #0066cc;
}
</style>
</head>
<body>
<h1>Hello from Rhai Nerdctl!</h1>
<p>This page is served by an Nginx container created using the Rhai nerdctl wrapper.</p>
</body>
</html>
`;
// Write the HTML file
let html_file = `${work_dir}/index.html`;
file_write(html_file, html_content);
// Set up environment variables
let env_map = #{};
env_map["NGINX_HOST"] = "localhost";
env_map["NGINX_PORT"] = "80";
env_map["NGINX_WORKER_PROCESSES"] = "auto";
// Create and configure the container
let container_name = "rhai-nginx-demo";
// First, try to remove any existing container with the same name
nerdctl_remove(container_name);
// Create a container with a rich set of options using the builder pattern
let container = nerdctl_container_from_image(container_name, "nginx:latest")
.reset() // Reset to default configuration
.with_detach(true)
.with_ports(["8080:80"]) // Add multiple ports at once
.with_volumes([`${work_dir}:/usr/share/nginx/html`]) // Mount our work dir
.with_envs(env_map) // Add multiple environment variables at once
.with_network("bridge")
.with_network_aliases(["web-server", "nginx-demo"]) // Add multiple network aliases
.with_cpu_limit("1.0")
.with_memory_limit("512m");
// Build and start the container
let built_container = container.build();
let start_result = built_container.start();
println("The web server is running at http://localhost:8080");
```
## Using Local Images Created with Buildah
When working with images created by Buildah, you may need to take additional steps to ensure nerdctl can find and use these images. This is because Buildah and nerdctl may use different storage backends by default.
### Tagging with localhost Prefix
One approach is to tag the Buildah-created image with a `localhost/` prefix:
```rhai
// Create and commit a container with Buildah
let builder = bah_new("my-container", "alpine:latest");
builder.run("echo 'Hello' > /hello.txt");
builder.commit("my-custom-image:latest");
// Tag the image with localhost prefix for nerdctl compatibility
let local_image_name = "localhost/my-custom-image:latest";
bah_image_tag("my-custom-image:latest", local_image_name);
// Now use the image with nerdctl
let container = nerdctl_container_from_image("my-app", local_image_name)
.with_detach(true)
.build();
```
### Using a Local Registry
For more reliable interoperability, you can push the image to a local registry:
```rhai
// Push the Buildah-created image to a local registry
bah_image_push("my-custom-image:latest", "localhost:5000/my-custom-image:latest", false);
// Pull the image with nerdctl
nerdctl_image_pull("localhost:5000/my-custom-image:latest");
// Use the image
let container = nerdctl_container_from_image("my-app", "localhost:5000/my-custom-image:latest")
.with_detach(true)
.build();
```
## Image Management Functions
The module also provides functions for managing container images:
| Function | Description | Example |
| --------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------- |
| `nerdctl_images()` | List images in local storage | `nerdctl_images()` |
| `nerdctl_image_remove(image)` | Remove an image | `nerdctl_image_remove("nginx:latest")` |
| `nerdctl_image_push(image, destination)` | Push an image to a registry | `nerdctl_image_push("my-image:latest", "registry.example.com/my-image:latest")` |
| `nerdctl_image_tag(image, new_name)` | Add an additional name to a local image | `nerdctl_image_tag("nginx:latest", "my-nginx:latest")` |
| `nerdctl_image_pull(image)` | Pull an image from a registry | `nerdctl_image_pull("nginx:latest")` |
| `nerdctl_image_commit(container, image_name)` | Commit a container to an image | `nerdctl_image_commit("web-server", "my-nginx:latest")` |
| `nerdctl_image_build(tag, context_path)` | Build an image using a Dockerfile | `nerdctl_image_build("my-image:latest", "./")` |

View File

@ -1,359 +0,0 @@
---
title: "os"
sidebar_position: 2
hide_title: true
---
# OS Tools
The OS module provides functions for working with files, directories, and downloading files from the internet.
## File System Functions
### `copy(src, dest)`
Recursively copies a file or directory from source to destination.
**Parameters:**
- `src` (string): The source file or directory path
- `dest` (string): The destination path
**Returns:** A message confirming the copy was successful.
**Example:**
```js
// Copy a file
copy("source.txt", "destination.txt");
// Copy a directory recursively
copy("source_dir", "destination_dir");
```
### `exist(path)`
Checks if a file or directory exists.
**Parameters:**
- `path` (string): The path to check
**Returns:** A boolean value - `true` if the file or directory exists, `false` otherwise.
**Example:**
```js
if exist("config.json") {
// File exists, do something
} else {
// File doesn't exist
}
```
### `find_file(dir, filename)`
Finds a file in a directory with support for wildcards.
**Parameters:**
- `dir` (string): The directory to search in
- `filename` (string): The filename pattern to search for (supports wildcards)
**Returns:** The path of the first matching file.
**Example:**
```js
// Find a specific file
let config_file = find_file("./config", "settings.json");
// Find using wildcards
let log_file = find_file("./logs", "*.log");
```
### `find_files(dir, filename)`
Finds multiple files in a directory recursively with support for wildcards.
**Parameters:**
- `dir` (string): The directory to search in
- `filename` (string): The filename pattern to search for (supports wildcards)
**Returns:** A list of matching file paths.
**Example:**
```js
// Find all JSON files
let json_files = find_files("./data", "*.json");
// Process each file
for file in json_files {
print(`Found file: ${file}`);
}
```
### `find_dir(dir, dirname)`
Finds a directory in a parent directory with support for wildcards.
**Parameters:**
- `dir` (string): The parent directory to search in
- `dirname` (string): The directory name pattern to search for (supports wildcards)
**Returns:** The path of the first matching directory.
**Example:**
```js
// Find a specific directory
let config_dir = find_dir("./", "config");
// Find using wildcards
let version_dir = find_dir("./releases", "v*");
```
### `find_dirs(dir, dirname)`
Finds multiple directories in a parent directory recursively with support for wildcards.
**Parameters:**
- `dir` (string): The parent directory to search in
- `dirname` (string): The directory name pattern to search for (supports wildcards)
**Returns:** A list of matching directory paths.
**Example:**
```js
// Find all version directories
let version_dirs = find_dirs("./releases", "v*");
// Process each directory
for dir in version_dirs {
print(`Found directory: ${dir}`);
}
```
### `delete(path)`
Deletes a file or directory. This function is defensive and doesn't error if the file doesn't exist.
**Parameters:**
- `path` (string): The path of the file or directory to delete
**Returns:** A message confirming the deletion was successful.
**Example:**
```js
// Delete a file
delete("temp.txt");
// Delete a directory
delete("temp_dir");
```
### `mv(src, dest)`
Moves a file or directory from source to destination.
**Parameters:**
- `src` (string): The source path
- `dest` (string): The destination path
**Returns:** A message confirming the move was successful.
**Example:**
```js
// Move a file
mv("file.txt", "new_location/file.txt");
// Move a directory
mv("source_dir", "destination_dir");
// Rename a file
mv("old_name.txt", "new_name.txt");
```
### `mkdir(path)`
Creates a directory and all parent directories. This function is defensive and doesn't error if the directory already exists.
**Parameters:**
- `path` (string): The path of the directory to create
**Returns:** A message confirming the directory was created.
**Example:**
```js
// Create a directory
mkdir("new_dir");
// Create nested directories
mkdir("parent/child/grandchild");
```
### `file_size(path)`
Gets the size of a file in bytes.
**Parameters:**
- `path` (string): The path of the file
**Returns:** The size of the file in bytes.
**Example:**
```js
// Get file size
let size = file_size("large_file.dat");
print(`File size: ${size} bytes`);
```
## File Content Functions
### `file_read(path)`
Reads the contents of a file.
**Parameters:**
- `path` (string): The path of the file to read
**Returns:** The content of the file as a string.
**Example:**
```js
// Read a file
let content = file_read("config.json");
print(`File content: ${content}`);
```
### `file_write(path, content)`
Writes content to a file. Creates the file if it doesn't exist, overwrites if it does.
**Parameters:**
- `path` (string): The path of the file to write to
- `content` (string): The content to write to the file
**Returns:** A message confirming the file was written.
**Example:**
```js
// Write to a file
file_write("config.json", "{\n \"setting\": \"value\"\n}");
```
### `file_write_append(path, content)`
Appends content to a file. Creates the file if it doesn't exist.
**Parameters:**
- `path` (string): The path of the file to append to
- `content` (string): The content to append to the file
**Returns:** A message confirming the content was appended.
**Example:**
```js
// Append to a log file
file_write_append("log.txt", "New log entry\n");
```
### `rsync(src, dest)`
Syncs directories using rsync (or platform equivalent).
**Parameters:**
- `src` (string): The source directory
- `dest` (string): The destination directory
**Returns:** A message confirming the directories were synced.
**Example:**
```js
// Sync directories
rsync("source_dir", "backup_dir");
```
### `chdir(path)`
Changes the current working directory.
**Parameters:**
- `path` (string): The path to change to
**Returns:** A message confirming the directory was changed.
**Example:**
```js
// Change directory
chdir("project/src");
```
## Download Functions
### `download(url, dest, min_size_kb)`
Downloads a file from a URL to a destination directory using the curl command. If the URL ends with a supported archive format, the file will be automatically extracted to the destination directory.
**Supported archive formats for automatic extraction:**
- `.tar.gz`
- `.tgz`
- `.tar`
- `.zip`
**Parameters:**
- `url` (string): The URL to download from
- `dest` (string): The destination directory where the file will be saved or extracted
- `min_size_kb` (integer): The minimum expected file size in kilobytes (for validation)
**Returns:** The path where the file was saved or extracted.
**Example:**
```js
// Download a file to a directory
download("https://example.com/file.zip", "downloads/", 10);
```
### `download_file(url, dest, min_size_kb)`
Downloads a file from a URL to a specific file destination using the curl command. This function is designed for downloading files to a specific path, not for extracting archives.
**Parameters:**
- `url` (string): The URL to download from
- `dest` (string): The destination file path where the file will be saved
- `min_size_kb` (integer): The minimum expected file size in kilobytes (for validation)
**Returns:** The path where the file was saved.
**Example:**
```js
// Download a file to a specific path
download_file("https://example.com/file.txt", "downloads/myfile.txt", 10);
```
### `download_install(url, min_size_kb)`
Downloads a file and installs it if it's a supported package format.
**Supported package formats for automatic installation:**
- `.deb` packages on Debian-based systems
**Parameters:**
- `url` (string): The URL to download from
- `min_size_kb` (integer): The minimum expected file size in kilobytes (for validation)
**Returns:** The path where the file was saved or installed.
**Example:**
```js
// Download and install a package
download_install("https://example.com/package.deb", 1000);
```
### `chmod_exec(path)`
Makes a file executable (equivalent to `chmod +x` in Unix).
**Parameters:**
- `path` (string): The path to the file to make executable
**Returns:** A message confirming the file was made executable.
**Example:**
```js
// Make a file executable
chmod_exec("downloads/script.sh");
```

View File

@ -1,60 +0,0 @@
# 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.");
```

View File

@ -1,338 +0,0 @@
# 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.");
```

View File

@ -1,157 +0,0 @@
# 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}`);
```

View File

@ -1,237 +0,0 @@
---
title: "process"
sidebar_position: 3
hide_title: true
---
# Process Module
The Process module provides functions for running commands and managing processes on your system.
## Command Results
### Command Output Information
When you run a command, you get back information about what happened:
- `stdout`: The normal output of the command
- `stderr`: Any error messages from the command
- `success`: Whether the command worked (true) or failed (false)
- `code`: The exit code (0 usually means success)
### Process Information
When you get information about a running process, you can see:
- `pid`: The process ID number
- `name`: The name of the process
- `memory`: How much memory the process is using
- `cpu`: How much CPU the process is using
## Run Functions
### `run(command)`
Runs a command or multiline script with arguments.
**Parameters:**
- `command` (string): The command to run (can be a single command or a multiline script)
**Returns:** The result of the command, including output and whether it succeeded.
**Example 1: Running a simple command**
```js
// Run a simple command
let result = run("ls -la");
// Check if the command was successful
if result.success {
print(`Command output: ${result.stdout}`);
} else {
print(`Command failed with error: ${result.stderr}`);
}
```
**Example 2: Running a multiline script**
```js
// Create a multiline script using backtick string literals
let setup_script = `
# Create directories
mkdir -p /tmp/test_project
cd /tmp/test_project
# Initialize git repository
git init
echo 'Initial content' > README.md
git add README.md
git config --local user.email 'test@example.com'
git config --local user.name 'Test User'
git commit -m 'Initial commit'
`;
// Execute the multiline script
let result = run(setup_script);
```
### `run_silent(command)`
Runs a command or multiline script with arguments silently (without displaying output).
**Parameters:**
- `command` (string): The command to run
**Returns:** The result of the command, without displaying the output.
**Example:**
```js
// Run a command silently
let result = run_silent("git pull");
// Check the exit code
if result.code == 0 {
print("Git pull successful");
} else {
print(`Git pull failed with code ${result.code}`);
}
```
### `new_run_options()`
Creates a new map with default run options.
**Returns:** A map with the following default options:
- `die` (boolean): `true` - Whether to throw an error if the command fails
- `silent` (boolean): `false` - Whether to suppress command output
- `async_exec` (boolean): `false` - Whether to run the command asynchronously
- `log` (boolean): `false` - Whether to log the command execution
**Example:**
```js
// Create run options
let options = new_run_options();
```
### `run_with_options(command, options)`
Runs a command with options specified in a map.
**Parameters:**
- `command` (string): The command to run
- `options` (map): A map of options created with `new_run_options()`
**Returns:** The result of the command with your custom settings applied.
**Example:**
```js
// Create and customize run options
let options = new_run_options();
options.die = false; // Don't throw an error if the command fails
options.silent = true; // Suppress command output
options.async_exec = false; // Run synchronously
options.log = true; // Log the command execution
// Run a command with options
let result = run_with_options("npm install", options);
```
## Working with Multiline Scripts
The Process module allows you to execute multiline scripts, which is particularly useful for complex operations that require multiple commands to be executed in sequence.
### Creating Multiline Scripts
Multiline scripts can be created using backtick (`) string literals in HeroScript:
```js
let my_script = `
# This is a multiline bash script
echo "Hello, World!"
mkdir -p /tmp/my_project
cd /tmp/my_project
touch example.txt
`;
```
## Process Management Functions
### `which(cmd)`
Checks if a command exists in the PATH.
**Parameters:**
- `cmd` (string): The command to check
**Returns:** The full path to the command if found, or nothing if not found.
**Example:**
```js
// Check if a command exists
let git_path = which("git");
if git_path != () {
print(`Git is installed at: ${git_path}`);
} else {
print("Git is not installed");
}
```
### `kill(pattern)`
Kills processes matching a pattern.
**Parameters:**
- `pattern` (string): The pattern to match process names against
**Returns:** A message confirming the processes were killed.
**Example:**
```js
// Kill all processes with "node" in their name
kill("node");
```
### `process_list(pattern)`
Lists processes matching a pattern (or all processes if the pattern is empty).
**Parameters:**
- `pattern` (string): The pattern to match process names against (can be empty to list all processes)
**Returns:** A list of processes matching your search.
**Example:**
```js
// List all processes
let all_processes = process_list("");
// List processes containing "node" in their name
let node_processes = process_list("node");
// Display process information
for process in node_processes {
print(`PID: ${process.pid}, Name: ${process.name}, Memory: ${process.memory}, CPU: ${process.cpu}`);
}
```
### `process_get(pattern)`
Gets a single process matching the pattern. Throws an error if zero or more than one process matches.
**Parameters:**
- `pattern` (string): The pattern to match process names against
**Returns:** Information about the matching process. This will only work if exactly one process matches.
**Example:**
```js
// Try to get a specific process
try {
let process = process_get("my_app");
print(`Found process: PID=${process.pid}, Name=${process.name}`);
} catch(err) {
print(`Error: ${err}`);
}
```

View File

@ -1,223 +0,0 @@
# 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,154 +0,0 @@
# RFS (Remote File System)
The RFS module provides a Rust wrapper for the RFS tool, which allows mounting remote filesystems locally and managing filesystem layers.
## Overview
RFS (Remote File System) is a tool that enables mounting various types of remote filesystems locally, as well as creating and managing filesystem layers. The SAL library provides a Rust wrapper for RFS with a fluent builder API, making it easy to use in your applications.
## Features
- Mount remote filesystems locally (SSH, S3, WebDAV, etc.)
- List mounted filesystems
- Unmount filesystems
- Pack directories into filesystem layers
- Unpack filesystem layers
- List contents of filesystem layers
- Verify filesystem layers
## Usage in Rust
### Mounting a Filesystem
```rust
use sal::virt::rfs::{RfsBuilder, MountType};
// Create a new RFS builder
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()?;
println!("Mounted filesystem with ID: {}", mount.id);
```
### Listing Mounts
```rust
use sal::virt::rfs::list_mounts;
// List all mounts
let mounts = list_mounts()?;
for mount in mounts {
println!("Mount ID: {}, Source: {}, Target: {}", mount.id, mount.source, mount.target);
}
```
### Unmounting a Filesystem
```rust
use sal::virt::rfs::unmount;
// Unmount a filesystem
unmount("/local/mount/point")?;
```
### Packing a Directory
```rust
use sal::virt::rfs::{PackBuilder, StoreSpec};
// Create store specifications
let store_spec = StoreSpec::new("file")
.with_option("path", "/path/to/store");
// Pack a directory with builder pattern
let result = PackBuilder::new("/path/to/directory", "output.fl")
.with_store_spec(store_spec)
.with_debug(true)
.pack()?;
```
### Unpacking a Filesystem Layer
```rust
use sal::virt::rfs::unpack;
// Unpack a filesystem layer
unpack("input.fl", "/path/to/unpack")?;
```
## Usage in Rhai Scripts
### Mounting a Filesystem
```rhai
// Create a map for mount options
let options = #{
"port": "22",
"identity_file": "/path/to/key",
"readonly": "true"
};
// Mount the directory
let mount = rfs_mount("user@example.com:/remote/path", "/local/mount/point", "ssh", options);
print(`Mounted ${mount.source} to ${mount.target} with ID: ${mount.id}`);
```
### Listing Mounts
```rhai
// List all mounts
let mounts = rfs_list_mounts();
print(`Number of mounts: ${mounts.len()}`);
for mount in mounts {
print(`Mount ID: ${mount.id}, Source: ${mount.source}, Target: ${mount.target}`);
}
```
### Unmounting a Filesystem
```rhai
// Unmount the directory
rfs_unmount("/local/mount/point");
```
### Packing a Directory
```rhai
// Pack the directory
// Store specs format: "file:path=/path/to/store,s3:bucket=my-bucket"
rfs_pack("/path/to/directory", "output.fl", "file:path=/path/to/store");
```
### Unpacking a Filesystem Layer
```rhai
// Unpack the filesystem layer
rfs_unpack("output.fl", "/path/to/unpack");
```
## Mount Types
The RFS module supports various mount types:
- **Local**: Mount a local directory
- **SSH**: Mount a remote directory via SSH
- **S3**: Mount an S3 bucket
- **WebDAV**: Mount a WebDAV server
## Store Specifications
When packing a directory into a filesystem layer, you can specify one or more stores to use. Each store has a type and options:
- **File**: Store files on the local filesystem
- Options: `path` (path to the store)
- **S3**: Store files in an S3 bucket
- Options: `bucket` (bucket name), `region` (AWS region), `access_key`, `secret_key`
## Examples
See the [RFS example script](../../rhaiexamples/rfs_example.rhai) for more examples of how to use the RFS module in Rhai scripts.

View File

@ -1,237 +0,0 @@
# Text Manipulation Tools
The SAL text module provides powerful text manipulation capabilities that can be used from Rhai scripts. These include text replacement (with regex support), template rendering, string normalization, and text formatting utilities.
## Table of Contents
- [Text Replacement](#text-replacement)
- [Template Rendering](#template-rendering)
- [String Normalization](#string-normalization)
- [Text Formatting](#text-formatting)
## Text Replacement
The text replacement tools allow you to perform simple or complex text replacements, with support for regular expressions, case-insensitive matching, and file operations.
### Basic Usage
```rhai
// Create a new text replacer
let replacer = text_replacer_new()
.pattern("foo") // Set the pattern to search for
.replacement("bar") // Set the replacement text
.build(); // Build the replacer
// Apply the replacer to a string
let result = replacer.replace("foo bar foo");
// Result: "bar bar bar"
```
### Advanced Features
#### Regular Expressions
```rhai
// Create a replacer with regex support
let replacer = text_replacer_new()
.pattern("\\bfoo\\b") // Use regex pattern (word boundary)
.replacement("bar")
.regex(true) // Enable regex mode
.build();
// Apply the replacer to a string
let result = replacer.replace("foo foobar");
// Result: "bar foobar" (only replaces whole "foo" words)
```
#### Case-Insensitive Matching
```rhai
// Create a replacer with case-insensitive matching
let replacer = text_replacer_new()
.pattern("foo")
.replacement("bar")
.regex(true)
.case_insensitive(true) // Enable case-insensitive matching
.build();
// Apply the replacer to a string
let result = replacer.replace("FOO foo Foo");
// Result: "bar bar bar"
```
#### Multiple Replacements
```rhai
// Chain multiple replacements
let replacer = text_replacer_new()
.pattern("foo")
.replacement("bar")
.and() // Add another replacement operation
.pattern("baz")
.replacement("qux")
.build();
// Apply the replacer to a string
let result = replacer.replace("foo baz");
// Result: "bar qux"
```
#### File Operations
```rhai
// Create a replacer
let replacer = text_replacer_new()
.pattern("foo")
.replacement("bar")
.build();
// Replace in a file and get the result as a string
let result = replacer.replace_file("input.txt");
// Replace in a file and write back to the same file
replacer.replace_file_in_place("input.txt");
// Replace in a file and write to a new file
replacer.replace_file_to("input.txt", "output.txt");
```
## Template Rendering
The template rendering tools allow you to create and render templates with variables, using the powerful Tera template engine.
### Basic Usage
```rhai
// Create a template builder with a template file
let template = template_builder_open("template.txt")
.add_var("name", "John") // Add a string variable
.add_var("age", 30) // Add a numeric variable
.add_var("items", ["a", "b", "c"]); // Add an array variable
// Render the template
let result = template.render();
// Render to a file
template.render_to_file("output.txt");
```
### Template Variables
You can add variables of various types:
```rhai
let template = template_builder_open("template.txt")
.add_var("name", "John") // String
.add_var("age", 30) // Integer
.add_var("height", 1.85) // Float
.add_var("is_active", true) // Boolean
.add_var("items", ["a", "b", "c"]); // Array
```
### Using Map for Variables
```rhai
// Create a map of variables
let vars = #{
name: "Alice",
place: "Wonderland"
};
// Add all variables from the map
let template = template_builder_open("template.txt")
.add_vars(vars);
```
### Template Syntax
The template engine uses Tera, which supports:
- Variable interpolation: `{{ variable }}`
- Conditionals: `{% if condition %}...{% endif %}`
- Loops: `{% for item in items %}...{% endfor %}`
- Filters: `{{ variable | filter }}`
Example template:
```
Hello, {{ name }}!
{% if show_greeting %}
Welcome to {{ place }}.
{% endif %}
Your items:
{% for item in items %}
- {{ item }}{% if not loop.last %}{% endif %}
{% endfor %}
```
## String Normalization
The string normalization tools help convert strings to consistent formats for use as file names or paths.
### name_fix
Converts a string to a safe, normalized name by:
- Converting to lowercase
- Replacing spaces and special characters with underscores
- Removing non-alphanumeric characters
```rhai
let fixed_name = name_fix("Hello World!");
// Result: "hello_world"
let fixed_name = name_fix("File-Name.txt");
// Result: "file_name.txt"
```
### path_fix
Similar to name_fix, but preserves path separators:
```rhai
let fixed_path = path_fix("/path/to/Hello World!");
// Result: "/path/to/hello_world"
let fixed_path = path_fix("./relative/path/to/DOCUMENT-123.pdf");
// Result: "./relative/path/to/document_123.pdf"
```
## Text Formatting
Tools to help with text formatting and indentation.
### dedent
Removes common leading whitespace from multi-line strings:
```rhai
let indented_text = " line 1
line 2
line 3";
let dedented = dedent(indented_text);
// Result: "line 1
// line 2
// line 3"
```
### prefix
Adds a prefix to every line in a multi-line string:
```rhai
let text = "line 1
line 2
line 3";
let prefixed = prefix(text, " ");
// Result: " line 1
// line 2
// line 3"
```
## Examples
See the [text_tools.rhai](https://github.com/ourworld-tf/herocode/blob/main/sal/src/rhaiexamples/text_tools.rhai) example script for more detailed examples of using these text manipulation tools.

1
example.conf Normal file
View File

@ -0,0 +1 @@
EXAMPLE FILE TO TEST

8
example_Dockerfile Normal file
View File

@ -0,0 +1,8 @@
# syntax=docker/dockerfile:1
FROM node:lts-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
EXPOSE 3000

View File

@ -1,64 +0,0 @@
// 03_process_management.rhai
// Demonstrates process management operations using SAL
// Check if common commands exist
println("Checking if common commands exist:");
let commands = ["ls", "echo", "cat", "grep"];
for cmd in commands {
let exists = which(cmd);
println(` - ${cmd}: ${exists}`);
}
// Run a simple command
println("\nRunning a simple echo command:");
let echo_result = run_command("echo 'Hello from Rhai process management!'");
println(`Command output: ${echo_result.stdout}`);
// The CommandResult type doesn't have an exit_code property
println(`Success: ${echo_result.success}`);
// Run a command silently (no output to console)
println("\nRunning a command silently:");
let silent_result = run_silent("ls -la");
println(`Command success: ${silent_result.success}`);
println(`Command output length: ${silent_result.stdout.len()} characters`);
// Create custom run options
println("\nRunning a command with custom options:");
let options = new_run_options();
options["die"] = false; // Don't return error if command fails
options["silent"] = true; // Suppress output to stdout/stderr
options["async_exec"] = false; // Run synchronously
options["log"] = true; // Log command execution
let custom_result = run("echo 'Custom options test'", options);
println(`Command success: ${custom_result.success}`);
println(`Command output: ${custom_result.stdout}`);
// List processes
println("\nListing processes (limited to 5):");
let processes = process_list("");
let count = 0;
for proc in processes {
if count >= 5 {
break;
}
// Just print the PID since we're not sure what other properties are available
println(` - PID: ${proc.pid}`);
count += 1;
}
println(`Total processes: ${processes.len()}`);
// Run a command that will create a background process
// Note: This is just for demonstration, the process will be short-lived
println("\nRunning a background process:");
let bg_options = new_run_options();
bg_options["async_exec"] = true;
// Fix the command to avoid issues with shell interpretation
let bg_result = run("sleep 1", bg_options);
println("Background process started");
// Wait a moment to let the background process run
run_command("sleep 0.5");
println("Main script continuing while background process runs");
"Process management script completed successfully!"

View File

@ -1,65 +0,0 @@
// 06_file_read_write.rhai
// Demonstrates file read and write operations using SAL
// Create a test directory
let test_dir = "rhai_file_test_dir";
println(`Creating directory: ${test_dir}`);
let mkdir_result = mkdir(test_dir);
println(`Directory creation result: ${mkdir_result}`);
// Define file paths
let test_file = test_dir + "/test_file.txt";
let append_file = test_dir + "/append_file.txt";
// 1. Write to a file
println(`\n--- Writing to file: ${test_file} ---`);
let content = "This is the first line of text.\nThis is the second line of text.";
let write_result = file_write(test_file, content);
println(`Write result: ${write_result}`);
// 2. Read from a file
println(`\n--- Reading from file: ${test_file} ---`);
let read_content = file_read(test_file);
println("File content:");
println(read_content);
// 3. Append to a file
println(`\n--- Creating and appending to file: ${append_file} ---`);
// First create the file with initial content
let initial_content = "Initial content - line 1\nInitial content - line 2\n";
let create_result = file_write(append_file, initial_content);
println(`Create result: ${create_result}`);
// Now append to the file
let append_content = "Appended content - line 3\nAppended content - line 4\n";
let append_result = file_write_append(append_file, append_content);
println(`Append result: ${append_result}`);
// Read the appended file to verify
println(`\n--- Reading appended file: ${append_file} ---`);
let appended_content = file_read(append_file);
println("Appended file content:");
println(appended_content);
// 4. Demonstrate multiple appends
println(`\n--- Demonstrating multiple appends ---`);
for i in range(1, 4) {
// Use a simple counter instead of timestamp to avoid issues
let log_entry = `Log entry #${i} - appended at iteration ${i}\n`;
file_write_append(append_file, log_entry);
println(`Added log entry #${i}`);
}
// Read the final file content
println(`\n--- Final file content after multiple appends ---`);
let final_content = file_read(append_file);
println(final_content);
// Clean up (uncomment to actually delete the files)
// println("\nCleaning up...");
// delete(test_file);
// delete(append_file);
// delete(test_dir);
// println("Cleanup complete");
"File read/write operations script completed successfully!"

View File

@ -1,62 +0,0 @@
// File: /root/code/git.ourworld.tf/herocode/sal/examples/container_example.rs
use std::error::Error;
use sal::virt::nerdctl::Container;
fn main() -> Result<(), Box<dyn Error>> {
// Create a container from an image
println!("Creating container from image...");
let container = Container::from_image("my-nginx", "nginx:latest")?
.with_port("8080:80")
.with_env("NGINX_HOST", "example.com")
.with_volume("/tmp/nginx:/usr/share/nginx/html")
.with_health_check("curl -f http://localhost/ || exit 1")
.with_detach(true)
.build()?;
println!("Container created successfully");
// Execute a command in the container
println!("Executing command in container...");
let result = container.exec("echo 'Hello from container'")?;
println!("Command output: {}", result.stdout);
// Get container status
println!("Getting container status...");
let status = container.status()?;
println!("Container status: {}", status.status);
// Get resource usage
println!("Getting resource usage...");
let resources = container.resources()?;
println!("CPU usage: {}", resources.cpu_usage);
println!("Memory usage: {}", resources.memory_usage);
// Stop and remove the container
println!("Stopping and removing container...");
container.stop()?;
container.remove()?;
println!("Container stopped and removed");
// Get a container by name (if it exists)
println!("\nGetting a container by name...");
match Container::new("existing-container") {
Ok(container) => {
if container.container_id.is_some() {
println!("Found container with ID: {}", container.container_id.as_ref().unwrap());
// Perform operations on the existing container
let status = container.status()?;
println!("Container status: {}", status.status);
} else {
println!("Container exists but has no ID");
}
},
Err(e) => {
println!("Error getting container: {}", e);
}
}
Ok(())
}

View File

@ -1,210 +0,0 @@
// containerd_grpc_setup.rhai
//
// This script sets up a Rust project with gRPC connectivity to containerd
// Following the steps from the instructions document
run("apt-get -y protobuf-compiler ");
// Step 1: Set up project directory
let project_dir = "/tmp/containerd-rust-client";
print(`Setting up project in: ${project_dir}`);
// Clean up any existing directory
if exist(project_dir) {
print("Found existing project directory, removing it...");
delete(project_dir);
}
// Create our project directory
mkdir(project_dir);
// Change to the project directory
chdir(project_dir);
// Step 2: Clone containerd's gRPC proto files
print("Cloning containerd repository to get proto files...");
let git_tree = gittree_new(project_dir);
let repos = git_tree.get("https://github.com/containerd/containerd.git");
let repo = repos[0];
print(`Cloned containerd repository to: ${repo.path()}`);
// Step 3: Create necessary project files
print("Creating Cargo.toml file...");
// Using raw string with # for multiline content
let cargo_toml = #"
[package]
name = "containerd-rust-client"
version = "0.1.0"
edition = "2021"
[dependencies]
tonic = "0.11"
prost = "0.12"
tokio = { version = "1", features = ["full"] }
hyper-unix-connector = "0.2.0"
tower = "0.4"
[build-dependencies]
tonic-build = "0.11"
"#;
file_write("Cargo.toml", cargo_toml);
print("Created Cargo.toml file");
// Step 4: Set up build.rs to compile protos
print("Creating build.rs file...");
let build_rs = #"
fn main() {
println!("cargo:rerun-if-changed=containerd/api/services/images/v1/images.proto");
println!("cargo:rerun-if-changed=containerd/api/services/containers/v1/containers.proto");
tonic_build::configure()
.build_server(false)
.compile(
&[
"containerd/api/services/images/v1/images.proto",
"containerd/api/services/containers/v1/containers.proto",
// Add more proto files as needed
],
&[
"containerd",
"containerd/api",
"containerd/api/types"
],
)
.unwrap();
}
"#;
file_write("build.rs", build_rs);
print("Created build.rs file");
// Step 5: Create src directory and main.rs file
mkdir("src");
// Create a helper function for Unix socket connection
print("Creating src/main.rs file...");
let main_rs = #"
use tonic::transport::{Channel, Endpoint, Uri};
use tower::service_fn;
use std::convert::TryFrom;
// The proto-generated modules will be available after build
// use containerd::services::images::v1::{
// images_client::ImagesClient,
// GetImageRequest,
// };
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Connecting to containerd gRPC...");
// Path to containerd socket
let socket_path = "/run/containerd/containerd.sock";
// Connect to the Unix socket
let channel = unix_socket_channel(socket_path).await?;
// Now we'd create a client and use it
// let mut client = ImagesClient::new(channel);
// let response = client.get(GetImageRequest {
// name: "docker.io/library/ubuntu:latest".to_string(),
// }).await?;
// println!("Image: {:?}", response.into_inner());
println!("Connection to containerd socket established successfully!");
println!("This is a template - uncomment the client code after building.");
Ok(())
}
// Helper function to connect to Unix socket
async fn unix_socket_channel(path: &str) -> Result<Channel, Box<dyn std::error::Error>> {
// Use a placeholder URI since Unix sockets don't have URIs
let endpoint = Endpoint::try_from("http://[::]:50051")?;
// The socket path to connect to
let path_to_connect = path.to_string();
// Create a connector function that connects to the Unix socket
let channel = endpoint
.connect_with_connector(service_fn(move |_: Uri| {
let path = path_to_connect.clone();
async move {
tokio::net::UnixStream::connect(path)
.await
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
}
}))
.await?;
Ok(channel)
}
"#;
file_write("src/main.rs", main_rs);
print("Created src/main.rs file");
// Step 6: Create a README.md file
print("Creating README.md file...");
// Using raw string with # for multiline content containing markdown backticks
let readme = #"# containerd Rust gRPC Client
A Rust client for interacting with containerd via gRPC.
## Prerequisites
- Rust and Cargo installed
- containerd running on your system
## Building
```bash
cargo build
```
## Running
```bash
cargo run
```
## Features
- Connect to containerd via Unix socket
- Query image information
- Work with containers
## Structure
- `src/main.rs` - Example client code
- `build.rs` - Proto compilation script
"#;
file_write("README.md", readme);
print("Created README.md file");
// Step 7: Build the project
print("Building the project...");
let build_result = run("cargo build");
if build_result.success {
print("Project built successfully!");
} else {
print(`Build failed with error: ${build_result.stderr}`);
}
print(`
--------------------------------------
🎉 Setup complete!
Project created at: ${project_dir}
To use the project:
1. cd ${project_dir}
2. cargo run
Note: Make sure containerd is running and the socket exists at /run/containerd/containerd.sock
--------------------------------------
`);

View File

@ -1,105 +0,0 @@
print("\n=== Test download() Functionality ===");
// Create test directory
let download_dir = "/tmp/downloadtest";
// Clean up any previous test files
delete(download_dir);
mkdir(download_dir);
print("Created test directory for downloads at " + download_dir);
// Test URLs
let zip_url = "https://github.com/freeflowuniverse/herolib/archive/refs/tags/v1.0.24.zip";
let targz_url = "https://github.com/freeflowuniverse/herolib/archive/refs/tags/v1.0.24.tar.gz";
let binary_url = "https://github.com/freeflowuniverse/herolib/releases/download/v1.0.24/hero-aarch64-unknown-linux-musl";
// Create destinations
let zip_dest = `${download_dir}/zip`;
let targz_dest = `${download_dir}/targz`;
let binary_dest = `${download_dir}/hero-binary`;
//PART 1
// Download and extract .zip file
print("\nTesting .zip download:");
// Download function now extracts zip files automatically
let result = download(zip_url, zip_dest, 0);
// Check if files were extracted
let file_count = find_files(zip_dest, "*").len();
print(` Files found after extraction: ${file_count}`);
let success_msg = if file_count > 0 { "yes" } else { "no" };
print(` Extraction successful: ${success_msg}`);
//PART 2
// Download and extract .tar.gz file
print("\nTesting .tar.gz download:");
let result = download(targz_url, targz_dest, 0);
// Check if files were extracted (download function should extract tar.gz automatically)
let file_count = find_files(targz_dest, "*").len();
print(` Files found after extraction: ${file_count}`);
let success_msg = if file_count > 100 { "yes" } else { "no" };
print(` Extraction successful: ${success_msg}`);
//PART 3
// Download binary file and check size
print("\nTesting binary download:");
download_file(binary_url, binary_dest, 8000);
// Check file size using our new file_size function
let size_bytes = file_size(binary_dest);
let size_mb = size_bytes / (1024 * 1024);
print(` File size: ${size_mb} MB`);
let size_check = if size_mb > 5 { "yes" } else { "no" };
print(` Size > 5MB: ${size_check}`);
let success_msg = if size_mb >= 8 > 100 { "yes" } else { "no" };
print(` Minimum size check passed:${success_msg}`);
// Clean up test files
delete(download_dir);
print("Cleaned up test directory");
//PART 4
// Test the new download_file function
print("\nTesting download_file function:");
let text_url = "https://raw.githubusercontent.com/freeflowuniverse/herolib/main/README.md";
let text_file_dest = `${download_dir}/README.md`;
// Create the directory again for this test
mkdir(download_dir);
// Download a text file using the new download_file function
let file_result = download_file(text_url, text_file_dest, 0);
print(` File downloaded to: ${file_result}`);
// Check if the file exists and has content
let file_exists = exist(text_file_dest);
print(` File exists: ${file_exists}`);
let file_content = file_read(text_file_dest);
let content_check = if file_content.len() > 100 { "yes" } else { "no" };
print(` File has content: ${content_check}`);
//PART 5
// Test the new chmod_exec function
print("\nTesting chmod_exec function:");
// Create a simple shell script
let script_path = `${download_dir}/test_script.sh`;
file_write(script_path, "#!/bin/sh\necho 'Hello from test script'");
// Make it executable
let chmod_result = chmod_exec(script_path);
print(` ${chmod_result}`);
// Clean up test files again
delete(download_dir);
print("Cleaned up test directory");
print("\nAll Download Tests completed successfully!");
"Download Tests Success"
"Download Tests Success"

View File

@ -1,217 +0,0 @@
// Comprehensive file system operations test script with assertions
print("===== File System Operations Test =====");
// Helper functions for testing
fn assert(condition, message) {
if (condition == false) {
print(`FAILED: ${message}`);
throw `Assertion failed: ${message}`;
} else {
print(`PASSED: ${message}`);
}
}
fn assert_equal(actual, expected, message) {
// Convert numbers to strings before comparison to avoid type issues
let actual_str = actual.to_string();
let expected_str = expected.to_string();
if (actual_str != expected_str) {
print(`FAILED: ${message} - Expected '${expected}', got '${actual}'`);
throw `Assertion failed: ${message}`;
} else {
print(`PASSED: ${message}`);
}
}
fn assert_true(value, message) {
assert(value, message);
}
fn assert_false(value, message) {
assert(value == false, message);
}
// Directory for tests
let test_dir = "/tmp/herodo_test_fs";
let tests_total = 0;
// Setup - create test directory
print("\n=== Setup ===");
if exist(test_dir) {
print(`Test directory exists, removing it first...`);
let result = delete(test_dir);
// Function will throw an error if it fails
assert_false(exist(test_dir), "Test directory should not exist after deletion");
}
// Test mkdir
print("\n=== Test mkdir() ===");
print(`Creating test directory: ${test_dir}`);
tests_total += 1;
let mkdir_result = mkdir(test_dir);
// Now can directly use the returned success message
assert_true(exist(test_dir), "Test directory should exist after creation");
// Test mkdir with nested paths
print(`Creating nested directory: ${test_dir}/subdir/nested`);
tests_total += 1;
let nested_result = mkdir(`${test_dir}/subdir/nested`);
assert_true(exist(`${test_dir}/subdir/nested`), "Nested directory should exist after creation");
// Test duplicate mkdir (should not error)
print(`Creating existing directory again: ${test_dir}`);
tests_total += 1;
let duplicate_result = mkdir(test_dir);
// This should just return a message that directory already exists
// Test file creation using run
print("\n=== Test file creation ===");
let file1 = `${test_dir}/file1.txt`;
let file2 = `${test_dir}/file2.txt`;
let file3 = `${test_dir}/subdir/file3.txt`;
// Create files
print(`Creating test files...`);
let touch_cmd = `touch ${file1} ${file2} ${file3}`;
let touch_result = run(touch_cmd);
tests_total += 1;
assert_true(touch_result.success, "File creation using touch should succeed");
// Verify files exist
print(`Verifying files exist...`);
tests_total += 1;
assert_true(exist(file1), "File 1 should exist after creation");
assert_true(exist(file2), "File 2 should exist after creation");
assert_true(exist(file3), "File 3 should exist after creation");
print("All test files were created successfully");
// Test copy
print("\n=== Test copy() ===");
let copy_file = `${test_dir}/file1_copy.txt`;
print(`Copying ${file1} to ${copy_file}`);
tests_total += 1;
let copy_result = copy(file1, copy_file);
tests_total += 1;
assert_true(exist(copy_file), "Copied file should exist");
// Test directory copy
print(`Copying directory ${test_dir}/subdir to ${test_dir}/subdir_copy`);
tests_total += 1;
let dir_copy_result = copy(`${test_dir}/subdir`, `${test_dir}/subdir_copy`);
tests_total += 1;
assert_true(exist(`${test_dir}/subdir_copy`), "Copied directory should exist");
tests_total += 1;
assert_true(exist(`${test_dir}/subdir_copy/file3.txt`), "Files in copied directory should exist");
// Test file searching
print("\n=== Test find_file() and find_files() ===");
// Create log files for testing search
print("Creating log files for testing search...");
let log_file1 = `${test_dir}/subdir/test1.log`;
let log_file2 = `${test_dir}/subdir/test2.log`;
let log_file3 = `${test_dir}/subdir_copy/test3.log`;
let log_touch_cmd = `touch ${log_file1} ${log_file2} ${log_file3}`;
let log_touch_result = run(log_touch_cmd);
tests_total += 1;
assert_true(log_touch_result.success, "Log file creation should succeed");
// Verify log files exist
print("Verifying log files exist...");
assert_true(exist(log_file1), "Log file 1 should exist after creation");
assert_true(exist(log_file2), "Log file 2 should exist after creation");
assert_true(exist(log_file3), "Log file 3 should exist after creation");
print("All log files were created successfully");
// Test find_file
print("Testing find_file for a single file:");
let found_file = find_file(test_dir, "file1.txt");
tests_total += 1;
assert_true(found_file.to_string().contains("file1.txt"), "find_file should find the correct file");
// Test find_file with wildcard
print("Testing find_file with wildcard:");
let log_file = find_file(test_dir, "*.log");
print(`Found log file: ${log_file}`);
tests_total += 1;
// Check if the log file path contains '.log'
let is_log_file = log_file.to_string().contains(".log");
assert_true(is_log_file, "find_file should find a log file");
// Test find_files
print("Testing find_files with wildcard:");
let log_files = find_files(test_dir, "*.log");
print(`Found ${log_files.len()} log files with find_files`);
tests_total += 1;
assert_equal(log_files.len(), 3, "find_files should find all 3 log files");
// Test find_dir
print("\n=== Test find_dir() and find_dirs() ===");
let found_dir = find_dir(test_dir, "subdir");
tests_total += 1;
assert_true(found_dir.to_string().contains("subdir"), "find_dir should find the correct directory");
// Test find_dirs
let all_dirs = find_dirs(test_dir, "*dir*");
tests_total += 1;
assert_equal(all_dirs.len(), 2, "find_dirs should find both 'subdir' and 'subdir_copy'");
tests_total += 2;
assert_true(all_dirs.contains(`${test_dir}/subdir`), "find_dirs should include the 'subdir' directory");
assert_true(all_dirs.contains(`${test_dir}/subdir_copy`), "find_dirs should include the 'subdir_copy' directory");
// Test sync by manually copying instead of rsync
print("\n=== Test sync() ===");
print(`Copying directory ${test_dir}/subdir to ${test_dir}/sync_target`);
tests_total += 1;
let sync_result = copy(`${test_dir}/subdir`, `${test_dir}/sync_target`);
tests_total += 1;
assert_true(exist(`${test_dir}/sync_target`), "Sync target directory should exist");
// Create test files in sync target to verify they exist
print("Creating test files in sync target...");
let sync_file1 = `${test_dir}/sync_target/sync_test1.log`;
let sync_file2 = `${test_dir}/sync_target/sync_test2.log`;
let sync_touch_cmd = `touch ${sync_file1} ${sync_file2}`;
let sync_touch_result = run(sync_touch_cmd);
tests_total += 1;
assert_true(sync_touch_result.success, "Creating test files in sync target should succeed");
tests_total += 1;
assert_true(exist(sync_file1), "Test files should exist in sync target");
// Test delete
print("\n=== Test delete() ===");
print(`Deleting file: ${copy_file}`);
tests_total += 1;
let delete_file_result = delete(copy_file);
tests_total += 1;
assert_false(exist(copy_file), "File should not exist after deletion");
// Test delete non-existent file (should be defensive)
print(`Deleting non-existent file:`);
tests_total += 1;
let nonexistent_result = delete(`${test_dir}/nonexistent.txt`);
// This should not throw an error, just inform no file was deleted
// Test delete directory
print(`Deleting directory: ${test_dir}/subdir_copy`);
tests_total += 1;
let dir_delete_result = delete(`${test_dir}/subdir_copy`);
tests_total += 1;
assert_false(exist(`${test_dir}/subdir_copy`), "Directory should not exist after deletion");
// Cleanup
print("\n=== Cleanup ===");
print(`Removing test directory: ${test_dir}`);
tests_total += 1;
let cleanup_result = delete(test_dir);
tests_total += 1;
assert_false(exist(test_dir), "Test directory should not exist after cleanup");
// Test summary
print("\n===== Test Summary =====");
print(`Total tests run: ${tests_total}`);
print(`All tests passed!`);
"File System Test Success - All tests passed"

View File

@ -1,32 +0,0 @@
fn dragonfly(){
download("https://github.com/dragonflyoss/dragonfly/releases/download/v2.2.1/dragonfly-2.2.1-linux-amd64.tar.gz", "/tmp/dragonfly", 55000);
copy("/tmp/dragonfly","/root/hero/bin");
delete("/tmp/dragonfly");
}
fn nydus(){
let url="https://github.com/dragonflyoss/nydus/releases/download/v2.3.1/nydus-static-v2.3.1-linux-amd64.tgz";
download(url,"/tmp/nydus",20);
copy("/tmp/nydus/nydus-static/*","/root/hero/bin/");
delete("/tmp/nydus");
}
fn nerdctl(){
let name="nerctl"
let url="https://github.com/containerd/nerdctl/releases/download/v2.0.4/nerdctl-2.0.4-linux-amd64.tar.gz"
download(url,"/tmp/nydus",20);
//copy(`/tmp/{name}/*`,"/root/hero/bin/");
//delete("/tmp/{name}");
let name="containerd"
let url="https://github.com/containerd/containerd/releases/download/v2.0.4/containerd-2.0.4-linux-amd64.tar.gz";
download(url,"/tmp/nydus",20);
//copy(`/tmp/{name}/*`,"/root/hero/bin/");
//delete("/tmp/{name}");
}
nydus();
"done"

View File

@ -1,124 +0,0 @@
## ✅ Step-by-step: Connect Rust to containerd via gRPC
CREATE A rhai script executing the following instructions below
use all rhai script features we have + give priority to what functions we have defined in /src/docs/docs/sal in this repo
---
### 1. 🧱 Install Dependencies
Add this to your `Cargo.toml`:
```toml
[dependencies]
tonic = "0.11"
prost = "0.12"
tokio = { version = "1", features = ["full"] }
[build-dependencies]
tonic-build = "0.11"
```
---
### 2. 📁 Clone containerd's gRPC proto files
```bash
git clone https://github.com/containerd/containerd.git
cd containerd
```
Containerd's API protos are in:
```
api/services/ # gRPC service definitions
api/types/ # message types
```
---
### 3. 📦 Set up `build.rs` to compile protos
In your Rust project root, create a `build.rs` file:
```rust
fn main() {
tonic_build::configure()
.build_server(false)
.compile(
&[
"containerd/api/services/images/v1/images.proto",
"containerd/api/services/containers/v1/containers.proto",
// Add more proto files as needed
],
&[
"containerd/api",
"containerd/api/types"
],
)
.unwrap();
}
```
Make sure to place the `containerd` directory somewhere your build can see — for example, symlink it or move it into your project as `proto/containerd`.
---
### 4. 🧪 Example: Connect to containerd's image service
After `build.rs` compiles the protos, your code can access them like this:
```rust
use tonic::transport::Channel;
use containerd::services::images::v1::{
images_client::ImagesClient,
GetImageRequest,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to containerd's gRPC socket (default path)
let channel = Channel::from_static("http://[::]:50051") // placeholder
.connect()
.await?;
let mut client = ImagesClient::new(channel);
let response = client.get(GetImageRequest {
name: "docker.io/library/ubuntu:latest".to_string(),
}).await?;
println!("Image: {:?}", response.into_inner());
Ok(())
}
```
🔧 Note: containerd uses a **Unix socket**, so replace the channel connection with:
```rust
use tonic::transport::{Endpoint, Uri};
use tower::service_fn;
use hyper_unix_connector::UnixConnector;
let uds = tokio::net::UnixStream::connect("/run/containerd/containerd.sock").await?;
let channel = Endpoint::try_from("http://[::]:50051")?
.connect_with_connector(service_fn(move |_| async move {
Ok::<_, std::io::Error>(uds)
}))
.await?;
```
(We can wrap that part into a helper if you want.)
---
### 5. 🔁 Rebuild the project
Each time you add or change a `.proto`, rebuild to regenerate code:
```bash
cargo clean && cargo build
```

View File

@ -1,113 +0,0 @@
// Example script demonstrating the mypackage management functions
// Set debug mode to true to see detailed output
package_set_debug(true);
// Function to demonstrate mypackage management on Ubuntu
fn demo_ubuntu() {
print("Demonstrating mypackage management on Ubuntu...");
// Update mypackage lists
print("Updating mypackage lists...");
let result = package_update();
print(`Update result: ${result}`);
// Check if a mypackage is installed
let mypackage = "htop";
print(`Checking if ${mypackage} is installed...`);
let is_installed = package_is_installed(mypackage);
print(`${mypackage} is installed: ${is_installed}`);
// Install a mypackage if not already installed
if !is_installed {
print(`Installing ${mypackage}...`);
let install_result = package_install(mypackage);
print(`Install result: ${install_result}`);
}
// List installed packages (limited to first 5 for brevity)
print("Listing installed packages (first 5)...");
let packages = package_list();
for i in 0..min(5, packages.len()) {
print(` - ${packages[i]}`);
}
// Search for packages
let search_term = "editor";
print(`Searching for packages with term '${search_term}'...`);
let search_results = package_search(search_term);
print(`Found ${search_results.len()} packages. First 5 results:`);
for i in 0..min(5, search_results.len()) {
print(` - ${search_results[i]}`);
}
// Remove the mypackage if we installed it
if !is_installed {
print(`Removing ${mypackage}...`);
let remove_result = package_remove(mypackage);
print(`Remove result: ${remove_result}`);
}
}
// Function to demonstrate mypackage management on macOS
fn demo_macos() {
print("Demonstrating mypackage management on macOS...");
// Update mypackage lists
print("Updating mypackage lists...");
let result = package_update();
print(`Update result: ${result}`);
// Check if a mypackage is installed
let mypackage = "wget";
print(`Checking if ${mypackage} is installed...`);
let is_installed = package_is_installed(mypackage);
print(`${mypackage} is installed: ${is_installed}`);
// Install a mypackage if not already installed
if !is_installed {
print(`Installing ${mypackage}...`);
let install_result = package_install(mypackage);
print(`Install result: ${install_result}`);
}
// List installed packages (limited to first 5 for brevity)
print("Listing installed packages (first 5)...");
let packages = package_list();
for i in 0..min(5, packages.len()) {
print(` - ${packages[i]}`);
}
// Search for packages
let search_term = "editor";
print(`Searching for packages with term '${search_term}'...`);
let search_results = package_search(search_term);
print(`Found ${search_results.len()} packages. First 5 results:`);
for i in 0..min(5, search_results.len()) {
print(` - ${search_results[i]}`);
}
// Remove the mypackage if we installed it
if !is_installed {
print(`Removing ${mypackage}...`);
let remove_result = package_remove(mypackage);
print(`Remove result: ${remove_result}`);
}
}
// Detect platform and run the appropriate demo
fn main() {
// Create a PackHero instance to detect the platform
let platform = package_platform();
if platform == "Ubuntu" {
demo_ubuntu();
} else if platform == "MacOS" {
demo_macos();
} else {
print(`Unsupported platform: ${platform}`);
}
}
// Run the main function
main();

View File

@ -1,100 +0,0 @@
//! Example of using the package management module
//!
//! This example demonstrates how to use the package management module
//! to install, remove, and manage packages on different platforms.
use sal::os::package::{PackHero, Platform};
fn main() {
// Create a new PackHero instance
let mut hero = PackHero::new();
// Enable debug output
hero.set_debug(true);
// Detect the platform
let platform = hero.platform();
println!("Detected platform: {:?}", platform);
// Only proceed if we're on a supported platform
if platform == Platform::Unknown {
println!("Unsupported platform. This example only works on Ubuntu and macOS.");
return;
}
// Test package to install/check
let test_package = if platform == Platform::Ubuntu { "wget" } else { "wget" };
// Check if the package is installed
match hero.is_installed(test_package) {
Ok(is_installed) => {
println!("Package {} is installed: {}", test_package, is_installed);
if is_installed {
println!("Package {} is already installed", test_package);
} else {
println!("Package {} is not installed, attempting to install...", test_package);
// Try to install the package
match hero.install(test_package) {
Ok(_) => println!("Successfully installed package {}", test_package),
Err(e) => println!("Failed to install package {}: {}", test_package, e),
}
// Check if it was installed successfully
match hero.is_installed(test_package) {
Ok(is_installed_now) => {
if is_installed_now {
println!("Verified package {} was installed successfully", test_package);
} else {
println!("Package {} was not installed successfully", test_package);
}
},
Err(e) => println!("Error checking if package is installed: {}", e),
}
}
},
Err(e) => println!("Error checking if package is installed: {}", e),
}
// Search for packages
let search_term = "wget";
println!("Searching for packages with term '{}'...", search_term);
match hero.search(search_term) {
Ok(results) => {
println!("Found {} packages matching '{}'", results.len(), search_term);
for (i, package) in results.iter().enumerate().take(5) {
println!(" {}. {}", i + 1, package);
}
if results.len() > 5 {
println!(" ... and {} more", results.len() - 5);
}
},
Err(e) => println!("Error searching for packages: {}", e),
}
// List installed packages
println!("Listing installed packages...");
match hero.list_installed() {
Ok(packages) => {
println!("Found {} installed packages", packages.len());
println!("First 5 installed packages:");
for (i, package) in packages.iter().enumerate().take(5) {
println!(" {}. {}", i + 1, package);
}
if packages.len() > 5 {
println!(" ... and {} more", packages.len() - 5);
}
},
Err(e) => println!("Error listing installed packages: {}", e),
}
// Update package lists
println!("Updating package lists...");
match hero.update() {
Ok(_) => println!("Successfully updated package lists"),
Err(e) => println!("Error updating package lists: {}", e),
}
println!("Package management example completed");
}

View File

@ -1,14 +0,0 @@
let x=0;
while x < 100 {
run(`
find /
ls /
`);
// sleep(100);
x=x+1;
}
"Process Management Test Success - All tests passed"

View File

@ -1,80 +0,0 @@
// Test script for run_silent functionality
print("===== Testing run_silent functionality =====");
// Helper function for assertions
fn assert(condition, message) {
if (condition == false) {
print(`FAILED: ${message}`);
throw `Assertion failed: ${message}`;
} else {
print(`PASSED: ${message}`);
}
}
// Test 1: Basic run_silent with a successful command
print("\n=== Test 1: Basic run_silent with successful command ===");
let silent_result = run_silent("echo This output should not be visible");
print("Result from silent echo command:");
print(` success: ${silent_result.success}`);
print(` code: ${silent_result.code}`);
print(` stdout length: ${silent_result.stdout.len()}`);
print(` stderr length: ${silent_result.stderr.len()}`);
// Assert that the command succeeded
assert(silent_result.success, "Silent command should succeed");
assert(silent_result.code.to_string() == "0", "Silent command should exit with code 0");
// Verify that stdout and stderr are empty as expected
assert(silent_result.stdout == "", "Silent command stdout should be empty");
assert(silent_result.stderr == "", "Silent command stderr should be empty");
// Test 2: Compare with regular run function
print("\n=== Test 2: Compare with regular run function ===");
let normal_result = run("echo This output should be visible");
print("Result from normal echo command:");
print(` success: ${normal_result.success}`);
print(` code: ${normal_result.code}`);
print(` stdout: "${normal_result.stdout.trim()}"`);
print(` stderr length: ${normal_result.stderr.len()}`);
// Assert that the command succeeded
assert(normal_result.success, "Normal command should succeed");
assert(normal_result.code.to_string() == "0", "Normal command should exit with code 0");
// Verify that stdout is not empty
assert(normal_result.stdout != "", "Normal command stdout should not be empty");
assert(normal_result.stdout.contains("visible"), "Normal command stdout should contain our message");
// Test 3: run_silent with a failing command
print("\n=== Test 3: run_silent with a failing command ===");
let silent_fail = run_silent("ls /directory_that_does_not_exist");
print("Result from silent failing command:");
print(` success: ${silent_fail.success}`);
print(` code: ${silent_fail.code}`);
print(` stdout length: ${silent_fail.stdout.len()}`);
print(` stderr length: ${silent_fail.stderr.len()}`);
// Assert that the command failed but didn't throw an error
assert(silent_fail.success == false, "Silent failing command should have success=false");
assert(silent_fail.code.to_string() != "0", "Silent failing command should have non-zero exit code");
// Verify that stdout and stderr are still empty for silent commands
assert(silent_fail.stdout == "", "Silent failing command stdout should be empty");
assert(silent_fail.stderr == "", "Silent failing command stderr should be empty");
// Test 4: Normal run with a failing command
print("\n=== Test 4: Normal run with a failing command ===");
let normal_fail = run("ls /directory_that_does_not_exist");
print("Result from normal failing command:");
print(` success: ${normal_fail.success}`);
print(` code: ${normal_fail.code}`);
print(` stdout length: ${normal_fail.stdout.len()}`);
print(` stderr length: ${normal_fail.stderr.len()}`);
// Assert that the command failed
assert(normal_fail.success == false, "Normal failing command should have success=false");
assert(normal_fail.code.to_string() != "0", "Normal failing command should have non-zero exit code");
// Verify that stderr is not empty for normal commands
assert(normal_fail.stderr != "", "Normal failing command stderr should not be empty");
print("\n===== All run_silent tests passed! =====");
"run_silent function works correctly"

View File

@ -1,149 +0,0 @@
// Comprehensive process management test script with assertions
print("===== Process Management Test =====");
// Helper functions for testing
fn assert(condition, message) {
if (condition == false) {
print(`FAILED: ${message}`);
throw `Assertion failed: ${message}`;
} else {
print(`PASSED: ${message}`);
}
}
fn assert_equal(actual, expected, message) {
// Convert numbers to strings before comparison to avoid type issues
let actual_str = actual.to_string();
let expected_str = expected.to_string();
if (actual_str != expected_str) {
print(`FAILED: ${message} - Expected '${expected}', got '${actual}'`);
throw `Assertion failed: ${message}`;
} else {
print(`PASSED: ${message}`);
}
}
fn assert_true(value, message) {
assert(value, message);
}
fn assert_false(value, message) {
assert(value == false, message);
}
let tests_total = 0;
// Test which() - command existence
print("\n=== Test which() ===");
// Check common commands that should exist
let commands = ["grep"];
print("Testing existence of common commands:");
for cmd in commands {
tests_total += 1;
let exists = which(cmd);
assert_true(exists, `Command '${cmd}' should exist`);
// Check that it returned a path by checking if it's not false
assert_true(exists != false, `Command '${cmd}' path should be a string`);
print(` Command '${cmd}' exists at: ${exists}`);
}
// Check a command that shouldn't exist
print("Testing non-existent command:");
let invalid_cmd = "this_command_should_not_exist_anywhere";
tests_total += 1;
let invalid_exists = which(invalid_cmd);
assert_false(invalid_exists, `Non-existent command '${invalid_cmd}' should return false`);
// Test run() - Basic command execution
print("\n=== Test run() - Basic ===");
print("Running simple echo command:");
let echo_result = run("echo Hello from process test");
tests_total += 1;
assert_true(echo_result.success, "Echo command should succeed");
tests_total += 1;
assert_equal(echo_result.code, 0, "Echo command should exit with code 0");
tests_total += 1;
// Print the actual output for debugging
let expected_text = "Hello from process test";
let actual_text = echo_result.stdout.trim();
print(`Expected text: "${expected_text}"`);
print(`Actual text: "${actual_text}"`);
// Simplify the test - we'll just assert that the command worked successfully
// since we can see the output in the logs
tests_total += 1;
assert_true(echo_result.success, "Echo command should output something");
print("Note: Manual verification confirms the command output looks correct");
print(` stdout: ${echo_result.stdout}`);
// Run a command that fails
print("Running a command that should fail:");
let fail_result = run("ls /directory_that_does_not_exist");
tests_total += 1;
assert_false(fail_result.success, "Command with invalid directory should fail");
tests_total += 1;
// Convert to string to compare
assert_true(fail_result.code.to_string() != "0", "Failed command should have non-zero exit code");
tests_total += 1;
// Check if stderr is not empty by converting to string
assert_true(fail_result.stderr != "", "Failed command should have error output");
print(` stderr: ${fail_result.stderr}`);
print(` exit code: ${fail_result.code}`);
// Test process_list()
print("\n=== Test process_list() ===");
// List all processes
let all_processes = process_list("");
tests_total += 1;
assert_true(all_processes.len() > 0, "At least some processes should be running");
print(`Total processes found: ${all_processes.len()}`);
// Test basic properties of a process
tests_total += 1;
// Check if it has pid property that is a number, which indicates it's a proper object
assert_true(all_processes[0].pid > 0, "Process items should be maps with valid PIDs");
tests_total += 1;
assert_true(all_processes[0].pid > 0, "Process PIDs should be positive numbers");
print("Sample of first few processes:");
// Simple function to find minimum of two values
let max = if all_processes.len() > 3 { 3 } else { all_processes.len() };
if max > 0 {
for i in 0..max {
let proc = all_processes[i];
print(` PID: ${proc.pid}, Name: ${proc.name}`);
}
} else {
print(" No processes found to display");
}
// List specific processes
print("Listing shell-related processes:");
let shell_processes = process_list("sh");
print(`Found ${shell_processes.len()} shell-related processes`);
if shell_processes.len() > 0 {
tests_total += 1;
// Just display the process rather than trying to validate its name
print("First shell process:");
print(` PID: ${shell_processes[0].pid}, Name: ${shell_processes[0].name}`);
assert_true(true, "Found some shell processes");
}
// Note: Background process and kill tests skipped in this version
// as they are more complex and environment-dependent
print("\n=== Process Test Note ===");
print("Skipping background process and kill tests in this version");
print("These tests require specific environment setup and permissions");
// Test summary
print("\n===== Test Summary =====");
print(`Total tests run: ${tests_total}`);
print(`All tests passed!`);
// print(all_processes[0]["cpu"]);
"Process Management Test Success - All tests passed"

View File

@ -1,121 +0,0 @@
// RFS Example Script
// This script demonstrates how to use the RFS wrapper in Rhai
// Mount a local directory
fn mount_local_example() {
print("Mounting a local directory...");
// Create a map for mount options
let options = #{
"readonly": "true"
};
// Mount the directory
let mount = rfs_mount("/source/path", "/target/path", "local", options);
print(`Mounted ${mount.source} to ${mount.target} with ID: ${mount.id}`);
// List all mounts
let mounts = rfs_list_mounts();
print(`Number of mounts: ${mounts.len()}`);
for mount in mounts {
print(`Mount ID: ${mount.id}, Source: ${mount.source}, Target: ${mount.target}`);
}
// Unmount the directory
rfs_unmount("/target/path");
print("Unmounted the directory");
}
// Pack a directory into a filesystem layer
fn pack_example() {
print("Packing a directory into a filesystem layer...");
// Pack the directory
// Store specs format: "file:path=/path/to/store,s3:bucket=my-bucket"
rfs_pack("/path/to/directory", "output.fl", "file:path=/path/to/store");
print("Directory packed successfully");
// List the contents of the filesystem layer
let contents = rfs_list_contents("output.fl");
print("Contents of the filesystem layer:");
print(contents);
// Verify the filesystem layer
let is_valid = rfs_verify("output.fl");
print(`Is the filesystem layer valid? ${is_valid}`);
// Unpack the filesystem layer
rfs_unpack("output.fl", "/path/to/unpack");
print("Filesystem layer unpacked successfully");
}
// SSH mount example
fn mount_ssh_example() {
print("Mounting a remote directory via SSH...");
// Create a map for mount options
let options = #{
"port": "22",
"identity_file": "/path/to/key",
"readonly": "true"
};
// Mount the directory
let mount = rfs_mount("user@example.com:/remote/path", "/local/mount/point", "ssh", options);
print(`Mounted ${mount.source} to ${mount.target} with ID: ${mount.id}`);
// Get mount info
let info = rfs_get_mount_info("/local/mount/point");
print(`Mount info: ${info}`);
// Unmount the directory
rfs_unmount("/local/mount/point");
print("Unmounted the directory");
}
// S3 mount example
fn mount_s3_example() {
print("Mounting an S3 bucket...");
// Create a map for mount options
let options = #{
"region": "us-east-1",
"access_key": "your-access-key",
"secret_key": "your-secret-key"
};
// Mount the S3 bucket
let mount = rfs_mount("s3://my-bucket", "/mnt/s3", "s3", options);
print(`Mounted ${mount.source} to ${mount.target} with ID: ${mount.id}`);
// Unmount the S3 bucket
rfs_unmount("/mnt/s3");
print("Unmounted the S3 bucket");
}
// Unmount all example
fn unmount_all_example() {
print("Unmounting all filesystems...");
// Unmount all filesystems
rfs_unmount_all();
print("All filesystems unmounted");
}
// Run the examples
// Note: These are commented out to prevent accidental execution
// Uncomment the ones you want to run
// mount_local_example();
// pack_example();
// mount_ssh_example();
// mount_s3_example();
// unmount_all_example();
print("RFS example script completed");

View File

@ -1,75 +0,0 @@
// Master test script that runs all herodo tests
// Use this script to verify all functionality in one go
print("===== HERODO COMPREHENSIVE TEST SUITE =====");
print("Running all test scripts to verify the herodo package functionality.\n");
// Track test results
let passed = 0;
let failed = 0;
let tests = [];
// Helper function to run a test script and report the result
fn run_test(name, script_path) {
print(`\n===== RUNNING TEST: ${name} =====`);
print(`Script: ${script_path}`);
print("----------------------------------------");
// The actual implementation would use an import/include mechanism
// But for our limited demo, we'll use descriptive placeholder
print("*Running test script...*");
print(`*See output by running './target/debug/herodo ${script_path}'*`);
print("*This is a meta-script for test organization*");
print("----------------------------------------");
print(`Test ${name} conceptually completed.`);
// Add to the tests list
let test = #{ name: name, path: script_path, status: "PASS" };
tests.push(test);
passed += 1;
}
// Run all individual test scripts
print("\n=== Filesystem Tests ===");
run_test("File System", "src/herodo/scripts/fs_test.rhai");
print("\n=== Process Management Tests ===");
run_test("Process Management", "src/herodo/scripts/process_test.rhai");
run_test("Run Command", "src/herodo/scripts/run_test.rhai");
print("\n=== Git and Download Tests ===");
run_test("Git Operations", "src/herodo/scripts/git_test.rhai");
print("\n=== Sample/Integration Tests ===");
run_test("Sample Integration", "src/herodo/scripts/sample.rhai");
// Print test summary
print("\n\n===== TEST SUMMARY =====");
print(`Total tests: ${tests.len()}`);
print(`Passed: ${passed}`);
print(`Failed: ${failed}`);
// List all tests and their status
print("\nTest Details:");
print("---------------------------------");
print("| Test Name | Status |");
print("---------------------------------");
for test in tests {
let name_padded = test.name.pad_right(20, " ");
print(`| ${name_padded} | ${test.status} |`);
}
print("---------------------------------");
if failed == 0 {
print("\nAll tests passed! The herodo package is working correctly.");
} else {
print("\nSome tests failed. Please check the individual test scripts for details.");
}
print("\nTo run individual tests, use:");
for test in tests {
print(`./target/debug/herodo ${test.path}`);
}
"All Tests Complete"

View File

@ -1,72 +0,0 @@
// Test script for the run command functionality
print("===== Run Command Test =====");
// Test single command
print("\n=== Single Command Execution ===");
let result = run("echo Hello, World!");
print(`Command stdout: ${result.stdout}`);
print(`Command stderr: ${result.stderr}`);
print(`Command success: ${result.success}`);
print(`Command exit code: ${result.code}`);
// Test command with arguments
print("\n=== Command With Arguments ===");
let ls_result = run("ls -la /tmp");
// Use string truncation by direct manipulation instead of substr
let ls_output = if ls_result.stdout.len() > 100 {
ls_result.stdout[0..100] + "..."
} else {
ls_result.stdout
};
print(`ls -la /tmp stdout: ${ls_output}`);
print(`ls success: ${ls_result.success}`);
// Test command that doesn't exist
print("\n=== Non-existent Command ===");
let bad_result = run("command_that_doesnt_exist");
print(`Bad command success: ${bad_result.success}`);
print(`Bad command error: ${bad_result.stderr}`);
// Test command with environment variables
print("\n=== Command With Environment Variables ===");
let home_result = run("echo $HOME");
print(`Home directory: ${home_result.stdout}`);
// Test multiline script
print("\n=== Multiline Script Execution ===");
let script = `
# This is a multiline script
echo "Line 1"
echo "Line 2"
echo "Line 3"
# Show the date
date
# List files in current directory
ls -la | head -n 5
`;
print("Executing multiline script:");
let script_result = run(script);
print("Script output:");
print(script_result.stdout);
// Test script with indentation (to test dedenting)
print("\n=== Indented Script (Testing Dedent) ===");
let indented_script = `
# This script has extra indentation
echo "This line has extra indentation"
echo "This line also has extra indentation"
echo "This line has normal indentation"
`;
print("Executing indented script:");
let indented_result = run(indented_script);
print("Indented script output:");
print(indented_result.stdout);
print("\n===== Run Command Test Completed =====");
"Success"

View File

@ -1,82 +0,0 @@
// This is a sample Rhai script demonstrating the Herodo module functionality
// It shows the use of file system, process management, and git operations
print("===== Herodo Sample Script =====");
// File System Operations ===========================================
print("\n===== File System Operations =====");
// Check if directory exists and make it if not
if !exist("./test_dir") {
print("Creating test directory...");
mkdir("./test_dir");
}
// Write a test file
print("Writing test file...");
let content = "This is a test file created by Herodo";
let file_path = "./test_dir/test.txt";
run(`echo "${content}" > ${file_path}`);
// Check existence
print(`File exists: ${exist(file_path)}`);
// Copy file
print("Copying file...");
let copy_path = "./test_dir/test_copy.txt";
copy(file_path, copy_path);
print(`Copy exists: ${exist(copy_path)}`);
// Show directory contents
print("Directory contents:");
print(run(`ls -la ./test_dir`).stdout);
// Process Management ==============================================
print("\n===== Process Management =====");
// Check if a command exists
print(`ls command exists: ${which("ls")}`);
print(`invalid command exists: ${which("thiscommanddoesnotexist")}`);
// Run a command and capture output
print("Running echo command:");
let echo_result = run("echo Hello from Herodo!");
print(` stdout: ${echo_result.stdout}`);
print(` success: ${echo_result.success}`);
// Run a multiline script
print("Running multiline script:");
let script = `
echo "Line 1"
echo "Line 2"
echo "Line 3"
`;
let script_result = run(script);
print(` stdout: ${script_result.stdout}`);
// List processes (limited to avoid large output)
print("Listing processes containing 'sh':");
let processes = process_list("sh");
if processes.len() > 0 {
print(`Found ${processes.len()} processes`);
let sample_process = processes[0];
print(` Sample: PID=${sample_process.pid}, Name=${sample_process.name}`);
} else {
print("No processes found matching 'sh'");
}
// Git and Download Operations ====================================
print("\n===== Git and Download Operations =====");
// Check if we can download a file (without actually downloading)
print("Download operations available:");
print(` download() function available: true`);
// Clean up test directory
print("\n===== Cleanup =====");
print("Deleting test directory...");
delete("./test_dir");
print(`Directory exists after deletion: ${exist("./test_dir")}`);
print("\nTest script completed successfully!");
"Success" // Return value

View File

@ -1,36 +0,0 @@
// Create a bash script to set up the test environment
let setup_script = `
# Configure git to suppress the default branch name warning
git config --global advice.initDefaultBranch false
rm -rf /tmp/code
mkdir -p /tmp/code
cd /tmp/code
mkdir -p myserver.com/myaccount/repogreen
mkdir -p myserver.com/myaccount/repored
cd myserver.com/myaccount/repogreen
git init
echo 'Initial test file' > test.txt
git add test.txt
git config --local user.email 'test@example.com'
git config --local user.name 'Test User'
git commit -m 'Initial commit'
cd /tmp/code/myserver.com/myaccount/repored
git init
echo 'Initial test file' > test2.txt
git add test2.txt
git config --local user.email 'test@example.com'
git config --local user.name 'Test User'
git commit -m 'Initial commit'
# now we have 2 repos
`;
// Run the setup script
let result = run(setup_script);

View File

@ -1,162 +0,0 @@
// text_tools.rhai
// Example script demonstrating the text tools functionality
// ===== TextReplacer Examples =====
println("===== TextReplacer Examples =====");
// Create a temporary file for testing
let temp_file = "text_replacer_test.txt";
file_write(temp_file, "This is a foo bar example with FOO and foo occurrences.\nAnother line with foo and bar.");
// Example 1: Simple replacement
println("\n--- Example 1: Simple replacement ---");
let replacer = text_replacer_new()
.pattern("foo")
.replacement("REPLACED")
.build();
let result = replacer.replace("foo bar foo");
println(`Result: ${result}`); // Should output: "REPLACED bar REPLACED"
// Example 2: Multiple replacements in one chain
println("\n--- Example 2: Multiple replacements in one chain ---");
let replacer = text_replacer_new()
.pattern("foo").replacement("AAA")
.pattern("bar").replacement("BBB")
.build();
let result = replacer.replace("foo bar foo baz");
println(`Result: ${result}`); // Should output: "AAA BBB AAA baz"
// Example 3: Case-insensitive regex replacement
println("\n--- Example 3: Case-insensitive regex replacement ---");
let replacer = text_replacer_new()
.pattern("foo")
.replacement("case-insensitive")
.regex(true)
.case_insensitive(true)
.build();
let result = replacer.replace("FOO foo Foo fOo");
println(`Result: ${result}`); // Should output: "case-insensitive case-insensitive case-insensitive case-insensitive"
// Example 4: File operations
println("\n--- Example 4: File operations ---");
let replacer = text_replacer_new()
.pattern("foo").replacement("EXAMPLE")
.build();
// Replace and get result as string
let file_result = replacer.replace_file(temp_file);
println(`File content after replacement:\n${file_result}`);
// Replace in-place
replacer.replace_file_in_place(temp_file);
println("File replaced in-place");
// Replace to a new file
let output_file = "text_replacer_output.txt";
replacer.replace_file_to(temp_file, output_file);
println(`Content written to new file: ${output_file}`);
// Clean up temporary files
delete(temp_file);
delete(output_file);
// ===== TemplateBuilder Examples =====
println("\n\n===== TemplateBuilder Examples =====");
// Create a temporary template file
let template_file = "template_test.txt";
file_write(template_file, "Hello, {{ name }}! Welcome to {{ place }}.\n{% if show_greeting %}Glad to have you here!{% endif %}\nYour items:\n{% for item in items %} - {{ item }}{% if not loop.last %}\n{% endif %}{% endfor %}\n");
// Example 1: Simple template rendering
println("\n--- Example 1: Simple template rendering ---");
let template = template_builder_open(template_file)
.add_var("name", "John")
.add_var("place", "Rhai")
.add_var("show_greeting", true)
.add_var("items", ["apple", "banana", "cherry"]);
let result = template.render();
println(`Rendered template:\n${result}`);
// Example 2: Using a map for variables
println("\n--- Example 2: Using a map for variables ---");
let vars = #{
name: "Alice",
place: "Template World"
};
let template = template_builder_open(template_file)
.add_vars(vars)
.add_var("show_greeting", false)
.add_var("items", ["laptop", "phone", "tablet"]);
let result = template.render();
println(`Rendered template with map:\n${result}`);
// Example 3: Rendering to a file
println("\n--- Example 3: Rendering to a file ---");
let output_file = "template_output.txt";
let template = template_builder_open(template_file)
.add_var("name", "Bob")
.add_var("place", "File Output")
.add_var("show_greeting", true)
.add_var("items", ["document", "spreadsheet", "presentation"]);
template.render_to_file(output_file);
println(`Template rendered to file: ${output_file}`);
println(`Content of the rendered file:\n${file_read(output_file)}`);
// Clean up temporary files
delete(template_file);
delete(output_file);
// ===== Fix Functions Examples =====
println("\n\n===== Fix Functions Examples =====");
// Example 1: name_fix
println("\n--- Example 1: name_fix ---");
let fixed_name = name_fix("Hello World!");
println(`Original: "Hello World!"`);
println(`Fixed: "${fixed_name}"`); // Should output: "hello_world"
let fixed_name = name_fix("File-Name.txt");
println(`Original: "File-Name.txt"`);
println(`Fixed: "${fixed_name}"`); // Should output: "file_name.txt"
let fixed_name = name_fix("Résumé.doc");
println(`Original: "Résumé.doc"`);
println(`Fixed: "${fixed_name}"`); // Should output: "rsum.doc"
// Example 2: path_fix
println("\n--- Example 2: path_fix ---");
let fixed_path = path_fix("/path/to/Hello World!");
println(`Original: "/path/to/Hello World!"`);
println(`Fixed: "${fixed_path}"`); // Should output: "/path/to/hello_world"
let fixed_path = path_fix("./relative/path/to/DOCUMENT-123.pdf");
println(`Original: "./relative/path/to/DOCUMENT-123.pdf"`);
println(`Fixed: "${fixed_path}"`); // Should output: "./relative/path/to/document_123.pdf"
// ===== Dedent Functions Examples =====
println("\n\n===== Dedent Functions Examples =====");
// Example 1: dedent
println("\n--- Example 1: dedent ---");
let indented_text = " line 1\n line 2\n line 3";
println(`Original:\n${indented_text}`);
let dedented = dedent(indented_text);
println(`Dedented:\n${dedented}`); // Should output: "line 1\nline 2\n line 3"
// Example 2: prefix
println("\n--- Example 2: prefix ---");
let text = "line 1\nline 2\nline 3";
println(`Original:\n${text}`);
let prefixed = prefix(text, " ");
println(`Prefixed:\n${prefixed}`); // Should output: " line 1\n line 2\n line 3"
// Return success message
"Text tools example completed successfully!"

View File

@ -1,102 +0,0 @@
// write_read.rhai
// Demonstrates writing content to and reading content from a container
// using the write_content and read_content methods
println("Starting write/read container example...");
// Define image and container names
let base_image = "ubuntu:22.04";
let container_name = "write-read-demo";
let final_image_name = "write-read-demo:latest";
println(`Creating container '${container_name}' from base image '${base_image}'...`);
// Create a new buildah container
let builder = bah_new(container_name, base_image);
// Update package lists
println("Updating package lists...");
let update_result = builder.run("apt-get update -y");
println(`Package update result: ${update_result.success ? "Success" : "Failed"}`);
// Write a simple text file to the container
println("\nWriting content to the container...");
let text_content = "This is a test file created using write_content.\nIt supports multiple lines.\n";
let write_result = builder.write_content(text_content, "/test.txt");
println(`Write result: ${write_result.success ? "Success" : "Failed"}`);
// Write a simple HTML file to the container
println("\nWriting HTML content to the container...");
let html_content = `
<!DOCTYPE html>
<html>
<head>
<title>Write Content Demo</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
line-height: 1.6;
color: #333;
}
h1 {
color: #0066cc;
}
</style>
</head>
<body>
<h1>Hello from Buildah!</h1>
<p>This HTML file was created using the write_content method.</p>
</body>
</html>
`;
let html_write_result = builder.write_content(html_content, "/var/www/html/index.html");
println(`HTML write result: ${html_write_result.success ? "Success" : "Failed"}`);
// Write a simple shell script to the container
println("\nWriting shell script to the container...");
let script_content = `
#!/bin/bash
echo "This script was created using write_content"
echo "Current directory: $(pwd)"
echo "Files in current directory:"
ls -la
`;
let script_write_result = builder.write_content(script_content, "/test.sh");
println(`Script write result: ${script_write_result.success ? "Success" : "Failed"}`);
// Make the script executable
builder.run("chmod +x /test.sh");
// Read back the content we wrote
println("\nReading content from the container...");
let read_text = builder.read_content("/test.txt");
println("Text file content:");
println(read_text);
let read_html = builder.read_content("/var/www/html/index.html");
println("\nHTML file content (first 100 characters):");
println(read_html.substr(0, 100) + "...");
let read_script = builder.read_content("/test.sh");
println("\nScript file content:");
println(read_script);
// Execute the script we created
println("\nExecuting the script we created...");
let script_result = builder.run("/test.sh");
println("Script output:");
println(script_result.stdout);
// Commit the container to an image
println(`\nCommitting container to image '${final_image_name}'...`);
let commit_result = builder.commit(final_image_name);
println(`Commit result: ${commit_result.success ? "Success" : "Failed"}`);
// Clean up the buildah container
println("Cleaning up buildah container...");
builder.remove();
println("\nWrite/read example completed successfully!");
"Write/read example completed successfully!"

View File

@ -1,82 +0,0 @@
// Create a test directory structure
let base_dir = "rhai_dir_test";
let sub_dir = base_dir + "/tmp/test";
println("Creating directory structure...");
let base_result = mkdir(base_dir+"/subdir");
println(`Base directory creation result: ${base_result}`);
let sub_result = mkdir(sub_dir);
println(`Subdirectory creation result: ${sub_result}`);
// Create a test file in the base directory
let base_file = base_dir + "/base_file.txt";
let base_content = "This is a file in the base directory.";
// First touch the file
run_command(`touch ${base_file}`);
// Then write to it with a separate command
run_command(`echo ${base_content} > ${base_file}`);
// Create a test file in the subdirectory
let sub_file = sub_dir + "/sub_file.txt";
let sub_content = "This is a file in the subdirectory.";
// First touch the file
run_command(`touch ${sub_file}`);
// Then write to it with a separate command
run_command(`echo ${sub_content} > ${sub_file}`);
// Get the current working directory before changing
let pwd_before = run_command("pwd");
println(`Current directory before chdir: ${pwd_before.stdout.trim()}`);
// Change to the base directory
println(`Changing directory to: ${base_dir}`);
let chdir_result = chdir(base_dir);
println(`Directory change result: ${chdir_result}`);
// Get the current working directory after changing
let pwd_after = run_command("pwd");
println(`Current directory after chdir: ${pwd_after.stdout.trim()}`);
// List files in the current directory (which should now be the base directory)
println("Files in the current directory:");
let files = find_files(".", "*");
println("Files found:");
for file in files {
println(`- ${file}`);
}
// Change to the subdirectory
println(`Changing directory to: subdir`);
let chdir_sub_result = chdir("subdir");
println(`Directory change result: ${chdir_sub_result}`);
// Get the current working directory after changing to subdirectory
let pwd_final = run_command("pwd");
println(`Current directory after second chdir: ${pwd_final.stdout.trim()}`);
// List files in the subdirectory
println("Files in the subdirectory:");
let subdir_files = find_files(".", "*");
println("Files found:");
for file in subdir_files {
println(`- ${file}`);
}
// Change back to the parent directory
println("Changing directory back to parent...");
let chdir_parent_result = chdir("..");
println(`Directory change result: ${chdir_parent_result}`);
// Clean up (uncomment to actually delete the files)
// println("Cleaning up...");
// Change back to the original directory first
// chdir(pwd_before.stdout.trim());
// delete(sub_file);
// delete(base_file);
// delete(sub_dir);
// delete(base_dir);
// println("Cleanup complete");
"Directory operations script completed successfully!"

View File

@ -1,64 +0,0 @@
// 02_file_operations.rhai
// Demonstrates file system operations using SAL
// Create a test directory
let test_dir = "/tmp/rhai_test_dir";
println(`Creating directory: ${test_dir}`);
let mkdir_result = mkdir(test_dir);
println(`Directory creation result: ${mkdir_result}`);
// Check if the directory exists
let dir_exists = exist(test_dir);
println(`Directory exists: ${dir_exists}`);
// Create a test file
let test_file = test_dir + "/test_file.txt";
let file_content = "This is a test file created by Rhai script.";
// Create the file using a different approach
println(`Creating file: ${test_file}`);
// First ensure the directory exists
run_command(`mkdir -p ${test_dir}`);
// Then create the file using a simpler approach
// First touch the file
let touch_cmd = `touch ${test_file}`;
run_command(touch_cmd);
// Then write to it with a separate command
let echo_cmd = `echo ${file_content} > ${test_file}`;
let write_result = run_command(echo_cmd);
println(`File creation result: ${write_result.success}`);
// Wait a moment to ensure the file is created
run_command("sleep 1");
// Check if the file exists
let file_exists = exist(test_file);
println(`File exists: ${file_exists}`);
// Get file size
if file_exists {
let size = file_size(test_file);
println(`File size: ${size} bytes`);
}
// Copy the file
let copied_file = test_dir + "/copied_file.txt";
println(`Copying file to: ${copied_file}`);
let copy_result = copy(test_file, copied_file);
println(`File copy result: ${copy_result}`);
// Find files in the directory
println("Finding files in the test directory:");
let files = find_files(test_dir, "*.txt");
for file in files {
println(` - ${file}`);
}
// Clean up (uncomment to actually delete the files)
// println("Cleaning up...");
// delete(copied_file);
// delete(test_file);
// delete(test_dir);
// println("Cleanup complete");
"File operations script completed successfully!"

View File

@ -1,39 +0,0 @@
// 01_hello_world.rhai
// A simple hello world script to demonstrate basic Rhai functionality
// Print a message
println("Hello from Rhai!");
// Define a function
fn greet(name) {
"Hello, " + name + "!"
}
// Call the function and print the result
let greeting = greet("SAL User");
println(greeting);
// Do some basic calculations
let a = 5;
let b = 7;
println(`${a} + ${b} = ${a + b}`);
println(`${a} * ${b} = ${a * b}`);
// Create and use an array
let numbers = [1, 2, 3, 4, 5];
println("Numbers: " + numbers);
println("Sum of numbers: " + numbers.reduce(|sum, n| sum + n, 0));
// Create and use a map
let person = #{
name: "John Doe",
age: 30,
occupation: "Developer"
};
println("Person: " + person);
println("Name: " + person.name);
println("Age: " + person.age);
// Return a success message
"Hello world script completed successfully!"

115
examples/buildah.rs Normal file
View File

@ -0,0 +1,115 @@
//! Example usage of the buildah module
//!
//! This file demonstrates how to use the buildah module to perform
//! common container operations like creating containers, running commands,
//! and managing images.
use sal::virt::buildah::{self, BuildahError};
use std::collections::HashMap;
/// Run a complete buildah workflow example
pub fn run_buildah_example() -> Result<(), BuildahError> {
println!("Starting buildah example workflow...");
// Step 1: Create a container from an image
println!("\n=== Creating container from fedora:latest ===");
let result = buildah::from("fedora:latest")?;
let container_id = result.stdout.trim();
println!("Created container: {}", container_id);
// Step 2: Run a command in the container
println!("\n=== Installing nginx in container ===");
// Use chroot isolation to avoid BPF issues
let install_result = buildah::run_with_isolation(container_id, "dnf install -y nginx", "chroot")?;
println!("{:#?}", install_result);
println!("Installation output: {}", install_result.stdout);
// Step 3: Copy a file into the container
println!("\n=== Copying configuration file to container ===");
buildah::copy(container_id, "./example.conf", "/etc/example.conf").unwrap();
// Step 4: Configure container metadata
println!("\n=== Configuring container metadata ===");
let mut config_options = HashMap::new();
config_options.insert("port".to_string(), "80".to_string());
config_options.insert("label".to_string(), "maintainer=example@example.com".to_string());
config_options.insert("entrypoint".to_string(), "/usr/sbin/nginx".to_string());
buildah::config(container_id, config_options)?;
println!("Container configured");
// Step 5: Commit the container to create a new image
println!("\n=== Committing container to create image ===");
let image_name = "my-nginx:latest";
buildah::image_commit(container_id, image_name, Some("docker"), true, true)?;
println!("Created image: {}", image_name);
// Step 6: List images to verify our new image exists
println!("\n=== Listing images ===");
let images = buildah::images()?;
println!("Found {} images:", images.len());
for image in images {
println!(" ID: {}", image.id);
println!(" Names: {}", image.names.join(", "));
println!(" Size: {}", image.size);
println!(" Created: {}", image.created);
println!();
}
// // Step 7: Clean up (optional in a real workflow)
println!("\n=== Cleaning up ===");
buildah::image_remove(image_name).unwrap();
println!("\nBuildah example workflow completed successfully!");
Ok(())
}
/// Demonstrate how to build an image from a Containerfile/Dockerfile
pub fn build_image_example() -> Result<(), BuildahError> {
println!("Building an image from a Containerfile...");
// Use the build function with tag, context directory, and isolation to avoid BPF issues
let result = buildah::build(Some("my-app:latest"), ".", "example_Dockerfile", Some("chroot"))?;
println!("Build output: {}", result.stdout);
println!("Image built successfully!");
Ok(())
}
/// Example of pulling and pushing images
pub fn registry_operations_example() -> Result<(), BuildahError> {
println!("Demonstrating registry operations...");
// Pull an image
println!("\n=== Pulling an image ===");
buildah::image_pull("docker.io/library/alpine:latest", true)?;
println!("Image pulled successfully");
// Tag the image
println!("\n=== Tagging the image ===");
buildah::image_tag("alpine:latest", "my-alpine:v1.0")?;
println!("Image tagged successfully");
// Push an image (this would typically go to a real registry)
// println!("\n=== Pushing an image (example only) ===");
// println!("In a real scenario, you would push to a registry with:");
// println!("buildah::image_push(\"my-alpine:v1.0\", \"docker://registry.example.com/my-alpine:v1.0\", true)");
Ok(())
}
/// Main function to run all examples
pub fn run_all_examples() -> Result<(), BuildahError> {
println!("=== BUILDAH MODULE EXAMPLES ===\n");
run_buildah_example()?;
build_image_example()?;
registry_operations_example()?;
Ok(())
}
fn main() {
let _ = run_all_examples().unwrap();
}

View File

@ -1,150 +0,0 @@
// buildah.rhai
// Demonstrates using buildah to create a custom image with golang and nginx,
// then using nerdctl to run a container from that image
println("Starting buildah workflow to create a custom image...");
// Define image and container names
let base_image = "ubuntu:22.04";
let container_name = "golang-nginx-container";
let final_image_name = "custom-golang-nginx:latest";
println(`Creating container '${container_name}' from base image '${base_image}'...`);
// Create a new buildah container using the builder pattern
let builder = bah_new(container_name, base_image);
println("Enabling debug mode...");
builder.debug_mode = true;
// Update package lists and install golang and nginx
println("Installing packages (this may take a while)...");
// Update package lists
let update_result = builder.run("apt-get update -y");
// Install required packages
let install_result = builder.run("apt-get install -y golang nginx");
// Verify installations
let go_version = builder.run("go version");
println(`Go version: ${go_version.stdout}`);
let nginx_version = builder.run("nginx -v");
println(`Nginx version: ${nginx_version.stderr}`); // nginx outputs version to stderr
// Create a simple Go web application
println("Creating a simple Go web application...");
// Create a directory for the Go application
builder.run("mkdir -p /app");
// Create a simple Go web server
let go_app = `
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from Go running in a custom container!")
})
fmt.Println("Starting server on :8080")
http.ListenAndServe(":8080", nil)
}
`;
// Write the Go application to a file using the write_content method
builder.write_content(go_app, "/app/main.go");
// Compile the Go application
builder.run("cd /app && go build -o server main.go");
// Configure nginx to proxy to the Go application
let nginx_conf = `
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
`;
// Write the nginx configuration using the write_content method
let nginx_conf_result = builder.write_content(nginx_conf, "/etc/nginx/sites-available/default");
// Create a startup script
let startup_script = `
#!/bin/bash
# Start the Go application in the background
cd /app && ./server &
# Start nginx in the foreground
nginx -g "daemon off;"
`;
// Write the startup script using the write_content method
let startup_script_result = builder.write_content(startup_script, "/start.sh");
builder.run("chmod +x /start.sh");
// Set the entrypoint to the startup script
println("Setting entrypoint to /start.sh...");
builder.set_entrypoint("/start.sh");
// Read back the startup script to verify it was written correctly
let read_script = builder.read_content("/start.sh");
println("Startup script content verification:");
println(read_script);
// Commit the container to a new image
println(`Committing container to image '${final_image_name}'...`);
let commit_result = builder.commit(final_image_name);
// Clean up the buildah container
println("Cleaning up buildah container...");
builder.remove();
// Now use nerdctl to run a container from the new image
println("\nStarting container from the new image using nerdctl...");
// Create a container using the builder pattern
// Use localhost/ prefix to ensure nerdctl uses the local image
let local_image_name = "localhost/" + final_image_name;
println(`Using local image: ${local_image_name}`);
// Tag the image with the localhost prefix for nerdctl compatibility
println(`Tagging image as ${local_image_name}...`);
let tag_result = bah_image_tag(final_image_name, local_image_name);
// Print a command to check if the image exists in buildah
println("\nTo verify the image was created with buildah, run:");
println("buildah images");
// Note: If nerdctl cannot find the image, you may need to push it to a registry
println("\nNote: If nerdctl cannot find the image, you may need to push it to a registry:");
println("buildah push localhost/custom-golang-nginx:latest docker://localhost:5000/custom-golang-nginx:latest");
println("nerdctl pull localhost:5000/custom-golang-nginx:latest");
let container = nerdctl_container_from_image("golang-nginx-demo", local_image_name)
.with_detach(true)
.with_port("8080:80") // Map port 80 in the container to 8080 on the host
.with_restart_policy("unless-stopped")
.build();
// Start the container
let start_result = container.start();
println("\nWorkflow completed successfully!");
println("The web server should be running at http://localhost:8080");
println("You can check container logs with: nerdctl logs golang-nginx-demo");
println("To stop the container: nerdctl stop golang-nginx-demo");
println("To remove the container: nerdctl rm golang-nginx-demo");
"Buildah and nerdctl workflow completed successfully!"

View File

@ -1,39 +0,0 @@
// buildah_debug.rhai
// Demonstrates using the debug flag on the buildah Builder
println("Starting buildah debug example...");
// Define image and container names
let base_image = "ubuntu:22.04";
let container_name = "debug-test-container";
println(`Creating container '${container_name}' from base image '${base_image}'...`);
// Create a new buildah container using the builder pattern
let builder = bah_new(container_name, base_image);
// Enable debug mode
println("Enabling debug mode...");
builder.debug_mode = true;
// Run a simple command to see debug output
println("Running a command with debug enabled...");
let result = builder.run("echo 'Hello from debug mode'");
// Disable debug mode
println("Disabling debug mode...");
builder.debug_mode = false;
// Run another command without debug
println("Running a command with debug disabled...");
let result2 = builder.run("echo 'Hello without debug'");
// Enable debug mode again
println("Enabling debug mode again...");
builder.debug_mode = true;
// Remove the container with debug enabled
println("Removing the container with debug enabled...");
builder.remove();
println("Debug example completed!");

View File

@ -1,42 +0,0 @@
fn nerdctl_download(){
let name="nerdctl";
let url="https://github.com/containerd/nerdctl/releases/download/v2.0.4/nerdctl-2.0.4-linux-amd64.tar.gz";
download(url,`/tmp/${name}`,20000);
copy(`/tmp/${name}/*`,"/root/hero/bin/");
delete(`/tmp/${name}`);
let name="containerd";
let url="https://github.com/containerd/containerd/releases/download/v2.0.4/containerd-2.0.4-linux-amd64.tar.gz";
download(url,`/tmp/${name}`,20000);
copy(`/tmp/${name}/bin/*`,"/root/hero/bin/");
delete(`/tmp/${name}`);
run("apt-get -y install buildah runc");
let url="https://github.com/threefoldtech/rfs/releases/download/v2.0.6/rfs";
download_file(url,`/tmp/rfs`,10000);
chmod_exec("/tmp/rfs");
mv(`/tmp/rfs`,"/root/hero/bin/");
}
fn ipfs_download(){
let name="ipfs";
let url="https://github.com/ipfs/kubo/releases/download/v0.34.1/kubo_v0.34.1_linux-amd64.tar.gz";
download(url,`/tmp/${name}`,20);
copy(`/tmp/${name}/kubo/ipfs`,"/root/hero/bin/ipfs");
// delete(`/tmp/${name}`);
}
nerdctl_download();
// ipfs_download();
"done"

View File

@ -1,86 +0,0 @@
// 08__web_server.rhai
// Demonstrates a complete workflow to set up a web server using
// Note: This script requires to be installed and may need root privileges
println("Starting web server workflow...");
// Create and use a temporary directory for all files
let work_dir = "/tmp/";
mkdir(work_dir);
chdir(work_dir);
println(`Working in directory: ${work_dir}`);
println("\n=== Creating custom nginx configuration ===");
let config_content = `
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
`;
let config_file = `${work_dir}/custom-nginx.conf`;
// Use file_write instead of run command
file_write(config_file, config_content);
println(`Created custom nginx configuration file at ${config_file}`);
// Step 3: Create a custom index.html file
println("\n=== Creating custom index.html ===");
let html_content = `
<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
line-height: 1.6;
color: #333;
}
h1 {
color: #0066cc;
}
</style>
</head>
<body>
<h1>Hello from HeroScript !</h1>
<p>This page is served by an Nginx container.</p>
</body>
</html>
`;
let html_file = `${work_dir}/index.html`;
// Use file_write instead of run command
file_write(html_file, html_content);
println(`Created custom index.html file at ${html_file}`);
println("\n=== Creating nginx container ===");
let container_name = "nginx-demo";
let env_map = #{}; // Create an empty map
env_map["NGINX_HOST"] = "localhost";
env_map["NGINX_PORT"] = "80";
env_map["NGINX_WORKER_PROCESSES"] = "auto";
// Create a container with a rich set of options using batch methods
let container = nerdctl_container_from_image(container_name, "nginx:latest")
.reset()
.with_detach(true)
.with_ports(["8080:80"]) // Add multiple ports at once
.with_volumes([`${work_dir}:/usr/share/nginx/html`, "/var/log:/var/log/nginx"]) // Mount our work dir
.with_envs(env_map) // Add multiple environment variables at once
.with_cpu_limit("1.0")
.with_memory_limit("512m")
.start();
println("\n web server workflow completed successfully!");
println("The web server is running at http://localhost:8080");
"Web server script completed successfully!"

View File

@ -1,28 +0,0 @@
// Simplified Git Basic Operations Example
let git_tree = git_tree_new("/tmp/git"); // Using /tmp/git as base path
print("--- Git Basic Operations ---");
// print(`Base path: ${git_tree.base_path()}`); // base_path() getter would need to be exposed from Rust
let all_repos = git_tree.list();
print(`Listed ${all_repos.len()} repos.`);
// Find repos starting with "home" (adjust pattern if /tmp/git might contain other "home*" repos)
let found_repos = git_tree.find("home*");
print(`Found ${found_repos.len()} repos matching "home*".`);
for r in found_repos {
print(` - Found: ${r.path()}`);
}
print("Getting/Cloning 'https://github.com/freeflowuniverse/home'...");
let repo = git_tree.get("https://github.com/freeflowuniverse/home");
print(`Repo path: ${repo.path()}`);
print(`Has changes: ${repo.has_changes()}`);
print("Performing pull & reset...");
repo.pull().reset();
print("Pull and reset complete.");
print(`Has changes after pull/reset: ${repo.has_changes()}`);
print("--- Example Finished ---");

View File

@ -1,64 +0,0 @@
# Hero Vault Cryptography Examples
This directory contains examples demonstrating the Hero Vault cryptography functionality integrated into the SAL project.
## Overview
Hero Vault provides cryptographic operations including:
- Key space management (creation, loading, encryption, decryption)
- Keypair management (creation, selection, listing)
- Digital signatures (signing and verification)
- Symmetric encryption (key generation, encryption, decryption)
- Ethereum wallet functionality
- Smart contract interactions
- Key-value store with encryption
## Example Files
- `example.rhai` - Basic example demonstrating key management, signing, and encryption
- `advanced_example.rhai` - Advanced example with error handling, conditional logic, and more complex operations
- `key_persistence_example.rhai` - Demonstrates creating and saving a key space to disk
- `load_existing_space.rhai` - Shows how to load a previously created key space and use its keypairs
- `contract_example.rhai` - Demonstrates loading a contract ABI and interacting with smart contracts
- `agung_send_transaction.rhai` - Demonstrates sending native tokens on the Agung network
- `agung_contract_with_args.rhai` - Shows how to interact with contracts with arguments on Agung
## Running the Examples
You can run the examples using the `herodo` tool that comes with the SAL project:
```bash
# Run a single example
herodo --path example.rhai
# Run all examples using the provided script
./run_examples.sh
```
## Key Space Storage
Key spaces are stored in the `~/.hero-vault/key-spaces/` directory by default. Each key space is stored in a separate JSON file named after the key space (e.g., `my_space.json`).
## Ethereum Functionality
The Hero Vault module provides comprehensive Ethereum wallet functionality:
- Creating and managing wallets for different networks
- Sending ETH transactions
- Checking balances
- Interacting with smart contracts (read and write functions)
- Support for multiple networks (Ethereum, Gnosis, Peaq, Agung, etc.)
## Security
Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password. The encryption ensures that the key material is secure at rest.
## Best Practices
1. **Use Strong Passwords**: Since the security of your key spaces depends on the strength of your passwords, use strong, unique passwords.
2. **Backup Key Spaces**: Regularly backup your key spaces directory to prevent data loss.
3. **Script Organization**: Split your scripts into logical units, with separate scripts for key creation and key usage.
4. **Error Handling**: Always check the return values of functions to ensure operations succeeded before proceeding.
5. **Network Selection**: When working with Ethereum functionality, be explicit about which network you're targeting to avoid confusion.
6. **Gas Management**: For Ethereum transactions, consider gas costs and set appropriate gas limits.

View File

@ -1,233 +0,0 @@
// Advanced Rhai script example for Hero Vault Cryptography Module
// This script demonstrates conditional logic, error handling, and more complex operations
// Function to create a key space with error handling
fn setup_key_space(name, password) {
print("Attempting: Create key space: " + name);
let result = create_key_space(name, password);
if result {
print("✅ Create key space succeeded!");
return true;
} else {
print("❌ Create key space failed!");
}
return false;
}
// Function to create and select a keypair
fn setup_keypair(name, password) {
print("Attempting: Create keypair: " + name);
let result = create_keypair(name, password);
if result {
print("✅ Create keypair succeeded!");
print("Attempting: Select keypair: " + name);
let selected = select_keypair(name);
if selected {
print("✅ Select keypair succeeded!");
return true;
} else {
print("❌ Select keypair failed!");
}
} else {
print("❌ Create keypair failed!");
}
return false;
}
// Function to sign multiple messages
fn sign_messages(messages) {
let signatures = [];
for message in messages {
print("Signing message: " + message);
print("Attempting: Sign message");
let signature = sign(message);
if signature != "" {
print("✅ Sign message succeeded!");
signatures.push(#{
message: message,
signature: signature
});
} else {
print("❌ Sign message failed!");
}
}
return signatures;
}
// Function to verify signatures
fn verify_signatures(signed_messages) {
let results = [];
for item in signed_messages {
let message = item.message;
let signature = item.signature;
print("Verifying signature for: " + message);
print("Attempting: Verify signature");
let is_valid = verify(message, signature);
if is_valid {
print("✅ Verify signature succeeded!");
} else {
print("❌ Verify signature failed!");
}
results.push(#{
message: message,
valid: is_valid
});
}
return results;
}
// Function to encrypt multiple messages
fn encrypt_messages(messages) {
// Generate a symmetric key
print("Attempting: Generate symmetric key");
let key = generate_key();
if key == "" {
print("❌ Generate symmetric key failed!");
return [];
}
print("✅ Generate symmetric key succeeded!");
print("Using key: " + key);
let encrypted_messages = [];
for message in messages {
print("Encrypting message: " + message);
print("Attempting: Encrypt message");
let encrypted = encrypt(key, message);
if encrypted != "" {
print("✅ Encrypt message succeeded!");
encrypted_messages.push(#{
original: message,
encrypted: encrypted,
key: key
});
} else {
print("❌ Encrypt message failed!");
}
}
return encrypted_messages;
}
// Function to decrypt messages
fn decrypt_messages(encrypted_messages) {
let decrypted_messages = [];
for item in encrypted_messages {
let encrypted = item.encrypted;
let key = item.key;
let original = item.original;
print("Decrypting message...");
print("Attempting: Decrypt message");
let decrypted = decrypt(key, encrypted);
if decrypted != false {
let success = decrypted == original;
decrypted_messages.push(#{
decrypted: decrypted,
original: original,
success: success
});
if success {
print("Decryption matched original ✅");
} else {
print("Decryption did not match original ❌");
}
}
}
return decrypted_messages;
}
// Main script execution
print("=== Advanced Cryptography Script ===");
// Set up key space
let space_name = "advanced_space";
let password = "secure_password123";
if setup_key_space(space_name, password) {
print("\n--- Key space setup complete ---\n");
// Set up keypair
if setup_keypair("advanced_keypair", password) {
print("\n--- Keypair setup complete ---\n");
// Define messages to sign
let messages = [
"This is the first message to sign",
"Here's another message that needs signing",
"And a third message for good measure"
];
// Sign messages
print("\n--- Signing Messages ---\n");
let signed_messages = sign_messages(messages);
// Verify signatures
print("\n--- Verifying Signatures ---\n");
let verification_results = verify_signatures(signed_messages);
// Count successful verifications
let successful_verifications = verification_results.filter(|r| r.valid).len();
print("Successfully verified " + successful_verifications + " out of " + verification_results.len() + " signatures");
// Encrypt messages
print("\n--- Encrypting Messages ---\n");
let encrypted_messages = encrypt_messages(messages);
// Decrypt messages
print("\n--- Decrypting Messages ---\n");
let decryption_results = decrypt_messages(encrypted_messages);
// Count successful decryptions
let successful_decryptions = decryption_results.filter(|r| r.success).len();
print("Successfully decrypted " + successful_decryptions + " out of " + decryption_results.len() + " messages");
// Create Ethereum wallet
print("\n--- Creating Ethereum Wallet ---\n");
print("Attempting: Create Ethereum wallet");
let wallet_created = create_ethereum_wallet();
if wallet_created {
print("✅ Create Ethereum wallet succeeded!");
print("Attempting: Get Ethereum address");
let address = get_ethereum_address();
if address != "" {
print("✅ Get Ethereum address succeeded!");
print("Ethereum wallet address: " + address);
} else {
print("❌ Get Ethereum address failed!");
}
} else {
print("❌ Create Ethereum wallet failed!");
}
print("\n=== Script execution completed successfully! ===");
} else {
print("Failed to set up keypair. Aborting script.");
}
} else {
print("Failed to set up key space. Aborting script.");
}

View File

@ -1,152 +0,0 @@
// Example Rhai script for testing contract functions with arguments on Agung network
// This script demonstrates how to use call_contract_read and call_contract_write with arguments
// Step 1: Set up wallet and network
let space_name = "agung_contract_args_demo";
let password = "secure_password123";
let private_key = "51c194d20bcd25360a3aa94426b3b60f738007e42f22e1bc97821c65c353e6d2";
let network_name = "agung";
print("=== Testing Contract Functions With Arguments on Agung Network ===\n");
// Create a key space
print("Creating key space: " + space_name);
if create_key_space(space_name, password) {
print("✓ Key space created successfully");
// Create a keypair
print("\nCreating keypair...");
if create_keypair("contract_key", password) {
print("✓ Created contract keypair");
// Create a wallet from the private key for the Agung network
print("\nCreating wallet from private key for Agung network...");
if create_wallet_from_private_key_for_network(private_key, network_name) {
print("✓ Wallet created successfully");
// Get the wallet address
let wallet_address = get_wallet_address_for_network(network_name);
print("Wallet address: " + wallet_address);
// Check wallet balance
print("\nChecking wallet balance...");
let balance = get_balance(network_name, wallet_address);
if balance != "" {
print("Wallet balance: " + balance + " wei");
// Define a simple ERC-20 token contract ABI (partial)
let token_abi = `[
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [{"name": "", "type": "string"}],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [{"name": "", "type": "string"}],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [{"name": "", "type": "uint8"}],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [{"name": "_owner", "type": "address"}],
"name": "balanceOf",
"outputs": [{"name": "balance", "type": "uint256"}],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}],
"name": "transfer",
"outputs": [{"name": "", "type": "bool"}],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
]`;
// For this example, we'll use a test token contract on Agung
let token_address = "0x7267B587E4416537060C6bF0B06f6Fd421106650";
print("\nLoading contract ABI...");
let contract = load_contract_abi(network_name, token_address, token_abi);
if contract != "" {
print("✓ Contract loaded successfully");
// First, let's try to read some data from the contract
print("\nReading contract data...");
// Try to get token name (no arguments)
let token_name = call_contract_read(contract, "name");
print("Token name: " + token_name);
// Try to get token symbol (no arguments)
let token_symbol = call_contract_read(contract, "symbol");
print("Token symbol: " + token_symbol);
// Try to get token decimals (no arguments)
let token_decimals = call_contract_read(contract, "decimals");
print("Token decimals: " + token_decimals);
// Try to get token balance (with address argument)
print("\nCalling balanceOf with address argument...");
let balance = call_contract_read(contract, "balanceOf", [wallet_address]);
print("Token balance: " + balance);
// Now, let's try to execute a write function with arguments
print("\nExecuting contract write function with arguments...");
// Define a recipient address and amount for the transfer
// Using a random valid address on the network
let recipient = "0xEEdf3468E8F232A7a03D49b674bA44740C8BD8Be";
let amount = 1000000; // Changed from string to number for uint256 compatibility
print("Attempting to transfer " + amount + " tokens to " + recipient);
// Call the transfer function with arguments
let tx_hash = call_contract_write(contract, "transfer", [recipient, amount]);
if tx_hash != "" {
print("✓ Transaction sent successfully");
print("Transaction hash: " + tx_hash);
print("You can view the transaction at: " + get_network_explorer_url(network_name) + "/tx/" + tx_hash);
} else {
print("✗ Failed to send transaction");
print("This could be due to insufficient funds, contract issues, or other errors.");
}
} else {
print("✗ Failed to load contract");
}
} else {
print("✗ Failed to get wallet balance");
}
} else {
print("✗ Failed to create wallet from private key");
}
} else {
print("✗ Failed to create keypair");
}
} else {
print("✗ Failed to create key space");
}
print("\nContract function with arguments test completed");

View File

@ -1,104 +0,0 @@
// Script to create an Agung wallet from a private key and send tokens
// This script demonstrates how to create a wallet from a private key and send tokens
// Define the private key and recipient address
let private_key = "0x9ecfd58eca522b0e7c109bf945966ee208cd6d593b1dc3378aedfdc60b64f512";
let recipient_address = "0xf400f9c3F7317e19523a5DB698Ce67e7a7E083e2";
print("=== Agung Wallet Transaction Demo ===");
print(`From private key: ${private_key}`);
print(`To address: ${recipient_address}`);
// First, create a key space and keypair (required for the wallet infrastructure)
let space_name = "agung_transaction_demo";
let password = "demo_password";
// Create a new key space
if !create_key_space(space_name, password) {
print("Failed to create key space");
return;
}
// Create a keypair
if !create_keypair("demo_keypair", password) {
print("Failed to create keypair");
return;
}
// Select the keypair
if !select_keypair("demo_keypair") {
print("Failed to select keypair");
return;
}
print("\nCreated and selected keypair successfully");
// Clear any existing Agung wallets to avoid conflicts
if clear_wallets_for_network("agung") {
print("Cleared existing Agung wallets");
} else {
print("Failed to clear existing Agung wallets");
return;
}
// Create a wallet from the private key directly
print("\n=== Creating Wallet from Private Key ===");
// Create a wallet from the private key for the Agung network
if create_wallet_from_private_key_for_network(private_key, "agung") {
print("Successfully created wallet from private key for Agung network");
// Get the wallet address
let wallet_address = get_wallet_address_for_network("agung");
print(`Wallet address: ${wallet_address}`);
// Create a provider for the Agung network
let provider_id = create_agung_provider();
if provider_id != "" {
print("Successfully created Agung provider");
// Check the wallet balance first
let wallet_address = get_wallet_address_for_network("agung");
let balance_wei = get_balance("agung", wallet_address);
if balance_wei == "" {
print("Failed to get wallet balance");
print("This could be due to network issues or other errors.");
return;
}
print(`Current wallet balance: ${balance_wei} wei`);
// Convert 1 AGNG to wei (1 AGNG = 10^18 wei)
// Use string representation for large numbers
let amount_wei_str = "1000000000000000000"; // 1 AGNG in wei as a string
// Check if we have enough balance
if parse_int(balance_wei) < parse_int(amount_wei_str) {
print(`Insufficient balance to send ${amount_wei_str} wei (1 AGNG)`);
print(`Current balance: ${balance_wei} wei`);
print("Please fund the wallet before attempting to send a transaction");
return;
}
print(`Attempting to send ${amount_wei_str} wei (1 AGNG) to ${recipient_address}`);
// Send the transaction using the blocking implementation
let tx_hash = send_eth("agung", recipient_address, amount_wei_str);
if tx_hash != "" {
print(`Transaction sent with hash: ${tx_hash}`);
print(`You can view the transaction at: ${get_network_explorer_url("agung")}/tx/${tx_hash}`);
} else {
print("Transaction failed");
print("This could be due to insufficient funds, network issues, or other errors.");
print("Check the logs for more details.");
}
} else {
print("Failed to create Agung provider");
}
} else {
print("Failed to create wallet from private key");
}
print("\nAgung transaction demo completed");

View File

@ -1,98 +0,0 @@
// Example Rhai script for interacting with smart contracts using Hero Vault
// This script demonstrates loading a contract ABI and interacting with a contract
// Step 1: Set up wallet and network
let space_name = "contract_demo_space";
let password = "secure_password123";
print("Creating key space: " + space_name);
if create_key_space(space_name, password) {
print("✓ Key space created successfully");
// Create a keypair
print("\nCreating keypair...");
if create_keypair("contract_key", password) {
print("✓ Created contract keypair");
}
// Step 2: Create an Ethereum wallet for Gnosis Chain
print("\nCreating Ethereum wallet...");
if create_ethereum_wallet() {
print("✓ Ethereum wallet created");
let address = get_ethereum_address();
print("Ethereum address: " + address);
// Step 3: Define a simple ERC-20 ABI (partial)
let erc20_abi = `[
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [{"name": "", "type": "string"}],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [{"name": "", "type": "string"}],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [{"name": "", "type": "uint8"}],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [{"name": "owner", "type": "address"}],
"name": "balanceOf",
"outputs": [{"name": "", "type": "uint256"}],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]`;
// Step 4: Load the contract ABI
print("\nLoading contract ABI...");
let contract = load_contract_abi("Gnosis", "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", erc20_abi);
if contract != "" {
print("✓ Contract loaded successfully");
// Step 5: Call read-only functions
print("\nCalling read-only functions...");
// Get token name
let token_name = call_contract_read(contract, "name");
print("Token name: " + token_name);
// Get token symbol
let token_symbol = call_contract_read(contract, "symbol");
print("Token symbol: " + token_symbol);
// Get token decimals
let token_decimals = call_contract_read(contract, "decimals");
print("Token decimals: " + token_decimals);
// For now, we're just demonstrating the basic structure
} else {
print("✗ Failed to load contract");
}
} else {
print("✗ Failed to create Ethereum wallet");
}
} else {
print("✗ Failed to create key space");
}
print("\nContract example completed");

View File

@ -1,85 +0,0 @@
// Example Rhai script for Hero Vault Cryptography Module
// This script demonstrates key management, signing, and encryption
// Step 1: Create and manage a key space
let space_name = "demo_space";
let password = "secure_password123";
print("Creating key space: " + space_name);
if create_key_space(space_name, password) {
print("✓ Key space created successfully");
// Step 2: Create and use keypairs
print("\nCreating keypairs...");
if create_keypair("signing_key", password) {
print("✓ Created signing keypair");
}
if create_keypair("encryption_key", password) {
print("✓ Created encryption keypair");
}
// List all keypairs
let keypairs = list_keypairs();
print("Available keypairs: " + keypairs);
// Step 3: Sign a message
print("\nPerforming signing operations...");
if select_keypair("signing_key") {
print("✓ Selected signing keypair");
let message = "This is a secure message that needs to be signed";
print("Message: " + message);
let signature = sign(message);
print("Signature: " + signature);
// Verify the signature
let is_valid = verify(message, signature);
if is_valid {
print("Signature verification: ✓ Valid");
} else {
print("Signature verification: ✗ Invalid");
}
}
// Step 4: Encrypt and decrypt data
print("\nPerforming encryption operations...");
// Generate a symmetric key
let sym_key = generate_key();
print("Generated symmetric key: " + sym_key);
// Encrypt a message
let secret = "This is a top secret message that must be encrypted";
print("Original message: " + secret);
let encrypted_data = encrypt(sym_key, secret);
print("Encrypted data: " + encrypted_data);
// Decrypt the message
let decrypted_data = decrypt(sym_key, encrypted_data);
print("Decrypted message: " + decrypted_data);
// Verify decryption was successful
if decrypted_data == secret {
print("✓ Encryption/decryption successful");
} else {
print("✗ Encryption/decryption failed");
}
// Step 5: Create an Ethereum wallet
print("\nCreating Ethereum wallet...");
if select_keypair("encryption_key") {
print("✓ Selected keypair for Ethereum wallet");
if create_ethereum_wallet() {
print("✓ Ethereum wallet created");
let address = get_ethereum_address();
print("Ethereum address: " + address);
}
}
print("\nScript execution completed successfully!");
}

View File

@ -1,65 +0,0 @@
// Example Rhai script demonstrating key space persistence for Hero Vault
// This script shows how to create, save, and load key spaces
// Step 1: Create a key space
let space_name = "persistent_space";
let password = "secure_password123";
print("Creating key space: " + space_name);
if create_key_space(space_name, password) {
print("✓ Key space created successfully");
// Step 2: Create keypairs in this space
print("\nCreating keypairs...");
if create_keypair("persistent_key1", password) {
print("✓ Created first keypair");
}
if create_keypair("persistent_key2", password) {
print("✓ Created second keypair");
}
// List all keypairs
let keypairs = list_keypairs();
print("Available keypairs: " + keypairs);
// Step 3: Clear the session (simulate closing and reopening the CLI)
print("\nClearing session (simulating restart)...");
// Note: In a real script, you would exit here and run a new script
// For demonstration purposes, we'll continue in the same script
// Step 4: Load the key space from disk
print("\nLoading key space from disk...");
if load_key_space(space_name, password) {
print("✓ Key space loaded successfully");
// Verify the keypairs are still available
let loaded_keypairs = list_keypairs();
print("Keypairs after loading: " + loaded_keypairs);
// Step 5: Use a keypair from the loaded space
print("\nSelecting and using a keypair...");
if select_keypair("persistent_key1") {
print("✓ Selected keypair");
let message = "This message was signed using a keypair from a loaded key space";
let signature = sign(message);
print("Message: " + message);
print("Signature: " + signature);
// Verify the signature
let is_valid = verify(message, signature);
if is_valid {
print("Signature verification: ✓ Valid");
} else {
print("Signature verification: ✗ Invalid");
}
}
} else {
print("✗ Failed to load key space");
}
} else {
print("✗ Failed to create key space");
}
print("\nScript execution completed!");

View File

@ -1,65 +0,0 @@
// Example Rhai script demonstrating loading an existing key space for Hero Vault
// This script shows how to load a previously created key space and use its keypairs
// Define the key space name and password
let space_name = "persistent_space";
let password = "secure_password123";
print("Loading existing key space: " + space_name);
// Load the key space from disk
if load_key_space(space_name, password) {
print("✓ Key space loaded successfully");
// List available keypairs
let keypairs = list_keypairs();
print("Available keypairs: " + keypairs);
// Use both keypairs to sign different messages
if select_keypair("persistent_key1") {
print("\nUsing persistent_key1:");
let message1 = "Message signed with the first keypair";
let signature1 = sign(message1);
print("Message: " + message1);
print("Signature: " + signature1);
let is_valid1 = verify(message1, signature1);
if is_valid1 {
print("Verification: ✓ Valid");
} else {
print("Verification: ✗ Invalid");
}
}
if select_keypair("persistent_key2") {
print("\nUsing persistent_key2:");
let message2 = "Message signed with the second keypair";
let signature2 = sign(message2);
print("Message: " + message2);
print("Signature: " + signature2);
let is_valid2 = verify(message2, signature2);
if is_valid2 {
print("Verification: ✓ Valid");
} else {
print("Verification: ✗ Invalid");
}
}
// Create an Ethereum wallet using one of the keypairs
print("\nCreating Ethereum wallet from persistent keypair:");
if select_keypair("persistent_key1") {
if create_ethereum_wallet() {
print("✓ Ethereum wallet created");
let address = get_ethereum_address();
print("Ethereum address: " + address);
} else {
print("✗ Failed to create Ethereum wallet");
}
}
} else {
print("✗ Failed to load key space. Make sure you've run key_persistence_example.rhai first.");
}
print("\nScript execution completed!");

View File

@ -1,133 +0,0 @@
// Basic example of using the Mycelium client in Rhai
// API URL for Mycelium
let api_url = "http://localhost:8989";
// Get node information
print("Getting node information:");
try {
let node_info = mycelium_get_node_info(api_url);
print(`Node subnet: ${node_info.nodeSubnet}`);
print(`Node public key: ${node_info.nodePubkey}`);
} catch(err) {
print(`Error getting node info: ${err}`);
}
// List all peers
print("\nListing all peers:");
try {
let peers = mycelium_list_peers(api_url);
if peers.is_empty() {
print("No peers connected.");
} else {
for peer in peers {
print(`Peer Endpoint: ${peer.endpoint.proto}://${peer.endpoint.socketAddr}`);
print(` Type: ${peer.type}`);
print(` Connection State: ${peer.connectionState}`);
print(` Bytes sent: ${peer.txBytes}`);
print(` Bytes received: ${peer.rxBytes}`);
}
}
} catch(err) {
print(`Error listing peers: ${err}`);
}
// Add a new peer
print("\nAdding a new peer:");
let new_peer_address = "tcp://65.21.231.58:9651";
try {
let result = mycelium_add_peer(api_url, new_peer_address);
print(`Peer added: ${result.success}`);
} catch(err) {
print(`Error adding peer: ${err}`);
}
// List selected routes
print("\nListing selected routes:");
try {
let routes = mycelium_list_selected_routes(api_url);
if routes.is_empty() {
print("No selected routes.");
} else {
for route in routes {
print(`Subnet: ${route.subnet}`);
print(` Next hop: ${route.nextHop}`);
print(` Metric: ${route.metric}`);
}
}
} catch(err) {
print(`Error listing routes: ${err}`);
}
// List fallback routes
print("\nListing fallback routes:");
try {
let routes = mycelium_list_fallback_routes(api_url);
if routes.is_empty() {
print("No fallback routes.");
} else {
for route in routes {
print(`Subnet: ${route.subnet}`);
print(` Next hop: ${route.nextHop}`);
print(` Metric: ${route.metric}`);
}
}
} catch(err) {
print(`Error listing fallback routes: ${err}`);
}
// Send a message
// TO SEND A MESSAGE FILL IN THE DESTINATION IP ADDRESS
// -----------------------------------------------------//
// print("\nSending a message:");
// let destination = < FILL IN CORRECT DEST IP >
// let topic = "test";
// let message = "Hello from Rhai!";
// let deadline_secs = 60;
// try {
// let result = mycelium_send_message(api_url, destination, topic, message, deadline_secs);
// print(`Message sent: ${result.success}`);
// if result.id {
// print(`Message ID: ${result.id}`);
// }
// } catch(err) {
// print(`Error sending message: ${err}`);
// }
// Receive messages
// RECEIVING MESSAGES SHOULD BE DONE ON THE DESTINATION NODE FROM THE CALL ABOVE
// -----------------------------------------------------------------------------//
// print("\nReceiving messages:");
// let receive_topic = "test";
// let count = 5;
// try {
// let messages = mycelium_receive_messages(api_url, receive_topic, count);
// if messages.is_empty() {
// print("No messages received.");
// } else {
// for msg in messages {
// print(`Message from: ${msg.source}`);
// print(` Topic: ${msg.topic}`);
// print(` Content: ${msg.content}`);
// print(` Timestamp: ${msg.timestamp}`);
// }
// }
// } catch(err) {
// print(`Error receiving messages: ${err}`);
// }
// Remove a peer
print("\nRemoving a peer:");
let peer_id = "tcp://65.21.231.58:9651"; // This is the peer we added earlier
try {
let result = mycelium_remove_peer(api_url, peer_id);
print(`Peer removed: ${result.success}`);
} catch(err) {
print(`Error removing peer: ${err}`);
}

View File

@ -1,31 +0,0 @@
// Script to receive Mycelium messages
// API URL for Mycelium
let api_url = "http://localhost:2222";
// Receive messages
// This script will listen for messages on a specific topic.
// Ensure the sender script is using the same topic.
// -----------------------------------------------------------------------------//
print("\nReceiving messages:");
let receive_topic = "test_topic";
let wait_deadline_secs = 100;
print(`Listening for messages on topic '${receive_topic}'...`);
try {
let messages = mycelium_receive_messages(api_url, receive_topic, wait_deadline_secs);
if messages.is_empty() {
// print("No new messages received in this poll.");
} else {
print("Received a message:");
print(` Message id: ${messages.id}`);
print(` Message from: ${messages.srcIp}`);
print(` Topic: ${messages.topic}`);
print(` Payload: ${messages.payload}`);
}
} catch(err) {
print(`Error receiving messages: ${err}`);
}
print("Finished attempting to receive messages.");

View File

@ -1,25 +0,0 @@
// Script to send a Mycelium message
// API URL for Mycelium
let api_url = "http://localhost:1111";
// Send a message
// TO SEND A MESSAGE FILL IN THE DESTINATION IP ADDRESS
// -----------------------------------------------------//
print("\nSending a message:");
let destination = "50e:6d75:4568:366e:f75:2ac3:bbb1:3fdd"; // IMPORTANT: Replace with the actual destination IP address
let topic = "test_topic";
let message = "Hello from Rhai sender!";
let deadline_secs = -10; // Seconds we wait for a reply
try {
print(`Attempting to send message to ${destination} on topic '${topic}'`);
let result = mycelium_send_message(api_url, destination, topic, message, deadline_secs);
print(`result: ${result}`);
print(`Message sent: ${result.success}`);
if result.id != "" {
print(`Message ID: ${result.id}`);
}
} catch(err) {
print(`Error sending message: ${err}`);
}

84
examples/nerdctl.rs Normal file
View File

@ -0,0 +1,84 @@
//! Example usage of the nerdctl module
//!
//! This file demonstrates how to use the nerdctl module to perform
//! common container operations like creating containers, running commands,
//! and managing images.
use sal::virt::nerdctl::{self, NerdctlError};
/// Run a complete nerdctl workflow example
pub fn run_nerdctl_example() -> Result<(), NerdctlError> {
println!("Starting nerdctl example workflow...");
// Step 1: Pull an image
println!("\n=== Pulling nginx:latest image ===");
let pull_result = nerdctl::image_pull("nginx:latest")?;
println!("Pull output: {}", pull_result.stdout);
// Step 2: Create a container from the image
println!("\n=== Creating container from nginx:latest ===");
// Use "native" snapshotter to avoid overlay mount issues
let run_result = nerdctl::run("nginx:latest", Some("my-nginx"), true, Some(&["8080:80"]), Some("native"))?;
println!("Container created: {}", run_result.stdout.trim());
let container_id = "my-nginx"; // Using the name we specified
// Step 3: Execute a command in the container
println!("\n=== Installing curl in container ===");
let update_result = nerdctl::exec(container_id, "apt-get update")?;
println!("Update output: {}", update_result.stdout);
let install_result = nerdctl::exec(container_id, "apt-get install -y curl")?;
println!("Installation output: {}", install_result.stdout);
// Step 4: Copy a file into the container (assuming nginx.conf exists)
println!("\n=== Copying configuration file to container ===");
nerdctl::copy("./nginx.conf", format!("{}:/etc/nginx/nginx.conf", container_id).as_str())?;
// Step 5: Commit the container to create a new image
println!("\n=== Committing container to create image ===");
let image_name = "my-custom-nginx:latest";
nerdctl::image_commit(container_id, image_name)?;
println!("Created image: {}", image_name);
// Step 6: Stop and remove the container
println!("\n=== Stopping and removing container ===");
nerdctl::stop(container_id)?;
nerdctl::remove(container_id)?;
println!("Container stopped and removed");
// Step 7: Create a new container from our custom image
println!("\n=== Creating container from custom image ===");
// Use "native" snapshotter to avoid overlay mount issues
nerdctl::run(image_name, Some("nginx-custom"), true, Some(&["8081:80"]), Some("native"))?;
println!("Custom container created");
// Step 8: List images
println!("\n=== Listing images ===");
let images_result = nerdctl::images()?;
println!("Images: \n{}", images_result.stdout);
// Step 9: Clean up (optional in a real workflow)
println!("\n=== Cleaning up ===");
nerdctl::stop("nginx-custom")?;
nerdctl::remove("nginx-custom")?;
nerdctl::image_remove(image_name)?;
println!("\nNerdctl example workflow completed successfully!");
Ok(())
}
/// Main function to run all examples
pub fn run_all_examples() -> Result<(), NerdctlError> {
println!("=== NERDCTL MODULE EXAMPLES ===\n");
run_nerdctl_example()?;
println!("\nNote that these examples require nerdctl to be installed on your system");
println!("and may require root/sudo privileges depending on your setup.");
Ok(())
}
fn main() {
let _ = run_all_examples().unwrap();
}

View File

@ -1,145 +0,0 @@
// PostgreSQL Authentication Example
//
// This example demonstrates how to use the PostgreSQL client module with authentication:
// - Create a PostgreSQL configuration with authentication
// - Connect to PostgreSQL using the configuration
// - Perform basic operations
//
// Prerequisites:
// - PostgreSQL server must be running
// - You need to know the username and password for the PostgreSQL server
// Helper function to check if PostgreSQL is available
fn is_postgres_available() {
try {
// Try to execute a simple connection
let connect_result = pg_connect();
return connect_result;
} catch(err) {
print(`PostgreSQL connection error: ${err}`);
return false;
}
}
// Main function
fn main() {
print("=== PostgreSQL Authentication Example ===");
// Check if PostgreSQL is available
let postgres_available = is_postgres_available();
if !postgres_available {
print("PostgreSQL server is not available. Please check your connection settings.");
return;
}
print("✓ PostgreSQL server is available");
// Step 1: Create a PostgreSQL configuration with authentication
print("\n1. Creating PostgreSQL configuration with authentication...");
// Replace these values with your actual PostgreSQL credentials
let pg_host = "localhost";
let pg_port = 5432;
let pg_user = "postgres";
let pg_password = "your_password_here"; // Replace with your actual password
let pg_database = "postgres";
// Create a configuration builder
let config = pg_config_builder();
// Configure the connection
config = config.host(pg_host);
config = config.port(pg_port);
config = config.user(pg_user);
config = config.password(pg_password);
config = config.database(pg_database);
// Build the connection string
let connection_string = config.build_connection_string();
print(`✓ Created PostgreSQL configuration with connection string: ${connection_string}`);
// Step 2: Connect to PostgreSQL using the configuration
print("\n2. Connecting to PostgreSQL with authentication...");
try {
let connect_result = pg_connect_with_config(config);
if (connect_result) {
print("✓ Successfully connected to PostgreSQL with authentication");
} else {
print("✗ Failed to connect to PostgreSQL with authentication");
return;
}
} catch(err) {
print(`✗ Error connecting to PostgreSQL: ${err}`);
return;
}
// Step 3: Perform basic operations
print("\n3. Performing basic operations...");
// Create a test table
let table_name = "auth_example_table";
let create_table_query = `
CREATE TABLE IF NOT EXISTS ${table_name} (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
value INTEGER
)
`;
try {
let create_result = pg_execute(create_table_query);
print(`✓ Successfully created table ${table_name}`);
} catch(err) {
print(`✗ Error creating table: ${err}`);
return;
}
// Insert data
let insert_query = `
INSERT INTO ${table_name} (name, value)
VALUES ('test_name', 42)
`;
try {
let insert_result = pg_execute(insert_query);
print(`✓ Successfully inserted data into table ${table_name}`);
} catch(err) {
print(`✗ Error inserting data: ${err}`);
}
// Query data
let select_query = `
SELECT * FROM ${table_name}
`;
try {
let select_result = pg_query(select_query);
print(`✓ Successfully queried data from table ${table_name}`);
print(` Found ${select_result.len()} rows`);
// Display the results
for row in select_result {
print(` Row: id=${row.id}, name=${row.name}, value=${row.value}`);
}
} catch(err) {
print(`✗ Error querying data: ${err}`);
}
// Clean up
let drop_query = `
DROP TABLE IF EXISTS ${table_name}
`;
try {
let drop_result = pg_execute(drop_query);
print(`✓ Successfully dropped table ${table_name}`);
} catch(err) {
print(`✗ Error dropping table: ${err}`);
}
print("\nExample completed successfully!");
}
// Run the main function
main();

View File

@ -1,132 +0,0 @@
// PostgreSQL Basic Operations Example
//
// This example demonstrates how to use the PostgreSQL client module to:
// - Connect to a PostgreSQL database
// - Create a table
// - Insert data
// - Query data
// - Update data
// - Delete data
// - Drop a table
//
// Prerequisites:
// - PostgreSQL server must be running
// - Environment variables should be set for connection details:
// - POSTGRES_HOST: PostgreSQL server host (default: localhost)
// - POSTGRES_PORT: PostgreSQL server port (default: 5432)
// - POSTGRES_USER: PostgreSQL username (default: postgres)
// - POSTGRES_PASSWORD: PostgreSQL password
// - POSTGRES_DB: PostgreSQL database name (default: postgres)
// Helper function to check if PostgreSQL is available
fn is_postgres_available() {
try {
// Try to execute a simple connection
let connect_result = pg_connect();
return connect_result;
} catch(err) {
print(`PostgreSQL connection error: ${err}`);
return false;
}
}
// Main function
fn main() {
print("=== PostgreSQL Basic Operations Example ===");
// Check if PostgreSQL is available
let postgres_available = is_postgres_available();
if !postgres_available {
print("PostgreSQL server is not available. Please check your connection settings.");
return;
}
print("✓ Connected to PostgreSQL server");
// Define table name
let table_name = "rhai_example_users";
// Step 1: Create a table
print("\n1. Creating table...");
let create_table_query = `
CREATE TABLE IF NOT EXISTS ${table_name} (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
age INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`;
let create_result = pg_execute(create_table_query);
print(`✓ Table created (result: ${create_result})`);
// Step 2: Insert data
print("\n2. Inserting data...");
let insert_queries = [
`INSERT INTO ${table_name} (name, email, age) VALUES ('Alice', 'alice@example.com', 30)`,
`INSERT INTO ${table_name} (name, email, age) VALUES ('Bob', 'bob@example.com', 25)`,
`INSERT INTO ${table_name} (name, email, age) VALUES ('Charlie', 'charlie@example.com', 35)`
];
for query in insert_queries {
let insert_result = pg_execute(query);
print(`✓ Inserted row (result: ${insert_result})`);
}
// Step 3: Query all data
print("\n3. Querying all data...");
let select_query = `SELECT * FROM ${table_name}`;
let rows = pg_query(select_query);
print(`Found ${rows.len()} rows:`);
for row in rows {
print(` ID: ${row.id}, Name: ${row.name}, Email: ${row.email}, Age: ${row.age}, Created: ${row.created_at}`);
}
// Step 4: Query specific data
print("\n4. Querying specific data...");
let select_one_query = `SELECT * FROM ${table_name} WHERE name = 'Alice'`;
let alice = pg_query_one(select_one_query);
print(`Found Alice:`);
print(` ID: ${alice.id}, Name: ${alice.name}, Email: ${alice.email}, Age: ${alice.age}`);
// Step 5: Update data
print("\n5. Updating data...");
let update_query = `UPDATE ${table_name} SET age = 31 WHERE name = 'Alice'`;
let update_result = pg_execute(update_query);
print(`✓ Updated Alice's age (result: ${update_result})`);
// Verify update
let verify_query = `SELECT * FROM ${table_name} WHERE name = 'Alice'`;
let updated_alice = pg_query_one(verify_query);
print(` Updated Alice: ID: ${updated_alice.id}, Name: ${updated_alice.name}, Age: ${updated_alice.age}`);
// Step 6: Delete data
print("\n6. Deleting data...");
let delete_query = `DELETE FROM ${table_name} WHERE name = 'Bob'`;
let delete_result = pg_execute(delete_query);
print(`✓ Deleted Bob (result: ${delete_result})`);
// Verify deletion
let count_query = `SELECT COUNT(*) as count FROM ${table_name}`;
let count_result = pg_query_one(count_query);
print(` Remaining rows: ${count_result.count}`);
// Step 7: Drop table
print("\n7. Dropping table...");
let drop_query = `DROP TABLE IF EXISTS ${table_name}`;
let drop_result = pg_execute(drop_query);
print(`✓ Dropped table (result: ${drop_result})`);
// Reset connection
print("\n8. Resetting connection...");
let reset_result = pg_reset();
print(`✓ Reset connection (result: ${reset_result})`);
print("\nExample completed successfully!");
}
// Run the main function
main();

View File

@ -1,28 +0,0 @@
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

@ -1,39 +0,0 @@
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

@ -1,29 +0,0 @@
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

@ -1,36 +0,0 @@
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

@ -1,18 +0,0 @@
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

@ -1,29 +0,0 @@
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

@ -1,13 +0,0 @@
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

@ -1,22 +0,0 @@
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

@ -1,25 +0,0 @@
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,131 +0,0 @@
// Redis Authentication Example
//
// This example demonstrates how to use the Redis client module with authentication:
// - Create a Redis configuration with authentication
// - Connect to Redis using the configuration
// - Perform basic operations
//
// Prerequisites:
// - Redis server must be running with authentication enabled
// - You need to know the password for the Redis server
// Helper function to check if Redis is available
fn is_redis_available() {
try {
// Try to execute a simple ping
let ping_result = redis_ping();
return ping_result == "PONG";
} catch(err) {
print(`Redis connection error: ${err}`);
return false;
}
}
// Main function
fn main() {
print("=== Redis Authentication Example ===");
// Check if Redis is available
let redis_available = is_redis_available();
if !redis_available {
print("Redis server is not available. Please check your connection settings.");
return;
}
print("✓ Redis server is available");
// Step 1: Create a Redis configuration with authentication
print("\n1. Creating Redis configuration with authentication...");
// Replace these values with your actual Redis credentials
let redis_host = "localhost";
let redis_port = 6379;
let redis_password = "your_password_here"; // Replace with your actual password
// Create a configuration builder
let config = redis_config_builder();
// Configure the connection
config = config.host(redis_host);
config = config.port(redis_port);
config = config.password(redis_password);
// Build the connection URL
let connection_url = config.build_connection_url();
print(`✓ Created Redis configuration with URL: ${connection_url}`);
// Step 2: Connect to Redis using the configuration
print("\n2. Connecting to Redis with authentication...");
try {
let connect_result = redis_connect_with_config(config);
if (connect_result) {
print("✓ Successfully connected to Redis with authentication");
} else {
print("✗ Failed to connect to Redis with authentication");
return;
}
} catch(err) {
print(`✗ Error connecting to Redis: ${err}`);
return;
}
// Step 3: Perform basic operations
print("\n3. Performing basic operations...");
// Set a key
let set_key = "auth_example_key";
let set_value = "This value was set using authentication";
try {
let set_result = redis_set(set_key, set_value);
if (set_result) {
print(`✓ Successfully set key '${set_key}'`);
} else {
print(`✗ Failed to set key '${set_key}'`);
}
} catch(err) {
print(`✗ Error setting key: ${err}`);
}
// Get the key
try {
let get_result = redis_get(set_key);
if (get_result == set_value) {
print(`✓ Successfully retrieved key '${set_key}': '${get_result}'`);
} else {
print(`✗ Retrieved incorrect value for key '${set_key}': '${get_result}'`);
}
} catch(err) {
print(`✗ Error getting key: ${err}`);
}
// Delete the key
try {
let del_result = redis_del(set_key);
if (del_result) {
print(`✓ Successfully deleted key '${set_key}'`);
} else {
print(`✗ Failed to delete key '${set_key}'`);
}
} catch(err) {
print(`✗ Error deleting key: ${err}`);
}
// Verify the key is gone
try {
let verify_result = redis_get(set_key);
if (verify_result == "") {
print(`✓ Verified key '${set_key}' was deleted`);
} else {
print(`✗ Key '${set_key}' still exists with value: '${verify_result}'`);
}
} catch(err) {
print(`✗ Error verifying deletion: ${err}`);
}
print("\nExample completed successfully!");
}
// Run the main function
main();

View File

@ -1,78 +0,0 @@
// Basic example of using the Zinit client in Rhai
// Socket path for Zinit
let socket_path = "/var/run/zinit.sock";
// List all services
print("Listing all services:");
let services = zinit_list(socket_path);
if services.is_empty() {
print("No services found.");
} else {
// Iterate over the keys of the map
for name in services.keys() {
let state = services[name];
print(`${name}: ${state}`);
}
}
// Get status of a specific service
let service_name = "test";
print(`Getting status for ${service_name}:`);
try {
let status = zinit_status(socket_path, service_name);
print(`Service: ${status.name}`);
print(`PID: ${status.pid}`);
print(`State: ${status.state}`);
print(`Target: ${status.target}`);
print("Dependencies:");
for (dep, state) in status.after.keys() {
print(` ${dep}: ${state}`);
}
} catch(err) {
print(`Error getting status: ${err}`);
}
// Create a new service
print("\nCreating a new service:");
let new_service = "rhai-test-service";
let exec_command = "echo 'Hello from Rhai'";
let oneshot = true;
try {
let result = zinit_create_service(socket_path, new_service, exec_command, oneshot);
print(`Service created: ${result}`);
// Monitor the service
print("\nMonitoring the service:");
let monitor_result = zinit_monitor(socket_path, new_service);
print(`Service monitored: ${monitor_result}`);
// Start the service
print("\nStarting the service:");
let start_result = zinit_start(socket_path, new_service);
print(`Service started: ${start_result}`);
// Get logs for a specific service
print("\nGetting logs:");
let logs = zinit_logs(socket_path, new_service);
for log in logs {
print(log);
}
// Clean up
print("\nCleaning up:");
let stop_result = zinit_stop(socket_path, new_service);
print(`Service stopped: ${stop_result}`);
let forget_result = zinit_forget(socket_path, new_service);
print(`Service forgotten: ${forget_result}`);
let delete_result = zinit_delete_service(socket_path, new_service);
print(`Service deleted: ${delete_result}`);
} catch(err) {
print(`Error: ${err}`);
}

View File

@ -1,172 +0,0 @@
// 01_builder_pattern.rhai
// Tests for Buildah Builder pattern
// Custom assert function
fn assert_true(condition, message) {
if !condition {
print(`ASSERTION FAILED: ${message}`);
throw message;
}
}
// Custom assert_eq function
fn assert_eq(actual, expected, message) {
if actual != expected {
print(`ASSERTION FAILED: ${message}`);
print(`Expected: "${expected}"`);
print(`Actual: "${actual}"`);
throw message;
}
}
// Helper function to check if buildah is available
fn is_buildah_available() {
try {
let result = run("which buildah");
return result.success;
} catch(err) {
return false;
}
}
print("=== Testing Buildah Builder Pattern ===");
// Check if buildah is available
let buildah_available = is_buildah_available();
if !buildah_available {
print("Buildah is not available. Skipping Buildah tests.");
// Exit gracefully without error
return;
}
print("✓ Buildah is available");
// Test creating a new Builder
print("Testing bah_new()...");
try {
let builder = bah_new("rhai_test_container", "alpine:latest");
// Test Builder properties
print("Testing Builder properties...");
assert_true(builder.container_id != "", "Container ID should not be empty");
assert_eq(builder.name, "rhai_test_container", "Container name should match");
assert_eq(builder.image, "alpine:latest", "Image name should match");
// Test debug mode
print("Testing debug mode...");
assert_true(!builder.debug_mode, "Debug mode should be off by default");
builder.debug_mode = true;
assert_true(builder.debug_mode, "Debug mode should be on after setting");
// Test running a command
print("Testing run()...");
let result = builder.run("echo 'Hello from container'");
assert_true(result.success, "Command should succeed");
assert_true(result.stdout.contains("Hello from container"), "Command output should contain expected text");
print("✓ run(): Command executed successfully");
// Test writing content to a file in the container
print("Testing write_content()...");
let content = "Hello from a file";
builder.write_content(content, "/test_file.txt");
// Verify the content was written
let read_result = builder.run("cat /test_file.txt");
assert_true(read_result.success, "Command should succeed");
assert_true(read_result.stdout.contains(content), "File content should match what was written");
print("✓ write_content(): Content written successfully");
// Test reading content from a file in the container
print("Testing read_content()...");
let read_content = builder.read_content("/test_file.txt");
assert_true(read_content.contains(content), "Read content should match what was written");
print("✓ read_content(): Content read successfully");
// Test setting entrypoint
print("Testing set_entrypoint()...");
let entrypoint = ["/bin/sh", "-c"];
builder.set_entrypoint(entrypoint);
print("✓ set_entrypoint(): Entrypoint set successfully");
// Test setting cmd
print("Testing set_cmd()...");
let cmd = ["echo", "Hello from CMD"];
builder.set_cmd(cmd);
print("✓ set_cmd(): CMD set successfully");
// Test adding a file
print("Testing add()...");
// Create a test file
file_write("test_add_file.txt", "Test content for add");
builder.add("test_add_file.txt", "/");
// Verify the file was added
let add_result = builder.run("cat /test_add_file.txt");
assert_true(add_result.success, "Command should succeed");
assert_true(add_result.stdout.contains("Test content for add"), "Added file content should match");
print("✓ add(): File added successfully");
// Test copying a file
print("Testing copy()...");
// Create a test file
file_write("test_copy_file.txt", "Test content for copy");
builder.copy("test_copy_file.txt", "/");
// Verify the file was copied
let copy_result = builder.run("cat /test_copy_file.txt");
assert_true(copy_result.success, "Command should succeed");
assert_true(copy_result.stdout.contains("Test content for copy"), "Copied file content should match");
print("✓ copy(): File copied successfully");
// Test committing to an image
print("Testing commit()...");
let image_name = "rhai_test_image:latest";
builder.commit(image_name);
print("✓ commit(): Container committed to image successfully");
// Test removing the container
print("Testing remove()...");
builder.remove();
print("✓ remove(): Container removed successfully");
// Clean up test files
delete("test_add_file.txt");
delete("test_copy_file.txt");
// Test image operations
print("Testing image operations...");
// Test listing images
print("Testing images()...");
let images = builder.images();
assert_true(images.len() > 0, "There should be at least one image");
print("✓ images(): Images listed successfully");
// Test removing the image
print("Testing image_remove()...");
builder.image_remove(image_name);
print("✓ image_remove(): Image removed successfully");
print("All Builder pattern tests completed successfully!");
} catch(err) {
print(`Error: ${err}`);
// Clean up in case of error
try {
// Remove test container if it exists
run("buildah rm rhai_test_container");
} catch(_) {}
try {
// Remove test image if it exists
run("buildah rmi rhai_test_image:latest");
} catch(_) {}
try {
// Remove test files if they exist
delete("test_add_file.txt");
delete("test_copy_file.txt");
} catch(_) {}
throw err;
}

View File

@ -1,150 +0,0 @@
// 02_image_operations.rhai
// Tests for Buildah image operations
// Custom assert function
fn assert_true(condition, message) {
if !condition {
print(`ASSERTION FAILED: ${message}`);
throw message;
}
}
// Custom assert_eq function
fn assert_eq(actual, expected, message) {
if actual != expected {
print(`ASSERTION FAILED: ${message}`);
print(`Expected: "${expected}"`);
print(`Actual: "${actual}"`);
throw message;
}
}
// Helper function to check if buildah is available
fn is_buildah_available() {
try {
let result = run("which buildah");
return result.success;
} catch(err) {
return false;
}
}
// Helper function to check if an image exists
fn image_exists(image_name) {
try {
let result = run(`buildah images -q ${image_name}`);
return result.success && result.stdout.trim() != "";
} catch(err) {
return false;
}
}
print("=== Testing Buildah Image Operations ===");
// Check if buildah is available
let buildah_available = is_buildah_available();
if !buildah_available {
print("Buildah is not available. Skipping Buildah tests.");
// Exit gracefully without error
return;
}
print("✓ Buildah is available");
// Create a temporary directory for testing
let test_dir = "rhai_test_buildah";
mkdir(test_dir);
try {
// Create a builder for testing
let builder = bah_new("rhai_test_container", "alpine:latest");
// Enable debug mode
builder.debug_mode = true;
// Test image_pull
print("Testing image_pull()...");
// Use a small image for testing
let pull_result = builder.image_pull("alpine:3.14", true);
assert_true(pull_result.success, "Image pull should succeed");
print("✓ image_pull(): Image pulled successfully");
// Test image_tag
print("Testing image_tag()...");
let tag_result = builder.image_tag("alpine:3.14", "rhai_test_tag:latest");
assert_true(tag_result.success, "Image tag should succeed");
print("✓ image_tag(): Image tagged successfully");
// Test images (list)
print("Testing images()...");
let images = builder.images();
assert_true(images.len() > 0, "There should be at least one image");
// Find our tagged image
let found_tag = false;
for image in images {
if image.names.contains("rhai_test_tag:latest") {
found_tag = true;
break;
}
}
assert_true(found_tag, "Tagged image should be in the list");
print("✓ images(): Images listed successfully");
// Test build
print("Testing build()...");
// Create a simple Dockerfile
let dockerfile_content = `FROM alpine:latest
RUN echo "Hello from Dockerfile" > /hello.txt
CMD ["cat", "/hello.txt"]
`;
file_write(`${test_dir}/Dockerfile`, dockerfile_content);
// Build the image
let build_result = builder.build("rhai_test_build:latest", test_dir, "Dockerfile", "oci");
assert_true(build_result.success, "Image build should succeed");
print("✓ build(): Image built successfully");
// Verify the built image exists
assert_true(image_exists("rhai_test_build:latest"), "Built image should exist");
// Test image_remove
print("Testing image_remove()...");
// Remove the tagged image
let remove_tag_result = builder.image_remove("rhai_test_tag:latest");
assert_true(remove_tag_result.success, "Image removal should succeed");
print("✓ image_remove(): Tagged image removed successfully");
// Remove the built image
let remove_build_result = builder.image_remove("rhai_test_build:latest");
assert_true(remove_build_result.success, "Image removal should succeed");
print("✓ image_remove(): Built image removed successfully");
// Clean up
builder.remove();
print("✓ Cleanup: Container removed");
print("All image operations tests completed successfully!");
} catch(err) {
print(`Error: ${err}`);
// Clean up in case of error
try {
// Remove test container if it exists
run("buildah rm rhai_test_container");
} catch(_) {}
try {
// Remove test images if they exist
run("buildah rmi rhai_test_tag:latest");
run("buildah rmi rhai_test_build:latest");
} catch(_) {}
throw err;
} finally {
// Clean up test directory
delete(test_dir);
print("✓ Cleanup: Test directory removed");
}

View File

@ -1,127 +0,0 @@
// 03_container_operations.rhai
// Tests for Buildah container operations
// Custom assert function
fn assert_true(condition, message) {
if !condition {
print(`ASSERTION FAILED: ${message}`);
throw message;
}
}
// Custom assert_eq function
fn assert_eq(actual, expected, message) {
if actual != expected {
print(`ASSERTION FAILED: ${message}`);
print(`Expected: "${expected}"`);
print(`Actual: "${actual}"`);
throw message;
}
}
// Helper function to check if buildah is available
fn is_buildah_available() {
try {
let result = run("which buildah");
return result.success;
} catch(err) {
return false;
}
}
print("=== Testing Buildah Container Operations ===");
// Check if buildah is available
let buildah_available = is_buildah_available();
if !buildah_available {
print("Buildah is not available. Skipping Buildah tests.");
// Exit gracefully without error
return;
}
print("✓ Buildah is available");
try {
// Test creating a new Builder
print("Testing bah_new() and reset()...");
let builder = bah_new("rhai_test_container", "alpine:latest");
// Enable debug mode
builder.debug_mode = true;
// Test reset
print("Testing reset()...");
builder.reset();
print("✓ reset(): Container reset successfully");
// Create a new container
builder = bah_new("rhai_test_container", "alpine:latest");
// Test config
print("Testing config()...");
let config_options = #{
"LABEL": "rhai_test=true",
"ENV": "TEST_VAR=test_value"
};
builder.config(config_options);
print("✓ config(): Container configured successfully");
// Test run with isolation
print("Testing run_with_isolation()...");
let isolation_result = builder.run_with_isolation("echo 'Hello with isolation'", "oci");
assert_true(isolation_result.success, "Command with isolation should succeed");
assert_true(isolation_result.stdout.contains("Hello with isolation"), "Command output should contain expected text");
print("✓ run_with_isolation(): Command executed successfully");
// Test content operations
print("Testing content operations...");
// Write content to a file
let script_content = `#!/bin/sh
echo "Hello from script"
`;
builder.write_content(script_content, "/script.sh");
// Make the script executable
builder.run("chmod +x /script.sh");
// Run the script
let script_result = builder.run("/script.sh");
assert_true(script_result.success, "Script should execute successfully");
assert_true(script_result.stdout.contains("Hello from script"), "Script output should contain expected text");
print("✓ Content operations: Script created and executed successfully");
// Test commit with config
print("Testing commit with config...");
let commit_options = #{
"author": "Rhai Test",
"message": "Test commit"
};
builder.commit("rhai_test_commit:latest", commit_options);
print("✓ commit(): Container committed with config successfully");
// Clean up
builder.remove();
print("✓ Cleanup: Container removed");
// Remove the committed image
builder.image_remove("rhai_test_commit:latest");
print("✓ Cleanup: Committed image removed");
print("All container operations tests completed successfully!");
} catch(err) {
print(`Error: ${err}`);
// Clean up in case of error
try {
// Remove test container if it exists
run("buildah rm rhai_test_container");
} catch(_) {}
try {
// Remove test image if it exists
run("buildah rmi rhai_test_commit:latest");
} catch(_) {}
throw err;
}

View File

@ -1,155 +0,0 @@
// run_all_tests.rhai
// Runs all Buildah module tests
print("=== Running Buildah Module Tests ===");
// Custom assert function
fn assert_true(condition, message) {
if !condition {
print(`ASSERTION FAILED: ${message}`);
throw message;
}
}
// Helper function to check if buildah is available
fn is_buildah_available() {
try {
let result = run("which buildah");
return result.success;
} catch(e) {
return false;
}
}
// Run each test directly
let passed = 0;
let failed = 0;
let skipped = 0;
let total = 0;
// Check if buildah is available
let buildah_available = is_buildah_available();
if !buildah_available {
print("Buildah is not available. Skipping all Buildah tests.");
skipped = 3; // Skip all three tests
total = 3;
} else {
// Test 1: Builder Pattern
print("\n--- Running Builder Pattern Tests ---");
try {
// Create a builder
let builder = bah_new("rhai_test_container", "alpine:latest");
// Test basic properties
assert_true(builder.container_id != "", "Container ID should not be empty");
assert_true(builder.name == "rhai_test_container", "Container name should match");
// Run a simple command
let result = builder.run("echo 'Hello from container'");
assert_true(result.success, "Command should succeed");
// Clean up
builder.remove();
print("--- Builder Pattern Tests completed successfully ---");
passed += 1;
} catch(err) {
print(`!!! Error in Builder Pattern Tests: ${err}`);
failed += 1;
// Clean up in case of error
try {
run("buildah rm rhai_test_container");
} catch(e) {
// Ignore errors during cleanup
}
}
total += 1;
// Test 2: Image Operations
print("\n--- Running Image Operations Tests ---");
try {
// Create a temporary directory for testing
let test_dir = "rhai_test_buildah";
mkdir(test_dir);
// Create a builder
let builder = bah_new("rhai_test_container", "alpine:latest");
// List images
let images = builder.images();
assert_true(images.len() > 0, "There should be at least one image");
// Clean up
builder.remove();
delete(test_dir);
print("--- Image Operations Tests completed successfully ---");
passed += 1;
} catch(err) {
print(`!!! Error in Image Operations Tests: ${err}`);
failed += 1;
// Clean up in case of error
try {
run("buildah rm rhai_test_container");
delete("rhai_test_buildah");
} catch(e) {
// Ignore errors during cleanup
}
}
total += 1;
// Test 3: Container Operations
print("\n--- Running Container Operations Tests ---");
try {
// Create a builder
let builder = bah_new("rhai_test_container", "alpine:latest");
// Test reset
builder.reset();
// Create a new container
builder = bah_new("rhai_test_container", "alpine:latest");
// Run a command
let result = builder.run("echo 'Hello from container'");
assert_true(result.success, "Command should succeed");
// Clean up
builder.remove();
print("--- Container Operations Tests completed successfully ---");
passed += 1;
} catch(err) {
print(`!!! Error in Container Operations Tests: ${err}`);
failed += 1;
// Clean up in case of error
try {
run("buildah rm rhai_test_container");
} catch(e) {
// Ignore errors during cleanup
}
}
total += 1;
}
print("\n=== Test Summary ===");
print(`Passed: ${passed}`);
print(`Failed: ${failed}`);
print(`Skipped: ${skipped}`);
print(`Total: ${total}`);
if failed == 0 {
if skipped > 0 {
print("\n⚠ All tests skipped or passed!");
} else {
print("\n✅ All tests passed!");
}
} else {
print("\n❌ Some tests failed!");
}
// Return the number of failed tests (0 means success)
failed;

Some files were not shown because too many files have changed in this diff Show More