#progress-reporting #run-command #progress-bar #stderr #async #redaction #cancellation #ls

ensembler

A library for running commands and reporting progress

27 releases (5 stable)

new 1.1.1 Apr 17, 2026
1.0.2 Feb 22, 2026
0.3.0 Oct 19, 2025
0.2.10 May 16, 2025
0.2.7 Mar 30, 2025

#553 in Rust patterns

Download history 27/week @ 2026-02-21 265/week @ 2026-04-11

293 downloads per month
Used in hk

MIT license

36KB
515 lines

ensembler

A Rust library for executing external commands with advanced output handling and progress reporting.

Features

  • Async execution - Built on Tokio for non-blocking command execution
  • Output capture - Capture stdout, stderr, and combined output
  • Progress integration - Real-time progress bar updates via the clx crate
  • Secret redaction - Automatically redact sensitive data from output
  • Cancellation - Cancel running commands via CancellationToken
  • Cross-platform - Works on Unix and Windows

Installation

Add to your Cargo.toml:

[dependencies]
ensembler = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

Usage

Note: Examples use Unix commands for clarity. On Windows, substitute equivalent commands (e.g., dir for ls, type for cat, timeout for sleep).

Basic Command Execution

use ensembler::CmdLineRunner;

#[tokio::main]
async fn main() -> ensembler::Result<()> {
    let result = CmdLineRunner::new("echo")
        .arg("hello")
        .execute()
        .await?;

    println!("stdout: {}", result.stdout);
    Ok(())
}

Capturing Output

use ensembler::CmdLineRunner;

#[tokio::main]
async fn main() -> ensembler::Result<()> {
    let result = CmdLineRunner::new("ls")
        .arg("-la")
        .current_dir("/tmp")
        .execute()
        .await?;

    println!("stdout: {}", result.stdout);
    println!("stderr: {}", result.stderr);
    println!("exit code: {:?}", result.status.code());
    Ok(())
}

Redacting Secrets

Automatically hide sensitive data in command output:

use ensembler::CmdLineRunner;

#[tokio::main]
async fn main() -> ensembler::Result<()> {
    let api_key = "super-secret-key";

    let result = CmdLineRunner::new("echo")
        .arg(api_key)
        .redact(vec![api_key.to_string()])
        .execute()
        .await?;

    // Output will show "[redacted]" instead of the actual key
    assert!(!result.stdout.contains(api_key));
    Ok(())
}

Piping Input via Stdin

use ensembler::CmdLineRunner;

#[tokio::main]
async fn main() -> ensembler::Result<()> {
    let result = CmdLineRunner::new("cat")
        .stdin_string("hello from stdin")
        .execute()
        .await?;

    assert_eq!(result.stdout.trim(), "hello from stdin");
    Ok(())
}

Cancellation

Cancel long-running commands:

use ensembler::CmdLineRunner;
use tokio_util::sync::CancellationToken;
use std::time::Duration;

#[tokio::main]
async fn main() -> ensembler::Result<()> {
    let cancel = CancellationToken::new();
    let cancel_clone = cancel.clone();

    // Cancel after 1 second
    tokio::spawn(async move {
        tokio::time::sleep(Duration::from_secs(1)).await;
        cancel_clone.cancel();
    });

    let result = CmdLineRunner::new("sleep")
        .arg("60")
        .with_cancel_token(cancel)
        .execute()
        .await;

    // Command was cancelled
    assert!(result.is_err());
    Ok(())
}

Environment Variables

use ensembler::CmdLineRunner;

#[tokio::main]
async fn main() -> ensembler::Result<()> {
    let result = CmdLineRunner::new("bash")
        .arg("-c")
        .arg("echo $MY_VAR")
        .env("MY_VAR", "hello")
        .execute()
        .await?;

    assert_eq!(result.stdout.trim(), "hello");
    Ok(())
}

Error Handling

use ensembler::{CmdLineRunner, Error};

#[tokio::main]
async fn main() {
    let result = CmdLineRunner::new("bash")
        .arg("-c")
        .arg("exit 42")
        .execute()
        .await;

    match result {
        Ok(_) => println!("Command succeeded"),
        Err(Error::ScriptFailed(details)) => {
            let (program, _args, output, cmd_result) = *details;
            println!("Command '{}' failed with exit code {:?}",
                program, cmd_result.status.code());
            println!("Output: {}", output);
        }
        Err(Error::Io(e)) => println!("IO error: {}", e),
        Err(e) => println!("Other error: {}", e),
    }
}

License

MIT

Dependencies

~10–17MB
~321K SLoC