openrunner-rs 1.0.0

A Rust library for running OpenScript
Documentation

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 openrunner-rs

# Interactive development
docker run -it -v $(pwd):/workspace openrunner-rs bash

๐Ÿ”ง Development

Prerequisites

  • Rust 1.70+
  • tokio runtime
  • Shell interpreter (bash, sh, etc.)

Building

# Debug build
cargo build

# Release build
cargo build --release

# Run examples
cargo run --example basic
cargo run --example advanced

# Generate documentation
cargo doc --open

Linting

# Format code
cargo fmt

# Run clippy
cargo clippy -- -D warnings

# Full lint check
make lint

๐Ÿ“ License

This project is dual-licensed under either:

at your option.

๐Ÿค Contributing

Contributions are welcome! Please read our Contributing Guide for details on our development process, how to propose bugfixes and improvements.

๐Ÿ“ž Support