progress.rs
raw
use std::fmt::Display;
use std::io::{self, Write};
/// Says what is happening, for work slow enough that silence looks like a hang.
///
/// Everything goes to stderr, leaving stdout to the report a caller might pipe
/// somewhere.
///
/// The interesting part is [`start`] and [`finish`]: the name of a thing is
/// written, and flushed, *before* the work begins, and its outcome lands on the
/// same line afterwards. So a run that is still going says which item it is
/// stuck on rather than merely that it has not finished — and a boot log read
/// the next morning ends on the name of whatever was in progress when the
/// machine gave up.
///
/// [`start`]: Reporter::start
/// [`finish`]: Reporter::finish
pub struct Reporter {
enabled: bool,
}
impl Reporter {
pub fn to_stderr() -> Self {
Self { enabled: true }
}
/// A reporter that says nothing.
///
/// Only tests want this. Every command has a user in front of it, or a log
/// behind it, and both are better off being told what is going on.
#[cfg(test)]
pub fn silent() -> Self {
Self { enabled: false }
}
/// A heading for a stretch of work.
pub fn phase(&self, message: impl Display) {
if self.enabled {
let _ = writeln!(io::stderr(), "{message}");
}
}
/// A whole line at once.
///
/// One `writeln!` holds the lock for its whole call, so this stays legible
/// when several threads report finishing at once — unlike [`start`], which
/// leaves a line open and must not be interleaved.
///
/// [`start`]: Reporter::start
pub fn line(&self, message: impl Display) {
if self.enabled {
let _ = writeln!(io::stderr(), " {message}");
}
}
/// Name the thing about to be worked on, leaving the line open.
///
/// Flushed, because the whole point is to appear before the waiting starts.
/// Only one thread may have a line open at a time.
pub fn start(&self, what: impl Display) {
if self.enabled {
let mut stderr = io::stderr();
let _ = write!(stderr, " {what} ... ");
let _ = stderr.flush();
}
}
/// Close the line opened by [`start`].
///
/// [`start`]: Reporter::start
pub fn finish(&self, outcome: impl Display) {
if self.enabled {
let _ = writeln!(io::stderr(), "{outcome}");
}
}
}
/// Pick a word for a count, so reports do not say "1 directories".
pub fn plural(count: usize, one: &'static str, many: &'static str) -> &'static str {
if count == 1 { one } else { many }
}