pulsebar 0.2.0

Elegant progress reporting for Rust CLIs
Documentation

pulsebar

Elegant progress bars, spinners, and task tracking for Rust terminal applications.

Pulsebar gives your CLI tools polished progress reporting with minimal code. It focuses on beautiful defaults, stable rendering, and an API that's hard to misuse.

Why pulsebar?

  • Beautiful defaults -- looks great without any configuration
  • Minimal API -- a handful of types and methods cover all common use cases
  • Stable rendering -- no flickering, no corrupted output, even with concurrent tasks
  • Native terminal width -- auto-detects terminal size via ioctl (Unix) and Console API (Windows)
  • Color themes -- built-in color themes (Default, Ocean, Sunset) with clean ANSI output
  • Rate and ETA -- automatic throughput measurement and time estimation
  • Custom formats -- template strings for full control over bar layout
  • Async ready -- works with tokio and other async runtimes (optional tokio feature)
  • Thread-safe -- progress handles are Clone + Send + Sync

Installation

Add to your Cargo.toml:

[dependencies]
pulsebar = "0.2"

For async/tokio support:

[dependencies]
pulsebar = { version = "0.2", features = ["tokio"] }

Quick Start

Progress Bar

use pulsebar::ProgressBar;

let bar = ProgressBar::new(100).with_message("Downloading");
for _ in 0..100 {
    bar.advance(1);
    std::thread::sleep(std::time::Duration::from_millis(20));
}
bar.finish_success("Download complete");

Output while running:

Downloading ██████████████████████████░░░░░░  67%  67/100  33/s  ETA 1s  1s

On completion:

✔ Downloading -- Download complete (2s)

Spinner

use pulsebar::Spinner;

let spinner = Spinner::new().with_message("Connecting");
std::thread::sleep(std::time::Duration::from_secs(2));
spinner.finish_success("Connected");

Multiple Concurrent Tasks

use pulsebar::MultiProgress;
use std::thread;

let multi = MultiProgress::new();
let build = multi.add_bar(100).with_message("Compile");
let tests = multi.add_bar(200).with_message("Run tests");

let build_clone = build.clone();
thread::spawn(move || {
    for _ in 0..100 {
        build_clone.advance(1);
        thread::sleep(std::time::Duration::from_millis(20));
    }
    build_clone.finish_success("Compiled");
});

for _ in 0..200 {
    tests.advance(1);
    thread::sleep(std::time::Duration::from_millis(10));
}
tests.finish_success("Tests passed");

multi.join();

Color Themes

use pulsebar::{ProgressBar, Theme};

// Green progress bar with cyan metadata
let bar = ProgressBar::new(100).with_theme(Theme::Default);

// Blue and cyan tones
let bar = ProgressBar::new(100).with_theme(Theme::Ocean);

// Warm orange and yellow tones
let bar = ProgressBar::new(100).with_theme(Theme::Sunset);

// No colors (plain text)
let bar = ProgressBar::new(100).with_theme(Theme::None);

Themes also apply to spinners:

use pulsebar::{Spinner, Theme};

let spinner = Spinner::new()
    .with_message("Working")
    .with_theme(Theme::Default);

Custom Format Strings

For full control over the bar layout, use a format template:

use pulsebar::ProgressBar;

let bar = ProgressBar::new(1000)
    .with_message("Sync")
    .with_format("{msg} [{bar:30}] {pos}/{total} {rate}/s ETA {eta} ({elapsed})");

Supported placeholders

Placeholder Description
{msg} The message
{bar} Progress bar (auto-sized to fill remaining space)
{bar:N} Progress bar with fixed width N
{pos} Current position
{total} Total
{pct} Percentage (e.g., 42%)
{elapsed} Elapsed time
{rate} Throughput (items/sec, auto-formatted)
{eta} Estimated time remaining

Rate and ETA

Pulsebar automatically tracks throughput using an exponential moving average. The rate is displayed in the default bar format and available programmatically:

use pulsebar::ProgressBar;

let bar = ProgressBar::new(1000);
// ... after some progress ...
let items_per_sec = bar.rate();
let seconds_remaining = bar.eta(); // Option<f64>

Rate formatting adapts to magnitude: 42, 1.5K, 2.5M.

Styles

Three built-in visual styles:

use pulsebar::{ProgressBar, Style};

// Modern Unicode (default)
let bar = ProgressBar::new(100);

// ASCII-safe for minimal terminals
let bar = ProgressBar::new(100).with_style(Style::Ascii);

// Compact Unicode
let bar = ProgressBar::new(100).with_style(Style::Compact);

Async Usage

With the tokio feature enabled, pulsebar works naturally with async code:

use pulsebar::MultiProgress;

#[tokio::main]
async fn main() {
    let multi = MultiProgress::new();
    let bar = multi.add_bar(100).with_message("Fetching");

    let bar_clone = bar.clone();
    tokio::spawn(async move {
        for _ in 0..100 {
            bar_clone.advance(1);
            tokio::time::sleep(std::time::Duration::from_millis(20)).await;
        }
        bar_clone.finish_success("Fetched");
    });

    multi.join();
}

API Overview

ProgressBar

Method Description
ProgressBar::new(total) Create a bar with the given total
.with_message(msg) Set initial message (builder)
.with_style(style) Set visual style (builder)
.with_theme(theme) Set color theme (builder)
.with_format(fmt) Set custom format string (builder)
.advance(n) Increment position by n
.set_position(n) Set absolute position
.set_message(msg) Update the message
.position() Get current position
.total() Get total
.rate() Get throughput (items/sec)
.eta() Get estimated time remaining
.finish() Mark as complete
.finish_success(msg) Mark as successful with message
.finish_error(msg) Mark as failed with message

Spinner

Method Description
Spinner::new() Create and start a spinner
.with_message(msg) Set initial message (builder)
.with_style(style) Set visual style (builder)
.with_theme(theme) Set color theme (builder)
.set_message(msg) Update the message
.tick() Manually advance one frame
.finish() Stop the spinner
.finish_success(msg) Stop with success indicator
.finish_error(msg) Stop with error indicator

MultiProgress

Method Description
MultiProgress::new() Create a multi-progress container
.add_bar(total) Add a progress bar
.add_spinner() Add a spinner
.join() Wait for all tasks to complete
.is_finished() Check if all tasks are done

Design Philosophy

  1. Defaults should be excellent. You should never need to configure anything to get good-looking output.
  2. The API should be obvious. Method names are predictable. Misuse is difficult.
  3. Progress reporting must never crash your program. All I/O errors are silently handled. Setting invalid positions is clamped. Double-finishing is a no-op.
  4. Rendering must be stable. No flickering, no garbled output, even under rapid concurrent updates.

Non-TTY Behavior

When stderr is not a terminal (e.g., piped to a file), pulsebar automatically falls back to simple line-based output without ANSI escape codes or cursor manipulation.

Contributing

Contributions are welcome. Please:

  1. Open an issue to discuss significant changes before submitting a PR
  2. Ensure cargo test, cargo clippy -- -D warnings, and cargo fmt --check all pass
  3. Add tests for new functionality

License

MIT