use std::borrow::Cow;
use std::time::Duration;
use indicatif::{HumanBytes, ProgressBar, ProgressStyle};
const TICK: Duration = Duration::from_millis(100);
fn is_terminal() -> bool {
use std::io::IsTerminal;
std::io::stdout().is_terminal()
}
pub struct Step {
bar: ProgressBar,
quiet: bool,
pending: Option<Cow<'static, str>>,
}
impl Step {
pub fn start(message: impl Into<Cow<'static, str>>) -> Self {
let message = message.into();
if !is_terminal() {
return Self {
bar: ProgressBar::hidden(),
quiet: true,
pending: Some(message),
};
}
let bar = ProgressBar::new_spinner();
bar.set_style(
ProgressStyle::with_template("{spinner:.cyan} {msg}")
.expect("valid template")
.tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏✓"),
);
bar.set_message(message);
bar.enable_steady_tick(TICK);
Self {
bar,
quiet: false,
pending: None,
}
}
pub fn update(&self, message: impl Into<Cow<'static, str>>) {
if !self.quiet {
self.bar.set_message(message);
}
}
pub fn done(self, message: impl Into<Cow<'static, str>>) {
let message = message.into();
if self.quiet {
println!("{message}");
} else {
self.bar.finish_with_message(message);
}
}
pub fn clear(self) {
if self.quiet {
if let Some(message) = &self.pending {
println!("{message}");
}
} else {
self.bar.finish_and_clear();
}
}
}
pub struct Transfer {
bar: ProgressBar,
quiet: bool,
}
impl Transfer {
pub fn start(files: u64, bytes: u64) -> Self {
let plural = if files == 1 { "" } else { "s" };
if !is_terminal() {
println!("Uploading {files} file{plural} ({}) ...", human_bytes(bytes));
return Self {
bar: ProgressBar::hidden(),
quiet: true,
};
}
let bar = ProgressBar::new(bytes);
bar.set_style(
ProgressStyle::with_template("{bar:28.cyan/blue} {bytes}/{total_bytes} · {bytes_per_sec} · eta {eta} {wide_msg}")
.expect("valid template")
.progress_chars("=> "),
);
bar.enable_steady_tick(TICK);
Self { bar, quiet: false }
}
pub fn file(&self, name: &str) {
if !self.quiet {
self.bar.set_message(name.to_string());
}
}
pub fn advance(&self, bytes: u64) {
if !self.quiet {
self.bar.inc(bytes);
}
}
pub fn finish(self) {
if !self.quiet {
self.bar.finish_and_clear();
}
}
}
pub fn human_bytes(bytes: u64) -> String {
HumanBytes(bytes).to_string()
}
#[cfg(test)]
mod tests {
use super::human_bytes;
#[test]
fn formats_byte_counts_like_the_bar() {
assert_eq!(human_bytes(0), "0 B");
assert_eq!(human_bytes(93), "93 B");
assert!(human_bytes(831_488).ends_with("KiB"), "got {}", human_bytes(831_488));
assert!(human_bytes(6_400_000).ends_with("MiB"), "got {}", human_bytes(6_400_000));
assert!(human_bytes(3_221_225_472).ends_with("GiB"), "got {}", human_bytes(3_221_225_472));
}
}