# OpenRunner-RS
[](https://crates.io/crates/openrunner-rs)
[](https://docs.rs/openrunner-rs)
[](https://github.com/openrunner-dev/openrunner-rs/actions)
[](LICENSE-MIT)
**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`:
```toml
[dependencies]
openrunner-rs = "1.0"
tokio = { version = "1", features = ["full"] }
```
## ๐ Quick Start
### Basic Script Execution
```rust
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
```rust
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
```rust
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
| `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
| `run_script!(script, options?)` | Simplified script execution |
| `spawn_script!(script, options?)` | Simplified script spawning |
| `run_file_script!(path, options?)` | Simplified file execution |
### Configuration Options
```rust
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
```rust
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
```rust
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
```rust
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:
```bash
# 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:
```bash
# 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
```bash
# 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
```bash
# Format code
cargo fmt
# Run clippy
cargo clippy -- -D warnings
# Full lint check
make lint
```
## ๐ License
This project is dual-licensed under either:
- [MIT License](LICENSE-MIT)
- [Apache License 2.0](LICENSE-APACHE)
at your option.
## ๐ค Contributing
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details on our development process, how to propose bugfixes and improvements.
## ๐ Support
- ๐ [Documentation](https://docs.rs/openrunner-rs)
- ๐ [Issue Tracker](https://github.com/openrunner-dev/openrunner-rs/issues)
- ๐ฌ [Discussions](https://github.com/openrunner-dev/openrunner-rs/discussions)