#[cfg(feature = "ui")]
use std::io::IsTerminal;
#[cfg(feature = "ui")]
fn is_interactive() -> bool {
if !std::io::stderr().is_terminal() {
return false;
}
if std::env::var("NO_COLOR").is_ok() {
return false;
}
if std::env::var("TOKMD_NO_PROGRESS").is_ok() {
return false;
}
true
}
#[cfg(feature = "ui")]
mod ui_impl {
use super::is_interactive;
use indicatif::{ProgressBar, ProgressStyle};
use std::time::{Duration, Instant};
pub struct Progress {
bar: Option<ProgressBar>,
}
impl Progress {
pub fn new(enabled: bool) -> Self {
let should_show = enabled && is_interactive();
let bar = if should_show {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::with_template("{spinner:.cyan} {msg}")
.expect("progress template is static and must be valid")
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏", " "]),
);
pb.enable_steady_tick(Duration::from_millis(80));
Some(pb)
} else {
None
};
Self { bar }
}
pub fn set_message(&self, msg: impl Into<String>) {
if let Some(bar) = &self.bar {
bar.set_message(msg.into());
}
}
pub fn finish_and_clear(&self) {
if let Some(bar) = &self.bar {
bar.finish_and_clear();
}
}
}
impl Drop for Progress {
fn drop(&mut self) {
if let Some(bar) = &self.bar {
bar.finish_and_clear();
}
}
}
#[allow(dead_code)]
pub struct ProgressBarWithEta {
bar: Option<indicatif::ProgressBar>,
start_time: Option<Instant>,
}
#[allow(dead_code)]
impl ProgressBarWithEta {
pub fn new(enabled: bool, total: u64, message: &str) -> Self {
let should_show = enabled && is_interactive();
let (bar, start_time) = if should_show {
let pb = indicatif::ProgressBar::new(total);
pb.set_style(
ProgressStyle::with_template(
"{spinner:.cyan} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta}) {msg}",
)
.expect("progress template is static and must be valid"),
);
pb.set_message(message.to_string());
pb.enable_steady_tick(Duration::from_millis(100));
(Some(pb), Some(Instant::now()))
} else {
(None, None)
};
Self { bar, start_time }
}
pub fn inc(&self) {
if let Some(bar) = &self.bar {
bar.inc(1);
}
}
pub fn inc_by(&self, delta: u64) {
if let Some(bar) = &self.bar {
bar.inc(delta);
}
}
pub fn set_position(&self, pos: u64) {
if let Some(bar) = &self.bar {
bar.set_position(pos);
}
}
pub fn set_message(&self, msg: &str) {
if let Some(bar) = &self.bar {
bar.set_message(msg.to_string());
}
}
pub fn set_length(&self, len: u64) {
if let Some(bar) = &self.bar {
bar.set_length(len);
}
}
pub fn finish_with_message(&self, msg: &str) {
if let Some(bar) = &self.bar {
bar.finish_with_message(msg.to_string());
}
}
pub fn finish_and_clear(&self) {
if let Some(bar) = &self.bar {
bar.finish_and_clear();
}
}
#[allow(dead_code)]
pub fn elapsed(&self) -> Option<Duration> {
self.start_time.map(|t| t.elapsed())
}
}
impl Drop for ProgressBarWithEta {
fn drop(&mut self) {
if let Some(bar) = &self.bar {
bar.finish_and_clear();
}
}
}
}
#[cfg(not(feature = "ui"))]
mod ui_impl {
pub struct Progress;
impl Progress {
pub fn new(_enabled: bool) -> Self {
Self
}
pub fn set_message(&self, _msg: impl Into<String>) {}
pub fn finish_and_clear(&self) {}
}
#[allow(dead_code)]
pub struct ProgressBarWithEta;
#[allow(dead_code)]
impl ProgressBarWithEta {
pub fn new(_enabled: bool, _total: u64, _message: &str) -> Self {
Self
}
pub fn inc(&self) {}
pub fn inc_by(&self, _delta: u64) {}
pub fn set_position(&self, _pos: u64) {}
pub fn set_message(&self, _msg: &str) {}
pub fn set_length(&self, _len: u64) {}
pub fn finish_with_message(&self, _msg: &str) {}
pub fn finish_and_clear(&self) {}
}
}
pub use ui_impl::{Progress, ProgressBarWithEta};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn progress_methods_do_not_panic_when_disabled() {
let progress = Progress::new(false);
progress.set_message("test");
progress.finish_and_clear();
}
#[test]
fn progress_bar_methods_do_not_panic_when_disabled() {
let progress = ProgressBarWithEta::new(false, 10, "scan");
progress.inc();
progress.inc_by(2);
progress.set_position(3);
progress.set_message("updated");
progress.set_length(20);
progress.finish_with_message("done");
progress.finish_and_clear();
}
}