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
tokiofeature) - Thread-safe -- progress handles are
Clone + Send + Sync
Installation
Add to your Cargo.toml:
[]
= "0.2"
For async/tokio support:
[]
= { = "0.2", = ["tokio"] }
Quick Start
Progress Bar
use ProgressBar;
let bar = new.with_message;
for _ in 0..100
bar.finish_success;
Output while running:
Downloading ██████████████████████████░░░░░░ 67% 67/100 33/s ETA 1s 1s
On completion:
✔ Downloading -- Download complete (2s)
Spinner
use Spinner;
let spinner = new.with_message;
sleep;
spinner.finish_success;
Multiple Concurrent Tasks
use MultiProgress;
use thread;
let multi = new;
let build = multi.add_bar.with_message;
let tests = multi.add_bar.with_message;
let build_clone = build.clone;
spawn;
for _ in 0..200
tests.finish_success;
multi.join;
Color Themes
use ;
// Green progress bar with cyan metadata
let bar = new.with_theme;
// Blue and cyan tones
let bar = new.with_theme;
// Warm orange and yellow tones
let bar = new.with_theme;
// No colors (plain text)
let bar = new.with_theme;
Themes also apply to spinners:
use ;
let spinner = new
.with_message
.with_theme;
Custom Format Strings
For full control over the bar layout, use a format template:
use ProgressBar;
let bar = new
.with_message
.with_format;
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 ProgressBar;
let bar = new;
// ... 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 ;
// Modern Unicode (default)
let bar = new;
// ASCII-safe for minimal terminals
let bar = new.with_style;
// Compact Unicode
let bar = new.with_style;
Async Usage
With the tokio feature enabled, pulsebar works naturally with async code:
use MultiProgress;
async
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
- Defaults should be excellent. You should never need to configure anything to get good-looking output.
- The API should be obvious. Method names are predictable. Misuse is difficult.
- 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.
- 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:
- Open an issue to discuss significant changes before submitting a PR
- Ensure
cargo test,cargo clippy -- -D warnings, andcargo fmt --checkall pass - Add tests for new functionality
License
MIT