# rusty-pv
Show progress, elapsed time, ETA, rate, & bytes transferred while data flows through a pipe. Rust port of [Andrew Wood's `pv(1)`](https://www.ivarch.com/programs/pv.shtml) 1.10.5.
[](https://crates.io/crates/rusty-pv)
[](https://docs.rs/rusty-pv)
[](https://github.com/jsh562/rusty-pv/actions/workflows/ci.yml)
[](#msrv)
[](#license)
Drop it into any pipe to watch the bytes move: `rusty-pv backup.tar | gzip > backup.tar.gz` draws a live progress bar with ETA & transfer rate. It adds a token-bucket throttle (`-L`), EMA-smoothed rate display, & IEC/SI unit math, plus a typed library API. Default mode adds parse-time conflict rejection, `--help`/`--version`, & a `completions` subcommand. Strict mode reverts every observable surface to byte-equal pv 1.10.5 for drop-in migration.
Part of the [Rusty portfolio](https://jsh562.github.io/rusty-portfolio).
## Install
```sh
cargo install rusty-pv
# or, with prebuilt binaries:
cargo binstall rusty-pv
# or, download directly from GitHub Releases:
# https://github.com/jsh562/rusty-pv/releases
```
## Usage
```sh
# Show progress copying a file to a block device
rusty-pv ubuntu.iso > /dev/sdb
# Throttle a copy to 5 MiB/s (avoid saturating a slow disk or network link)
rusty-pv -L 5M big.iso > out.iso
# Label a stage in a multi-stage pipeline so you can see which is slow
some-cmd | rusty-pv -N "stage 1" | another-cmd
# Numeric mode for dialog --gauge integration (one percentage per line)
rusty-pv -n -s 100M big.iso > out.iso
# Cursor mode for multiple pv instances in one pipeline (Unix only)
producer | rusty-pv -c -N gen | gzip | rusty-pv -c -N gz > out.gz
# Strict pv-compat mode (drop-in pv 1.10.5 replacement, byte-equal stderr)
rusty-pv --strict ubuntu.iso > /dev/sdb
RUSTY_PV_STRICT=1 rusty-pv ubuntu.iso > /dev/sdb
pv ubuntu.iso > /dev/sdb # via pv-alias argv[0] symlink
# Shell completions
rusty-pv completions bash # > ~/.bash_completion.d/rusty-pv
rusty-pv completions zsh # > ~/.zfunc/_rusty-pv
rusty-pv completions fish # > ~/.config/fish/completions/rusty-pv.fish
rusty-pv completions powershell
```
## Library API
The library exposes the `Pv` / `PvBuilder` / `Reporter` / `Progress` types, the token-bucket throttle, the EMA smoother, & the IEC/SI unit math without any CLI deps. Use it when you want pv-style throttling & reporting inside another Rust tool.
```rust,no_run
use rusty_pv::{Pv, PvBuilder};
use std::io::Cursor;
let pv = PvBuilder::new()
.total_bytes(1024 * 1024)
.rate_limit(500_000) // 500 kB/s
.build();
let mut reader = Cursor::new(vec![0u8; 1024 * 1024]);
let mut writer = Vec::new();
let n = pv.copy(&mut reader, &mut writer).unwrap();
assert_eq!(n, 1024 * 1024);
```
For library-only consumers without CLI deps see the [Cargo Features](#cargo-features) section.
## Cargo Features
`default` enables `full`, which (for this single-capability port) resolves to the `cli` umbrella. `pv-classic` reproduces v0.1.x bare-port behavior matching upstream pv 1.10.5 1:1. To strip the CLI surface use `default-features = false` or `--no-default-features` & add the features you want.
rusty-pv is a **single-capability port**: its one documented job is "show pv-style progress / throughput / ETA / rate while data flows through a pipe". No optional feature leaves are carved beyond the required umbrellas; see [`docs/feature-layout.md`](docs/feature-layout.md) for why.
### Feature matrix
| Feature | Description | Umbrella(s) |
|---|---|---|
| `cli` | All CLI-only dependencies (`clap`, `clap_complete`, `anyhow`, `crossterm`, `signal-hook` on Unix, `fd-lock`) and the binary entry point, mode resolver, stderr renderer, cursor coordinator, signal handlers, and Strict-mode pre-scanner. Library consumers strip via `default-features = false`. | `full`, `pv-classic`, `pv-minimal` |
### Preset bundles
| Bundle | Composition | Use case |
|---|---|---|
| `pv-classic` | `cli` | Drop-in upstream pv 1.10.5 replacement. Strict mode is invoked via `--strict`, `RUSTY_PV_STRICT`, or `pv`/`pv-alias` argv[0] auto-detect. |
| `pv-minimal` | `cli` | Explicit minimal-CLI alias for users who prefer the `<port>-minimal` naming convention seen across other portfolio ports. Identical composition to `pv-classic`. |
### Keep-list workaround (Cargo features are union-only)
Cargo features cannot subtract from `default`. To get "everything except a specific feature," disable defaults & enumerate the features you want:
```sh
cargo install rusty-pv --no-default-features --features "cli"
# → bare CLI. Equivalent to pv-classic / pv-minimal.
```
For the common cases the named [preset bundles](#preset-bundles) are usually sufficient.
### Library-only consumers
```toml
[dependencies]
rusty-pv = { version = "0.2", default-features = false }
```
This strips `clap`, `clap_complete`, `anyhow`, `crossterm`, `signal-hook`, & `fd-lock`. The resulting build pulls only `thiserror` (required by the always-on `PvError` enum).
### Convention authority
This layout follows the portfolio-wide Cargo Features Convention. The "why" lives in [ADR-0006](https://github.com/jsh562/rustylib/blob/main/specs/adrs/0006-cargo-features-convention-for-portfolio-ports.md); the "what" lives in [`project-instructions.md` §Cargo Feature Surface](https://github.com/jsh562/rustylib/blob/main/project-instructions.md). Every Rusty port from v0.2 onward exposes the same umbrella set (`default` / `full` / `cli` / `<port>-classic`), per-port leaves named in kebab-case, & 2 to 4 preset bundles.
## Compatibility
`rusty-pv` has two modes:
- **Default mode.** clap-styled flag parser. Conflicting flag pairs MUST be rejected at parse time. `--help`, `--version`, & the `completions` subcommand are all available.
- **Strict mode** (activated by `--strict`, `RUSTY_PV_STRICT=1`, or invoking the binary as `pv`/`pv-alias`). Byte-equal stderr against upstream v1.10.5 for documented diagnostics. Last-wins flag resolution. `--help`, `--version`, & `completions` MUST be rejected.
The EMA smoothing constant α = 0.3 is locked at v0.1.0 to preserve byte-equal Strict-mode compatibility. Future versions may expose it via an additive library method.
### Display modes
- **Single-line redraw** (default). Carriage-return + erase-to-EOL ANSI sequence. Works on every terminal that supports basic ANSI.
- **Cursor mode (`-c`)**. Unix-only in v0.1.0. Coordinates multiple `rusty-pv` instances in a pipeline so each writes its own row. Windows emits a stderr diagnostic & falls back to single-line redraw.
### Concurrency
The `-c` per-tty file lock guarantees only one `rusty-pv` instance writes ANSI cursor escapes at a time. The cursor-mode design isn't crash-safe; if a `rusty-pv` instance is killed mid-redraw the next surviving instance may briefly overwrite its row. This matches upstream `pv` behavior.
See [`docs/COMPATIBILITY.md`](docs/COMPATIBILITY.md) for the full per-flag matrix.
## What's not shipped
- **`-F` custom format strings.** Default format is fine for the 99% case. Forward-review candidate.
- **`-T` buffer percentage display.** Internal-buffer-fill UI; not part of the byte-equal Strict contract.
- **`-A` peek window.** Niche; deferred.
- **`--bits` bits-per-second display.** Trivially added later as an additive flag.
- **`--remote` / `--query` inter-instance control.** Multi-process IPC; out of scope for v0.1.0.
- **SIGUSR2 handling.** Deprecated by upstream pv.
- **Cursor mode on Windows.** `-c` falls back to single-line redraw with a stderr diagnostic.
## MSRV
Rust **1.85** (edition 2024). Re-verified against the portfolio's stable-minus-two policy at each release.
## License
Dual-licensed under [MIT](LICENSE) or [Apache-2.0](LICENSE-APACHE) at your option.