openrunner-rs

Crates.ioopenrunner-rs
lib.rsopenrunner-rs
version1.0.1
created_at2025-06-08 23:35:16.467275+00
updated_at2025-06-09 02:18:39.599025+00
descriptionA Rust library for running OpenScript
homepagehttps://github.com/openrunner-dev/openrunner-rs
repositoryhttps://github.com/openrunner-dev/openrunner-rs
max_upload_size
id1705325
size145,272
Nik (llamasearchai)

documentation

https://docs.rs/openrunner-rs

README

OpenRunner-RS

Crates.io Documentation CI License

OpenRunner-RS is a powerful, async-first Rust library for executing OpenScript code and shell scripts with fine-grained control over execution environment, I/O redirection, process lifecycle, and error handling.

Features

  • Async-First Design: Built on tokio for high-performance concurrent execution
  • Flexible Script Sources: Execute from strings, files, or remote sources
  • Fine-Grained Control: Full control over environment variables, working directory, I/O redirection
  • Process Management: Spawn long-running processes with full lifecycle control
  • Timeout Support: Built-in timeout handling with graceful process termination
  • Error Handling: Comprehensive error types with detailed context
  • Convenience Macros: Ergonomic macros for common use cases
  • Zero-Copy I/O: Efficient handling of large script outputs
  • Cross-Platform: Works on Linux, macOS, and Windows

Installation

Add to your Cargo.toml:

[dependencies]
openrunner-rs = "1.0"
tokio = { version = "1", features = ["full"] }

Quick Start

Basic Script Execution

use openrunner_rs::{run, ScriptOptions};

#[tokio::main]
async fn main() -> openrunner_rs::Result<()> {
    // Simple script execution
    let options = ScriptOptions::new().openscript_path("/bin/sh");
    let result = run("echo 'Hello, OpenRunner!'", options).await?;
    
    println!("Exit code: {}", result.exit_code);
    println!("Output: {}", result.stdout);
    println!("Duration: {:?}", result.duration);
    
    Ok(())
}

Advanced Configuration

use openrunner_rs::{run, ScriptOptions};
use std::time::Duration;

#[tokio::main]
async fn main() -> openrunner_rs::Result<()> {
    let options = ScriptOptions::new()
        .openscript_path("/bin/bash")
        .working_directory("/tmp")
        .env("LOG_LEVEL", "debug")
        .env("API_KEY", "secret")
        .timeout(Duration::from_secs(30))
        .args(vec!["arg1".to_string(), "arg2".to_string()]);
    
    let result = run(r#"
        echo "Working in: $(pwd)"
        echo "Log level: $LOG_LEVEL"
        echo "Arguments: $1 $2"
        sleep 2
        echo "Task completed!"
    "#, options).await?;
    
    if result.timed_out {
        println!("Script timed out!");
    } else {
        println!("Script completed successfully: {}", result.stdout);
    }
    
    Ok(())
}

Process Spawning

use openrunner_rs::{spawn, ScriptOptions};

#[tokio::main]
async fn main() -> openrunner_rs::Result<()> {
    let options = ScriptOptions::new().openscript_path("/bin/sh");
    
    // Spawn a long-running background process
    let spawn_result = spawn(r#"
        for i in {1..10}; do
            echo "Processing item $i"
            sleep 1
        done
    "#, options).await?;
    
    println!("Spawned process with PID: {:?}", spawn_result.child.id());
    
    // Do other work while process runs...
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
    
    // Wait for completion
    let output = spawn_result.child.wait_with_output().await?;
    println!("Process completed: {}", String::from_utf8_lossy(&output.stdout));
    
    Ok(())
}

API Reference

Core Functions

Function Description
run(script, options) Execute script and wait for completion
run_file(path, options) Execute script from file
spawn(script, options) Spawn script process without waiting
spawn_file(path, options) Spawn script from file

Convenience Macros

Macro Description
run_script!(script, options?) Simplified script execution
spawn_script!(script, options?) Simplified script spawning
run_file_script!(path, options?) Simplified file execution

Configuration Options

ScriptOptions::new()
    .openscript_path("/path/to/interpreter")  // Script interpreter
    .working_directory("/working/dir")        // Working directory
    .env("KEY", "value")                      // Environment variables
    .args(vec!["arg1", "arg2"])              // Command arguments
    .timeout(Duration::from_secs(30))         // Execution timeout
    .stdin(IoOptions::Pipe)                   // Stdin handling
    .stdout(IoOptions::Pipe)                  // Stdout handling
    .stderr(IoOptions::Pipe)                  // Stderr handling
    .clear_env(true)                          // Clear environment
    .exit_on_error(false)                     // Continue on errors
    .print_commands(true)                     // Debug output

Examples

File Execution

use openrunner_rs::{run_file, ScriptOptions};
use std::path::PathBuf;

#[tokio::main]
async fn main() -> openrunner_rs::Result<()> {
    let script_path = PathBuf::from("./scripts/deploy.sh");
    let options = ScriptOptions::new()
        .openscript_path("/bin/bash")
        .env("ENVIRONMENT", "production");
    
    let result = run_file(&script_path, options).await?;
    println!("Deployment result: {}", result.stdout);
    Ok(())
}

Error Handling

use openrunner_rs::{run, ScriptOptions, Error};

#[tokio::main]
async fn main() {
    let options = ScriptOptions::new().openscript_path("/bin/sh");
    
    match run("exit 1", options).await {
        Ok(result) => {
            if result.exit_code != 0 {
                println!("Script failed with code: {}", result.exit_code);
                println!("Error output: {}", result.stderr);
            }
        }
        Err(Error::Timeout(duration)) => {
            println!("Script timed out after {:?}", duration);
        }
        Err(Error::OpenScriptNotFound) => {
            println!("Script interpreter not found in PATH");
        }
        Err(e) => {
            println!("Execution error: {}", e);
        }
    }
}

Using Macros

use openrunner_rs::{run_script, spawn_script, ScriptOptions};

#[tokio::main]
async fn main() -> openrunner_rs::Result<()> {
    let options = ScriptOptions::new().openscript_path("/bin/sh");
    
    // Quick execution with macro
    let result = run_script!("echo 'Hello from macro!'", options).await?;
    println!("Macro result: {}", result.stdout);
    
    // Spawn with macro
    let spawn_result = spawn_script!("sleep 5 && echo 'Background task'", options).await?;
    let output = spawn_result.child.wait_with_output().await?;
    println!("Background result: {}", String::from_utf8_lossy(&output.stdout));
    
    Ok(())
}

Testing

Run the test suite:

# Run all tests
cargo test

# Run with output
cargo test -- --nocapture

# Run integration tests only
cargo test --test integration_test

# Run with coverage
cargo install cargo-tarpaulin
cargo tarpaulin --out Html

Docker Support

Build and test in Docker:

# Build the container
docker build -t openrunner-rs .

# Run tests in container
docker run --rm openrunner-rs cargo test

# Run examples
docker run --rm openrunner-rs cargo run --example basic

Benchmarks

Run performance benchmarks:

# Run all benchmarks
cargo bench

# Run specific benchmark
cargo bench --bench script_execution

# Generate benchmark report
cargo bench -- --output-format html

Cross-Platform Support

OpenRunner-RS works across platforms:

  • Linux: Full support with all features
  • macOS: Full support with all features
  • Windows: Full support with PowerShell and cmd.exe

Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

Development Setup

# Clone the repository
git clone https://github.com/openrunner-dev/openrunner-rs.git
cd openrunner-rs

# Install development dependencies
cargo install cargo-watch cargo-tarpaulin

# Run tests with file watching
cargo watch -x test

# Check code quality
cargo clippy --all-targets --all-features
cargo fmt --check

Changelog

See CHANGELOG.md for version history and breaking changes.

License

This project is licensed under either of

at your option.

Commit count: 0

cargo fmt