rusty-pv 0.2.0

Pipe viewer — a Rust port of Andrew Wood's `pv(1)` with progress bar, ETA, rate display, token-bucket rate limiting, IEC/SI unit math, SIGWINCH-aware terminal redraw, SIGUSR1 size refresh, multi-instance cursor coordination, and a typed library API.
Documentation
# 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.

[![crates.io](https://img.shields.io/crates/v/rusty-pv.svg)](https://crates.io/crates/rusty-pv)
[![docs.rs](https://docs.rs/rusty-pv/badge.svg)](https://docs.rs/rusty-pv)
[![CI](https://github.com/jsh562/rusty-pv/actions/workflows/ci.yml/badge.svg)](https://github.com/jsh562/rusty-pv/actions/workflows/ci.yml)
[![MSRV](https://img.shields.io/badge/MSRV-1.85-blue.svg)](#msrv)
[![license: MIT OR Apache-2.0](https://img.shields.io/crates/l/rusty-pv.svg)](#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.