//! A simple progress bar //! //! A single thread non-optimized progress bar use std::io::{self, Write}; /// A Simple ASCII Progress Bar pub(crate) struct ProgressReport { curr: f32, text: String, hidden: bool, len: u64, pos: u64, msg: String, } impl ProgressReport { pub(crate) fn new(len: u64) -> ProgressReport { ProgressReport { curr: 0.0, text: String::new(), hidden: false, len, pos: 0, msg: String::new(), } } pub(crate) fn hidden() -> ProgressReport { ProgressReport { curr: 0.0, text: String::new(), hidden: true, len: 0, pos: 0, msg: String::new(), } } pub(crate) fn set_message(&mut self, msg: &str) { self.msg = msg.to_string(); self.tick(); } pub(crate) fn println>(&mut self, msg: I) { self.clear(); println!("{}", msg.into()); self.tick(); } pub(crate) fn inc(&mut self, delta: u64) { self.pos += delta; if self.len == 0 { self.set_value(0.0) } else { self.set_value((self.pos as f32) / (self.len as f32)) } self.tick(); } pub(crate) fn finish_and_clear(&mut self) { self.clear(); } pub(crate) fn tick(&mut self) { if self.hidden { return; } let percent = (self.curr * 100.0) as u32; let text = format!("{}/{} {:3>}% {}", self.pos, self.len, percent, self.msg); self.update_text(&text); } fn update_text(&mut self, text: &str) { // Get length of common portion let mut common_prefix_length = 0; let common_length = usize::min(self.text.len(), text.len()); while common_prefix_length < common_length && text.chars().nth(common_prefix_length).unwrap() == self.text.chars().nth(common_prefix_length).unwrap() { common_prefix_length += 1; } // Backtrack to the first differing character let mut output = String::new(); output += &'\x08'.to_string().repeat(self.text.len() - common_prefix_length); // Output new suffix output += &text[common_prefix_length..text.len()]; // If the new text is shorter than the old one: delete overlapping characters if let Some(overlap_count) = self.text.len().checked_sub(text.len()) { if overlap_count > 0 { output += &" ".repeat(overlap_count); output += &"\x08".repeat(overlap_count); } } let _ = io::stdout().write(output.as_bytes()); let _ = io::stdout().flush(); self.text = text.to_string(); } fn set_value(&mut self, value: f32) { self.curr = f32::max(0.0, f32::min(1.0, value)); } fn clear(&mut self) { if self.hidden { return; } // Fill all last text to space and return the cursor let spaces = " ".repeat(self.text.len()); let backspaces = "\x08".repeat(self.text.len()); print!("{}{}{}", backspaces, spaces, backspaces); let _ = io::stdout().flush(); self.text = String::new(); } }