Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

std.cli.progress

Bars and spinners for long-running command-line work, implemented in Plenty over std.io, std.time and std.report. The only new native operation asks whether a process stream is a terminal.

import std.cli.progress as progress
from std.cli.progress import ProgressError

def main() -> Result[(), ProgressError]:
    mut bar = progress.bar(100u64)
    for item in range(100):
        # Process one item here, then count it.
        bar.advance()?
    bar.finish()?
    Ok(())

Construction and updates

bar(total: u64) and spinner() return an owned Progress, initially at zero. A bar has a known total; a spinner counts work without predicting a total. Construction does not draw. Neither creates a worker or timer thread.

MethodContract
advance(count: u64 = 1u64)Add completed work; checked for overflow and the bar’s total
set_position(position: u64)Set an absolute count; it must not decrease or exceed the total
tick()Request a redraw without changing the count; useful for a spinner
finish()Draw the final count and a newline, ignoring the throttle; repeated calls do nothing
position() / total()Exact u64 count / Option[u64] total
is_finished()Whether finished or stopped by a display error

Updates and finish return Result[(), ProgressError]. Finishing early preserves the actual count: it does not claim remaining work was done. A zero-total bar is already full, but still needs finish() if it was drawn. Updates after finishing return Finished; invalid positions/overflow return InvalidPosition and leave the count unchanged. Output failure preserves the accepted count, stops further updates, and returns Output(std.fmt.FormatError) with its original cause. Counts never wrap or saturate: an advance beyond u64.MAX is InvalidPosition, even for a spinner. This check happens before changing the count or drawing; a finished progress object still returns Finished first.

Display and metrics

Output goes only to stderr, and only if stderr is a terminal both at construction and before each draw. Redirected runs emit nothing, including on finish; stdout stays available for application results. Closed/undetectable streams count as nonterminals. The first update draws immediately. Subsequent updates draw at most every 100 ms, using a monotonic clock; skipped updates still count completed work. The spinner advances its frame on actual redraws, not every update call. If a later query finds redirected stderr, that progress display stays hidden.

The default line uses a ten-cell ASCII bar or a four-frame spinner, counts, average whole items/second and estimated whole seconds remaining. Counts and metrics use truncated decimal suffixes k, M, G, T, P, E to keep lines short, including at u64.MAX. The exact counts remain available through getters. The line is under 80 columns. Interactive rendering assumes an ANSI-compatible terminal at least 80 columns wide: carriage return and erase-line replace the previous frame. Cursor visibility and terminal modes are never changed.

rate(now: &Instant) -> Option[f64] is the average count per second since construction. eta(now: &Instant) -> Option[f64] is remaining seconds at that average. Zero elapsed time yields no rate; an unknown total or zero rate yields no ETA, displayed as --. Completed known totals have ETA zero. Estimates use floating-point arithmetic; displayed values truncate and saturate to u64. Queries use the supplied time even after finishing; they do not freeze metrics.

For tests or an application-owned clock, bar_at(total, now), spinner_at(now), set_position_at(position, now) and finish_at(now) take a borrowed Instant. Use one timeline consistently. Backwards elapsed time saturates at zero; backwards redraw timestamps cannot bypass the throttle. render[W](now, output: &mut Formatter[W]) writes the plain line without newline or cursor controls and leaves redraw state unchanged. A string formatter allows snapshot testing; formatter errors propagate unchanged.

Costs and limits

Progress state is fixed-size. Normal drawing uses the bounded reporting formatter, without constructing temporary strings. Hidden and throttled updates skip formatting. Stderr writes may fail or block; a frame consists of multiple small writes and is not atomic with concurrent output. Keep one progress owner on one thread, and finish it before writing other diagnostics to stderr. Dropping it does not write: explicitly finish before normal early returns if a clean line is needed. An output failure may leave a partial line.

This initial API has no automatic ticking, multiple-bar coordination, templates, labels, colors, terminal resizing or iterator adapters. It follows the manually updated, terminal-only stderr model of indicatif, without embedding it. See Show progress for a small example.

Acknowledgements

  • Design follows indicatif 0.18.6; no indicatif code is used.