├── .gitignore ├── gif ├── rec_v1.gif ├── rec_v2.gif └── rec_v3.gif ├── examples ├── max_refresh_rate.rs ├── simple.rs ├── npm_bar.rs ├── multi_bg.rs └── multi.rs ├── src ├── tty │ ├── wasi.rs │ ├── mod.rs │ ├── windows.rs │ └── unix.rs ├── lib.rs ├── multi.rs └── pb.rs ├── Cargo.toml ├── LICENSE.md ├── .github └── workflows │ └── ci.yml ├── README.md └── tests └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | draft* 4 | -------------------------------------------------------------------------------- /gif/rec_v1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a8m/pb/HEAD/gif/rec_v1.gif -------------------------------------------------------------------------------- /gif/rec_v2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a8m/pb/HEAD/gif/rec_v2.gif -------------------------------------------------------------------------------- /gif/rec_v3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a8m/pb/HEAD/gif/rec_v3.gif -------------------------------------------------------------------------------- /examples/max_refresh_rate.rs: -------------------------------------------------------------------------------- 1 | use pbr::ProgressBar; 2 | use std::thread; 3 | use std::time::Duration; 4 | 5 | fn main() { 6 | let mut pb = ProgressBar::new(10000); 7 | pb.set_max_refresh_rate(Some(Duration::from_secs(1))); 8 | 9 | for _ in 0..10000 { 10 | pb.inc(); 11 | thread::sleep(Duration::from_millis(1)); 12 | } 13 | 14 | pb.finish_print(""); 15 | } 16 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use pbr::ProgressBar; 2 | use rand::prelude::*; 3 | use std::thread; 4 | use std::time::Duration; 5 | 6 | fn main() { 7 | let count = 500; 8 | let mut pb = ProgressBar::new(count); 9 | pb.format("╢▌▌░╟"); 10 | for _ in 0..count { 11 | pb.inc(); 12 | let n = thread_rng().gen_range(0..100); 13 | thread::sleep(Duration::from_millis(n)); 14 | } 15 | pb.finish_println("done!"); 16 | } 17 | -------------------------------------------------------------------------------- /src/tty/wasi.rs: -------------------------------------------------------------------------------- 1 | use super::{Height, Width}; 2 | 3 | /// For WASI so far it will return none 4 | /// 5 | /// For background https://github.com/WebAssembly/WASI/issues/42 6 | pub fn terminal_size() -> Option<(Width, Height)> { 7 | return None; 8 | } 9 | 10 | /// This is inherited from unix and will work only when wasi executed on unix. 11 | /// 12 | /// For background https://github.com/WebAssembly/WASI/issues/42 13 | pub fn move_cursor_up(n: usize) -> String { 14 | format!("\x1B[{}A", n) 15 | } 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pbr" 3 | version = "1.1.1" 4 | authors = ["Ariel Mashraki ", "Steven Fackler "] 5 | edition = "2018" 6 | description = "Console progress bar for Rust" 7 | documentation = "https://a8m.github.io/pb/doc/pbr/index.html" 8 | repository = "https://github.com/a8m/pb" 9 | exclude = ["gif/"] 10 | keywords = ["cli", "progress", "terminal", "pb"] 11 | license = "MIT" 12 | 13 | [dependencies] 14 | libc = "0.2" 15 | crossbeam-channel = "0.5" 16 | 17 | [target.'cfg(target_os = "windows")'.dependencies.winapi] 18 | version = "0.3" 19 | features = ["wincon", "processenv", "winbase"] 20 | 21 | [dev-dependencies] 22 | rand = "0.8" 23 | -------------------------------------------------------------------------------- /src/tty/mod.rs: -------------------------------------------------------------------------------- 1 | //! Most of the code in for the `terminal_size()` function taken from: 2 | //! https://github.com/eminence/terminal-size 3 | //! 4 | //! A simple utility for getting the size of a terminal, and moving `n` lines up. 5 | //! 6 | //! Supports both Linux and Windows, but help is needed to test other platforms 7 | //! 8 | //! 9 | 10 | #[derive(Debug)] 11 | pub struct Width(pub u16); 12 | #[derive(Debug)] 13 | pub struct Height(pub u16); 14 | 15 | #[cfg(unix)] 16 | mod unix; 17 | #[cfg(unix)] 18 | pub use self::unix::*; 19 | 20 | #[cfg(target_os = "wasi")] 21 | mod wasi; 22 | #[cfg(target_os = "wasi")] 23 | pub use self::wasi::*; 24 | 25 | #[cfg(windows)] 26 | mod windows; 27 | #[cfg(windows)] 28 | pub use self::windows::*; 29 | -------------------------------------------------------------------------------- /examples/npm_bar.rs: -------------------------------------------------------------------------------- 1 | use pbr::ProgressBar; 2 | use std::thread; 3 | use std::time::Duration; 4 | 5 | fn main() { 6 | let count = 30; 7 | let mut pb = ProgressBar::new(count * 10); 8 | pb.tick_format("\\|/-"); 9 | pb.format("|#--|"); 10 | pb.show_tick = true; 11 | pb.show_speed = false; 12 | pb.show_percent = false; 13 | pb.show_counter = false; 14 | pb.show_time_left = false; 15 | pb.inc(); 16 | for _ in 0..count { 17 | for _ in 0..10 { 18 | pb.message("normalize -> thing "); 19 | thread::sleep(Duration::from_millis(80)); 20 | pb.tick(); 21 | } 22 | for _ in 0..10 { 23 | pb.message("fuzz -> tree "); 24 | thread::sleep(Duration::from_millis(80)); 25 | pb.inc(); 26 | } 27 | } 28 | pb.finish_println("done!"); 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2015-2016 Ariel Mashraki and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | 6 | jobs: 7 | test: 8 | name: Test 9 | strategy: 10 | matrix: 11 | os: ["ubuntu-latest", "windows-latest", "macos-latest"] 12 | rust: ["stable", "1.58"] 13 | runs-on: ${{ matrix.os }} 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v3 17 | - name: Install Rust 18 | uses: dtolnay/rust-toolchain@master 19 | with: 20 | toolchain: ${{ matrix.rust }} 21 | - name: Build 22 | run: cargo build 23 | - name: Build examples 24 | run: cargo build --examples 25 | - name: Install faketty 26 | if: matrix.rust == 'stable' && matrix.os != 'windows-latest' 27 | run: | 28 | cargo install faketty 29 | echo "fake_tty=faketty" >> $GITHUB_ENV 30 | - name: Test 31 | if: env.fake_tty || matrix.os == 'windows-latest' 32 | run: ${{ env.fake_tty }} cargo test --workspace 33 | 34 | fmt: 35 | name: rustfmt 36 | runs-on: ubuntu-latest 37 | steps: 38 | - name: Checkout source 39 | uses: actions/checkout@v3 40 | - name: Install Rust 41 | uses: dtolnay/rust-toolchain@stable 42 | with: 43 | components: rustfmt 44 | - name: Run rustfmt check 45 | run: cargo fmt --check 46 | -------------------------------------------------------------------------------- /examples/multi_bg.rs: -------------------------------------------------------------------------------- 1 | use pbr::MultiBar; 2 | use std::{ 3 | sync::{ 4 | atomic::{AtomicBool, Ordering}, 5 | Arc, 6 | }, 7 | thread, 8 | time::Duration, 9 | }; 10 | 11 | fn main() { 12 | let complete = Arc::new(AtomicBool::new(false)); 13 | let progress = Arc::new(MultiBar::new()); 14 | 15 | thread::spawn({ 16 | let complete = Arc::clone(&complete); 17 | let progress = Arc::clone(&progress); 18 | move || { 19 | for task in 1..=10 { 20 | thread::spawn({ 21 | let progress = Arc::clone(&progress); 22 | move || { 23 | let mut bar = progress.create_bar(100); 24 | bar.message(&format!("Task {}: ", task)); 25 | 26 | for _ in 0..100 { 27 | thread::sleep(Duration::from_millis(50)); 28 | bar.inc(); 29 | } 30 | 31 | bar.finish_print(&format!("Task {} Complete", task)); 32 | } 33 | }); 34 | 35 | thread::sleep(Duration::from_millis(1000)); 36 | } 37 | 38 | complete.store(true, Ordering::SeqCst); 39 | } 40 | }); 41 | 42 | while !complete.load(Ordering::SeqCst) { 43 | let _ = progress.listen(); 44 | thread::sleep(Duration::from_millis(1000)); 45 | } 46 | 47 | let _ = progress.listen(); 48 | } 49 | -------------------------------------------------------------------------------- /src/tty/windows.rs: -------------------------------------------------------------------------------- 1 | use super::{Height, Width}; 2 | 3 | /// Returns the size of the terminal, if available. 4 | /// 5 | /// Note that this returns the size of the actual command window, and 6 | /// not the overall size of the command window buffer 7 | pub fn terminal_size() -> Option<(Width, Height)> { 8 | if let Some((_, csbi)) = get_csbi() { 9 | let w: Width = Width((csbi.srWindow.Right - csbi.srWindow.Left) as u16); 10 | let h: Height = Height((csbi.srWindow.Bottom - csbi.srWindow.Top) as u16); 11 | Some((w, h)) 12 | } else { 13 | None 14 | } 15 | } 16 | 17 | /// move the cursor `n` lines up; return an empty string, just to 18 | /// be aligned with the unix version. 19 | pub fn move_cursor_up(n: usize) -> String { 20 | use winapi::um::wincon::{SetConsoleCursorPosition, COORD}; 21 | if let Some((hand, csbi)) = get_csbi() { 22 | unsafe { 23 | SetConsoleCursorPosition( 24 | hand, 25 | COORD { 26 | X: 0, 27 | Y: csbi.dwCursorPosition.Y - n as i16, 28 | }, 29 | ); 30 | } 31 | } 32 | "".to_string() 33 | } 34 | 35 | fn get_csbi() -> Option<( 36 | winapi::shared::ntdef::HANDLE, 37 | winapi::um::wincon::CONSOLE_SCREEN_BUFFER_INFO, 38 | )> { 39 | use winapi::shared::ntdef::HANDLE; 40 | use winapi::um::processenv::GetStdHandle; 41 | use winapi::um::winbase::STD_OUTPUT_HANDLE; 42 | use winapi::um::wincon::{ 43 | GetConsoleScreenBufferInfo, CONSOLE_SCREEN_BUFFER_INFO, COORD, SMALL_RECT, 44 | }; 45 | 46 | let hand: HANDLE = unsafe { GetStdHandle(STD_OUTPUT_HANDLE) }; 47 | 48 | let zc = COORD { X: 0, Y: 0 }; 49 | let mut csbi = CONSOLE_SCREEN_BUFFER_INFO { 50 | dwSize: zc.clone(), 51 | dwCursorPosition: zc.clone(), 52 | wAttributes: 0, 53 | srWindow: SMALL_RECT { 54 | Left: 0, 55 | Top: 0, 56 | Right: 0, 57 | Bottom: 0, 58 | }, 59 | dwMaximumWindowSize: zc, 60 | }; 61 | match unsafe { GetConsoleScreenBufferInfo(hand, &mut csbi) } { 62 | 0 => None, 63 | _ => Some((hand, csbi)), 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/multi.rs: -------------------------------------------------------------------------------- 1 | use pbr::MultiBar; 2 | use rand::prelude::*; 3 | use std::thread; 4 | use std::time::Duration; 5 | 6 | fn main() { 7 | let mb = MultiBar::new(); 8 | mb.println("Your Application Header:"); 9 | mb.println(""); 10 | 11 | for i in 1..6 { 12 | let count = 100 * i; 13 | let mut pb = mb.create_bar(count); 14 | pb.tick_format("▏▎▍▌▋▊▉██▉▊▋▌▍▎▏"); 15 | pb.show_message = true; 16 | thread::spawn(move || { 17 | for _ in 0..count / 20 { 18 | for _ in 0..20 { 19 | pb.message("Waiting : "); 20 | thread::sleep(Duration::from_millis(50)); 21 | pb.tick(); 22 | } 23 | for _ in 0..20 { 24 | let n = thread_rng().gen_range(0..100); 25 | pb.message("Connected: "); 26 | thread::sleep(Duration::from_millis(n)); 27 | pb.inc(); 28 | } 29 | } 30 | for _ in 0..20 { 31 | pb.message("Cleaning :"); 32 | thread::sleep(Duration::from_millis(100)); 33 | pb.tick(); 34 | } 35 | pb.finish_print(&format!("{}: Pull complete", rand_string())); 36 | }); 37 | } 38 | 39 | mb.println(""); 40 | mb.println("Text lines separate between two sections: "); 41 | mb.println(""); 42 | 43 | for i in 1..4 { 44 | let count = 100 * i; 45 | let mut pb = mb.create_bar(count); 46 | thread::spawn(move || { 47 | for _ in 0..count { 48 | pb.inc(); 49 | let n = thread_rng().gen_range(0..100); 50 | thread::sleep(Duration::from_millis(n)); 51 | } 52 | pb.finish(); 53 | }); 54 | } 55 | 56 | mb.listen(); 57 | 58 | println!("\nall bars done!\n"); 59 | } 60 | 61 | fn rand_string() -> String { 62 | let mut v = Vec::new(); 63 | while v.len() < 12 { 64 | let b = random::(); 65 | // [0-9a-f] 66 | if b > 47 && b < 58 || b > 96 && b < 103 { 67 | v.push(b); 68 | } 69 | } 70 | std::str::from_utf8(&v).unwrap().to_string() 71 | } 72 | -------------------------------------------------------------------------------- /src/tty/unix.rs: -------------------------------------------------------------------------------- 1 | use super::{Height, Width}; 2 | 3 | // We need to convert from c_int to c_ulong at least on DragonFly and FreeBSD. 4 | #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] 5 | fn ioctl_conv>(v: T) -> libc::c_ulong { 6 | v.into() 7 | } 8 | 9 | // No-op on any other operating system. 10 | #[cfg(not(any(target_os = "dragonfly", target_os = "freebsd")))] 11 | fn ioctl_conv(v: T) -> T { 12 | v 13 | } 14 | 15 | /// Returns the size of the terminal, if available. 16 | /// 17 | /// If STDOUT is not a tty, returns `None` 18 | pub fn terminal_size() -> Option<(Width, Height)> { 19 | use libc::{ioctl, isatty, winsize, STDOUT_FILENO, TIOCGWINSZ}; 20 | let is_tty: bool = unsafe { isatty(STDOUT_FILENO) == 1 }; 21 | 22 | if !is_tty { 23 | return None; 24 | } 25 | 26 | let (rows, cols) = unsafe { 27 | let mut winsize = winsize { 28 | ws_row: 0, 29 | ws_col: 0, 30 | ws_xpixel: 0, 31 | ws_ypixel: 0, 32 | }; 33 | ioctl(STDOUT_FILENO, ioctl_conv(TIOCGWINSZ), &mut winsize); 34 | let rows = if winsize.ws_row > 0 { 35 | winsize.ws_row 36 | } else { 37 | 0 38 | }; 39 | let cols = if winsize.ws_col > 0 { 40 | winsize.ws_col 41 | } else { 42 | 0 43 | }; 44 | (rows as u16, cols as u16) 45 | }; 46 | 47 | if rows > 0 && cols > 0 { 48 | Some((Width(cols), Height(rows))) 49 | } else { 50 | None 51 | } 52 | } 53 | 54 | /// Return string that move the cursor `n` lines up. 55 | pub fn move_cursor_up(n: usize) -> String { 56 | format!("\x1B[{}A", n) 57 | } 58 | 59 | #[cfg(not(target_os = "redox"))] 60 | #[test] 61 | /// Compare with the output of `stty size` 62 | fn compare_with_stty() { 63 | use std::process::Command; 64 | use std::process::Stdio; 65 | let mut args = vec!["-F", "/dev/stderr", "size"]; 66 | if cfg!(target_os = "macos") { 67 | args[0] = "-f" 68 | } 69 | let output = Command::new("stty") 70 | .args(&args) 71 | .stderr(Stdio::inherit()) 72 | .output() 73 | .unwrap(); 74 | let stdout = String::from_utf8(output.stdout).unwrap(); 75 | assert!(output.status.success()); 76 | 77 | // stdout is "rows cols" 78 | let mut data = stdout.split_whitespace(); 79 | let rows = u16::from_str_radix(data.next().unwrap(), 10).unwrap(); 80 | let cols = u16::from_str_radix(data.next().unwrap(), 10).unwrap(); 81 | println!("{}", stdout); 82 | println!("{} {}", rows, cols); 83 | 84 | if let Some((Width(w), Height(h))) = terminal_size() { 85 | assert_eq!(rows, h); 86 | assert_eq!(cols, w); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terminal progress bar for Rust 2 | 3 | [![Latest version](https://img.shields.io/crates/v/pbr.svg)](https://crates.io/crates/pbr) 4 | [![License](https://img.shields.io/crates/l/pbr.svg)](https://github.com/a8m/pb/blob/master/LICENSE.md) 5 | [![Docs](https://img.shields.io/badge/docs-reference-blue.svg)](https://a8m.github.io/pb/doc/pbr/index.html) 6 | [![Build Status](https://travis-ci.org/a8m/pb.svg?branch=master)](https://travis-ci.org/a8m/pb) 7 | [![Gitter](https://badges.gitter.im/a8m/pb.svg)](https://gitter.im/a8m/pb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 8 | 9 | Console progress bar for Rust Inspired from [pb](http://github.com/cheggaaa/pb), support and 10 | tested on MacOS, Linux and Windows 11 | 12 | ![Screenshot](https://github.com/a8m/pb/blob/master/gif/rec_v3.gif) 13 | 14 | [Documentation](https://a8m.github.io/pb/doc/pbr/index.html) 15 | 16 | ### Examples 17 | 1. simple example 18 | 19 | ```rust 20 | use pbr::ProgressBar; 21 | use std::thread; 22 | 23 | fn main() { 24 | let count = 1000; 25 | let mut pb = ProgressBar::new(count); 26 | pb.format("╢▌▌░╟"); 27 | for _ in 0..count { 28 | pb.inc(); 29 | thread::sleep_ms(200); 30 | } 31 | pb.finish_print("done"); 32 | } 33 | ``` 34 | 35 | 2. MultiBar example. see full example [here](https://github.com/a8m/pb/blob/master/examples/multi.rs) 36 | ```rust 37 | use std::thread; 38 | use pbr::MultiBar; 39 | use std::time::Duration; 40 | 41 | fn main() { 42 | let mut mb = MultiBar::new(); 43 | let count = 100; 44 | mb.println("Application header:"); 45 | 46 | let mut p1 = mb.create_bar(count); 47 | let _ = thread::spawn(move || { 48 | for _ in 0..count { 49 | p1.inc(); 50 | thread::sleep(Duration::from_millis(100)); 51 | } 52 | // notify the multibar that this bar finished. 53 | p1.finish(); 54 | }); 55 | 56 | mb.println("add a separator between the two bars"); 57 | 58 | let mut p2 = mb.create_bar(count * 2); 59 | let _ = thread::spawn(move || { 60 | for _ in 0..count * 2 { 61 | p2.inc(); 62 | thread::sleep(Duration::from_millis(100)); 63 | } 64 | // notify the multibar that this bar finished. 65 | p2.finish(); 66 | }); 67 | 68 | // start listen to all bars changes. 69 | // this is a blocking operation, until all bars will finish. 70 | // to ignore blocking, you can run it in a different thread. 71 | mb.listen(); 72 | } 73 | ``` 74 | 75 | 3. Broadcast writing (simple file copying) 76 | 77 | ```rust 78 | #![feature(io)] 79 | use std::io::copy; 80 | use std::io::prelude::*; 81 | use std::fs::File; 82 | use pbr::{ProgressBar, Units}; 83 | 84 | fn main() { 85 | let mut file = File::open("/usr/share/dict/words").unwrap(); 86 | let n_bytes = file.metadata().unwrap().len() as usize; 87 | let mut pb = ProgressBar::new(n_bytes); 88 | pb.set_units(Units::Bytes); 89 | let mut handle = File::create("copy-words").unwrap().broadcast(&mut pb); 90 | copy(&mut file, &mut handle).unwrap(); 91 | pb.finish_print("done"); 92 | } 93 | ``` 94 | 95 | ### License 96 | MIT 97 | 98 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate pbr; 2 | 3 | use pbr::{PbIter, ProgressBar}; 4 | use std::thread; 5 | use std::time::Duration; 6 | 7 | #[test] 8 | fn simple_example() { 9 | let count = 5000; 10 | let mut pb = ProgressBar::new(count); 11 | pb.format("╢▌▌░╟"); 12 | for _ in 0..count { 13 | pb.inc(); 14 | thread::sleep(Duration::from_millis(5)); 15 | } 16 | pb.finish_println("done!"); 17 | } 18 | 19 | #[test] 20 | fn custom_width_example() { 21 | let count = 500; 22 | let mut pb = ProgressBar::new(count); 23 | pb.set_width(Some(80)); 24 | pb.format("╢▌▌░╟"); 25 | for _ in 0..count { 26 | pb.inc(); 27 | thread::sleep(Duration::from_millis(5)); 28 | } 29 | pb.finish_println("done!"); 30 | } 31 | 32 | #[test] 33 | fn simple_iter_example() { 34 | for _ in PbIter::new(0..2000) { 35 | thread::sleep(Duration::from_millis(1)); 36 | } 37 | } 38 | 39 | #[test] 40 | fn timeout_example() { 41 | let count = 10; 42 | let mut pb = ProgressBar::new(count * 20); 43 | pb.tick_format("▏▎▍▌▋▊▉██▉▊▋▌▍▎▏"); 44 | pb.show_message = true; 45 | pb.inc(); 46 | for _ in 0..count { 47 | for _ in 0..20 { 48 | pb.message("Waiting : "); 49 | thread::sleep(Duration::from_millis(50)); 50 | pb.tick(); 51 | } 52 | for _ in 0..20 { 53 | pb.message("Connected: "); 54 | thread::sleep(Duration::from_millis(50)); 55 | pb.inc(); 56 | } 57 | } 58 | for _ in 0..10 { 59 | pb.message("Cleaning :"); 60 | thread::sleep(Duration::from_millis(50)); 61 | pb.tick(); 62 | } 63 | pb.finish_println("done!"); 64 | } 65 | 66 | #[test] 67 | // see: issue #11 68 | fn tick_before_start() { 69 | let count = 100; 70 | let mut pb = ProgressBar::new(count); 71 | pb.tick_format("▏▎▍▌▋▊▉██▉▊▋▌▍▎▏"); 72 | pb.tick(); 73 | for _ in 0..count { 74 | pb.tick(); 75 | thread::sleep(Duration::from_millis(50)); 76 | } 77 | for _ in 0..count { 78 | pb.inc(); 79 | thread::sleep(Duration::from_millis(50)); 80 | } 81 | } 82 | 83 | #[test] 84 | fn npm_bar() { 85 | let count = 30; 86 | let mut pb = ProgressBar::new(count * 5); 87 | pb.tick_format("\\|/-"); 88 | pb.format("|#--|"); 89 | pb.show_tick = true; 90 | pb.show_speed = false; 91 | pb.show_percent = false; 92 | pb.show_counter = false; 93 | pb.show_time_left = false; 94 | pb.inc(); 95 | for _ in 0..count { 96 | for _ in 0..5 { 97 | pb.message("normalize -> thing "); 98 | thread::sleep(Duration::from_millis(80)); 99 | pb.tick(); 100 | } 101 | for _ in 0..5 { 102 | pb.message("fuzz -> tree "); 103 | thread::sleep(Duration::from_millis(80)); 104 | pb.inc(); 105 | } 106 | } 107 | pb.finish_println("done!"); 108 | } 109 | 110 | #[test] 111 | // see: issue 45# 112 | fn final_redraw_max_refresh_rate() { 113 | let count = 500; 114 | let mut pb = ProgressBar::new(count); 115 | pb.format("╢▌▌░╟"); 116 | pb.set_max_refresh_rate(Some(Duration::from_millis(100))); 117 | for _ in 0..count { 118 | pb.inc(); 119 | thread::sleep(Duration::from_millis(5)); 120 | } 121 | pb.finish_println("done!"); 122 | } 123 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Terminal progress bar for Rust 2 | //! 3 | //! Console progress bar for Rust Inspired from [pb](http://github.com/cheggaaa/pb), support and 4 | //! tested on MacOS, Linux and Windows 5 | //! 6 | //! ![Screenshot](https://raw.githubusercontent.com/a8m/pb/master/gif/rec_v3.gif) 7 | //! 8 | //! [Documentation](http://a8m.github.io/pb/doc/pbr/index.html) 9 | //! 10 | //! ### Examples 11 | //! 1. simple example 12 | //! 13 | //! ```ignore 14 | //! use pbr::ProgressBar; 15 | //! use std::thread; 16 | //! 17 | //! fn main() { 18 | //! let count = 1000; 19 | //! let mut pb = ProgressBar::new(count); 20 | //! pb.format("╢▌▌░╟"); 21 | //! for _ in 0..count { 22 | //! pb.inc(); 23 | //! thread::sleep_ms(200); 24 | //! } 25 | //! pb.finish_print("done"); 26 | //! } 27 | //! ``` 28 | //! 29 | //! 2. MultiBar example. see full example [here](https://github.com/a8m/pb/blob/master/examples/multi.rs) 30 | //! 31 | //! ```ignore 32 | //! use std::thread; 33 | //! use pbr::MultiBar; 34 | //! use std::time::Duration; 35 | //! 36 | //! fn main() { 37 | //! let mut mb = MultiBar::new(); 38 | //! let count = 100; 39 | //! mb.println("Application header:"); 40 | //! 41 | //! let mut p1 = mb.create_bar(count); 42 | //! let _ = thread::spawn(move || { 43 | //! for _ in 0..count { 44 | //! p1.inc(); 45 | //! thread::sleep(Duration::from_millis(100)); 46 | //! } 47 | //! // notify the multibar that this bar finished. 48 | //! p1.finish(); 49 | //! }); 50 | //! 51 | //! mb.println("add a separator between the two bars"); 52 | //! 53 | //! let mut p2 = mb.create_bar(count * 2); 54 | //! let _ = thread::spawn(move || { 55 | //! for _ in 0..count * 2 { 56 | //! p2.inc(); 57 | //! thread::sleep(Duration::from_millis(100)); 58 | //! } 59 | //! // notify the multibar that this bar finished. 60 | //! p2.finish(); 61 | //! }); 62 | //! 63 | //! // start listen to all bars changes. 64 | //! // this is a blocking operation, until all bars will finish. 65 | //! // to ignore blocking, you can run it in a different thread. 66 | //! mb.listen(); 67 | //! } 68 | //! ``` 69 | //! 70 | //! 3. Broadcast writing(simple file copying) 71 | //! 72 | //! ```ignore 73 | //! #![feature(io)] 74 | //! use std::io::copy; 75 | //! use std::io::prelude::*; 76 | //! use std::fs::File; 77 | //! use pbr::{ProgressBar, Units}; 78 | //! 79 | //! fn main() { 80 | //! let mut file = File::open("/usr/share/dict/words").unwrap(); 81 | //! let n_bytes = file.metadata().unwrap().len() as usize; 82 | //! let mut pb = ProgressBar::new(n_bytes); 83 | //! pb.set_units(Units::Bytes); 84 | //! let mut handle = File::create("copy-words").unwrap().broadcast(&mut pb); 85 | //! copy(&mut file, &mut handle).unwrap(); 86 | //! pb.finish_print("done"); 87 | //! } 88 | //! ``` 89 | 90 | // Macro for writing to the giving writer. 91 | // Used in both pb.rs and multi.rs modules. 92 | // 93 | // # Examples 94 | // 95 | // ``` 96 | // let w = io::stdout(); 97 | // printfl!(w, ""); 98 | // printfl!(w, "\r{}", out); 99 | // 100 | // ``` 101 | macro_rules! printfl { 102 | ($w:expr, $($tt:tt)*) => {{ 103 | $w.write_all(&format!($($tt)*).as_bytes()).ok().expect("write() fail"); 104 | $w.flush().ok().expect("flush() fail"); 105 | }} 106 | } 107 | 108 | mod multi; 109 | mod pb; 110 | mod tty; 111 | pub use multi::{MultiBar, Pipe}; 112 | pub use pb::{ProgressBar, Units}; 113 | use std::io::{stdout, Stdout, Write}; 114 | 115 | pub struct PbIter 116 | where 117 | I: Iterator, 118 | T: Write, 119 | { 120 | iter: I, 121 | progress_bar: ProgressBar, 122 | } 123 | 124 | impl PbIter 125 | where 126 | I: Iterator, 127 | { 128 | pub fn new(iter: I) -> Self { 129 | Self::on(stdout(), iter) 130 | } 131 | } 132 | 133 | impl PbIter 134 | where 135 | I: Iterator, 136 | T: Write, 137 | { 138 | pub fn on(handle: T, iter: I) -> Self { 139 | let size = iter.size_hint().0; 140 | PbIter { 141 | iter, 142 | progress_bar: ProgressBar::on(handle, size as u64), 143 | } 144 | } 145 | } 146 | 147 | impl Iterator for PbIter 148 | where 149 | I: Iterator, 150 | T: Write, 151 | { 152 | type Item = I::Item; 153 | 154 | fn next(&mut self) -> Option { 155 | match self.iter.next() { 156 | Some(i) => { 157 | self.progress_bar.inc(); 158 | Some(i) 159 | } 160 | None => None, 161 | } 162 | } 163 | 164 | fn size_hint(&self) -> (usize, Option) { 165 | self.iter.size_hint() 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/multi.rs: -------------------------------------------------------------------------------- 1 | use crate::tty::move_cursor_up; 2 | use crate::ProgressBar; 3 | use crossbeam_channel::{unbounded, Receiver, Sender}; 4 | use std::io::{Result, Stdout, Write}; 5 | use std::str::from_utf8; 6 | use std::sync::atomic::{AtomicUsize, Ordering}; 7 | use std::sync::Mutex; 8 | 9 | pub struct MultiBar { 10 | state: Mutex>, 11 | chan: (Sender, Receiver), 12 | nbars: AtomicUsize, 13 | } 14 | 15 | struct State { 16 | lines: Vec, 17 | nlines: usize, 18 | handle: T, 19 | } 20 | 21 | impl MultiBar { 22 | /// Create a new MultiBar with stdout as a writer. 23 | /// 24 | /// # Examples 25 | /// 26 | /// ```no_run 27 | /// use std::thread; 28 | /// use pbr::MultiBar; 29 | /// use std::time::Duration; 30 | /// 31 | /// let mut mb = MultiBar::new(); 32 | /// mb.println("Application header:"); 33 | /// 34 | /// # let count = 250; 35 | /// let mut p1 = mb.create_bar(count); 36 | /// let _ = thread::spawn(move || { 37 | /// for _ in 0..count { 38 | /// p1.inc(); 39 | /// thread::sleep(Duration::from_millis(100)); 40 | /// } 41 | /// // notify the multibar that this bar finished. 42 | /// p1.finish(); 43 | /// }); 44 | /// 45 | /// mb.println("add a separator between the two bars"); 46 | /// 47 | /// let mut p2 = mb.create_bar(count * 2); 48 | /// let _ = thread::spawn(move || { 49 | /// for _ in 0..count * 2 { 50 | /// p2.inc(); 51 | /// thread::sleep(Duration::from_millis(100)); 52 | /// } 53 | /// // notify the multibar that this bar finished. 54 | /// p2.finish(); 55 | /// }); 56 | /// 57 | /// // start listen to all bars changes. 58 | /// // this is a blocking operation, until all bars will finish. 59 | /// // to ignore blocking, you can run it in a different thread. 60 | /// mb.listen(); 61 | /// ``` 62 | pub fn new() -> MultiBar { 63 | MultiBar::on(::std::io::stdout()) 64 | } 65 | } 66 | 67 | impl MultiBar { 68 | /// Create a new MultiBar with an arbitrary writer. 69 | /// 70 | /// # Examples 71 | /// 72 | /// ```no_run 73 | /// use pbr::MultiBar; 74 | /// use std::io::stderr; 75 | /// 76 | /// let mut mb = MultiBar::on(stderr()); 77 | /// // ... 78 | /// // see full example in `MultiBar::new` 79 | /// // ... 80 | /// ``` 81 | pub fn on(handle: T) -> MultiBar { 82 | MultiBar { 83 | state: Mutex::new(State { 84 | lines: Vec::new(), 85 | handle, 86 | nlines: 0, 87 | }), 88 | chan: unbounded(), 89 | nbars: AtomicUsize::new(0), 90 | } 91 | } 92 | 93 | /// println used to add text lines between the bars. 94 | /// for example: you could add a header to your application, 95 | /// or text separators between bars. 96 | /// 97 | /// # Examples 98 | /// 99 | /// ```no_run 100 | /// use pbr::MultiBar; 101 | /// 102 | /// let mut mb = MultiBar::new(); 103 | /// mb.println("Application header:"); 104 | /// 105 | /// # let count = 250; 106 | /// let mut p1 = mb.create_bar(count); 107 | /// // ... 108 | /// 109 | /// mb.println("Text line between bar1 and bar2"); 110 | /// 111 | /// let mut p2 = mb.create_bar(count); 112 | /// // ... 113 | /// 114 | /// mb.println("Text line between bar2 and bar3"); 115 | /// 116 | /// // ... 117 | /// // ... 118 | /// mb.listen(); 119 | /// ``` 120 | pub fn println(&self, s: &str) { 121 | let mut state = self.state.lock().unwrap(); 122 | state.lines.push(s.to_owned()); 123 | state.nlines += 1; 124 | } 125 | 126 | /// create_bar creates new `ProgressBar` with `Pipe` as the writer. 127 | /// 128 | /// The ordering of the method calls is important. it means that in 129 | /// the first call, you get a progress bar in level 1, in the 2nd call, 130 | /// you get a progress bar in level 2, and so on. 131 | /// 132 | /// ProgressBar that finish its work, must call `finish()` (or `finish_print`) 133 | /// to notify the `MultiBar` about it. 134 | /// 135 | /// # Examples 136 | /// 137 | /// ```no_run 138 | /// use pbr::MultiBar; 139 | /// 140 | /// let mut mb = MultiBar::new(); 141 | /// # let (count1, count2, count3) = (250, 62500, 15625000); 142 | /// 143 | /// // progress bar in level 1 144 | /// let mut p1 = mb.create_bar(count1); 145 | /// // ... 146 | /// 147 | /// // progress bar in level 2 148 | /// let mut p2 = mb.create_bar(count2); 149 | /// // ... 150 | /// 151 | /// // progress bar in level 3 152 | /// let mut p3 = mb.create_bar(count3); 153 | /// 154 | /// // ... 155 | /// mb.listen(); 156 | /// ``` 157 | pub fn create_bar(&self, total: u64) -> ProgressBar { 158 | let mut state = self.state.lock().unwrap(); 159 | 160 | state.lines.push(String::new()); 161 | state.nlines += 1; 162 | 163 | self.nbars.fetch_add(1, Ordering::SeqCst); 164 | 165 | let mut p = ProgressBar::on( 166 | Pipe { 167 | level: state.nlines - 1, 168 | chan: self.chan.0.clone(), 169 | }, 170 | total, 171 | ); 172 | 173 | p.is_multibar = true; 174 | p.add(0); 175 | p 176 | } 177 | 178 | /// listen start listen to all bars changes. 179 | /// 180 | /// `ProgressBar` that finish its work, must call `finish()` (or `finish_print`) 181 | /// to notify the `MultiBar` about it. 182 | /// 183 | /// This is a blocking operation and blocks until all bars will 184 | /// finish. 185 | /// To ignore blocking, you can run it in a different thread. 186 | /// 187 | /// # Examples 188 | /// 189 | /// ```no_run 190 | /// use std::thread; 191 | /// use pbr::MultiBar; 192 | /// 193 | /// let mut mb = MultiBar::new(); 194 | /// 195 | /// // ... 196 | /// // create some bars here 197 | /// // ... 198 | /// 199 | /// thread::spawn(move || { 200 | /// mb.listen(); 201 | /// println!("all bars done!"); 202 | /// }); 203 | /// 204 | /// // ... 205 | /// ``` 206 | pub fn listen(&self) { 207 | let mut first = true; 208 | let mut out = String::new(); 209 | 210 | while self.nbars.load(Ordering::SeqCst) > 0 { 211 | // receive message 212 | let msg = self.chan.1.recv().unwrap(); 213 | if msg.done { 214 | self.nbars.fetch_sub(1, Ordering::SeqCst); 215 | continue; 216 | } 217 | 218 | out.clear(); 219 | let mut state = self.state.lock().unwrap(); 220 | state.lines[msg.level] = msg.string; 221 | 222 | // and draw 223 | if !first { 224 | out += &move_cursor_up(state.nlines); 225 | } else { 226 | first = false; 227 | } 228 | 229 | for l in state.lines.iter() { 230 | out.push_str(&format!("\r{}\n", l)); 231 | } 232 | 233 | printfl!(state.handle, "{}", out); 234 | } 235 | } 236 | } 237 | 238 | pub struct Pipe { 239 | level: usize, 240 | chan: Sender, 241 | } 242 | 243 | impl Write for Pipe { 244 | fn write(&mut self, buf: &[u8]) -> Result { 245 | let s = from_utf8(buf).unwrap().to_owned(); 246 | self.chan 247 | .send(WriteMsg { 248 | // finish method emit empty string 249 | done: s.is_empty(), 250 | level: self.level, 251 | string: s, 252 | }) 253 | .unwrap(); 254 | Ok(buf.len()) 255 | } 256 | 257 | fn flush(&mut self) -> Result<()> { 258 | Ok(()) 259 | } 260 | } 261 | 262 | // WriteMsg is the message format used to communicate 263 | // between MultiBar and its bars 264 | struct WriteMsg { 265 | done: bool, 266 | level: usize, 267 | string: String, 268 | } 269 | -------------------------------------------------------------------------------- /src/pb.rs: -------------------------------------------------------------------------------- 1 | use crate::tty::{terminal_size, Width}; 2 | use std::io::Stdout; 3 | use std::io::{self, Write}; 4 | use std::time::{Duration, Instant}; 5 | 6 | macro_rules! kb_fmt { 7 | ($n: ident) => {{ 8 | let kb = 1024f64; 9 | match $n { 10 | $n if $n >= kb.powf(4_f64) => format!("{:.*} TB", 2, $n / kb.powf(4_f64)), 11 | $n if $n >= kb.powf(3_f64) => format!("{:.*} GB", 2, $n / kb.powf(3_f64)), 12 | $n if $n >= kb.powf(2_f64) => format!("{:.*} MB", 2, $n / kb.powf(2_f64)), 13 | $n if $n >= kb => format!("{:.*} KB", 2, $n / kb), 14 | _ => format!("{:.*} B", 0, $n), 15 | } 16 | }}; 17 | } 18 | 19 | const FORMAT: &str = "[=>-]"; 20 | const TICK_FORMAT: &str = "\\|/-"; 21 | 22 | // Output type format, indicate which format wil be used in 23 | // the speed box. 24 | #[derive(Debug)] 25 | pub enum Units { 26 | Default, 27 | Bytes, 28 | } 29 | 30 | pub struct ProgressBar { 31 | start_time: Instant, 32 | units: Units, 33 | pub total: u64, 34 | current: u64, 35 | bar_start: String, 36 | bar_current: String, 37 | bar_current_n: String, 38 | bar_remain: String, 39 | bar_end: String, 40 | tick: Vec, 41 | tick_state: usize, 42 | width: Option, 43 | message: String, 44 | last_refresh_time: Instant, 45 | max_refresh_rate: Option, 46 | pub is_finish: bool, 47 | pub is_multibar: bool, 48 | pub show_bar: bool, 49 | pub show_speed: bool, 50 | pub show_percent: bool, 51 | pub show_counter: bool, 52 | pub show_time_left: bool, 53 | pub show_tick: bool, 54 | pub show_message: bool, 55 | handle: T, 56 | } 57 | 58 | impl ProgressBar { 59 | /// Create a new ProgressBar with default configuration. 60 | /// 61 | /// # Examples 62 | /// 63 | /// ```no_run 64 | /// use std::thread; 65 | /// use pbr::{ProgressBar, Units}; 66 | /// 67 | /// let count = 1000; 68 | /// let mut pb = ProgressBar::new(count); 69 | /// pb.set_units(Units::Bytes); 70 | /// 71 | /// for _ in 0..count { 72 | /// pb.inc(); 73 | /// thread::sleep_ms(100); 74 | /// } 75 | /// ``` 76 | pub fn new(total: u64) -> ProgressBar { 77 | let handle = ::std::io::stdout(); 78 | ProgressBar::on(handle, total) 79 | } 80 | } 81 | 82 | impl ProgressBar { 83 | /// Create a new ProgressBar with default configuration but 84 | /// pass an arbitrary writer. 85 | /// 86 | /// # Examples 87 | /// 88 | /// ```no_run 89 | /// use std::thread; 90 | /// use std::io::stderr; 91 | /// use pbr::{ProgressBar, Units}; 92 | /// 93 | /// let count = 1000; 94 | /// let mut pb = ProgressBar::on(stderr(), count); 95 | /// pb.set_units(Units::Bytes); 96 | /// 97 | /// for _ in 0..count { 98 | /// pb.inc(); 99 | /// thread::sleep_ms(100); 100 | /// } 101 | /// ``` 102 | pub fn on(handle: T, total: u64) -> ProgressBar { 103 | let mut pb = ProgressBar { 104 | total, 105 | current: 0, 106 | start_time: Instant::now(), 107 | units: Units::Default, 108 | is_finish: false, 109 | is_multibar: false, 110 | show_bar: true, 111 | show_speed: true, 112 | show_percent: true, 113 | show_counter: true, 114 | show_time_left: true, 115 | show_tick: false, 116 | show_message: true, 117 | bar_start: String::new(), 118 | bar_current: String::new(), 119 | bar_current_n: String::new(), 120 | bar_remain: String::new(), 121 | bar_end: String::new(), 122 | tick: Vec::new(), 123 | tick_state: 0, 124 | width: None, 125 | message: String::new(), 126 | last_refresh_time: Instant::now(), 127 | max_refresh_rate: None, 128 | handle, 129 | }; 130 | pb.format(FORMAT); 131 | pb.tick_format(TICK_FORMAT); 132 | pb 133 | } 134 | 135 | /// Set units, default is simple numbers 136 | /// 137 | /// # Examples 138 | /// 139 | /// ```no_run 140 | /// use pbr::{ProgressBar, Units}; 141 | /// 142 | /// let n_bytes = 100; 143 | /// let mut pb = ProgressBar::new(n_bytes); 144 | /// pb.set_units(Units::Bytes); 145 | /// ``` 146 | pub fn set_units(&mut self, u: Units) { 147 | self.units = u; 148 | } 149 | 150 | /// Set custom format to the drawing bar, default is `[=>-]` 151 | /// 152 | /// # Examples 153 | /// 154 | /// ```ignore 155 | /// let mut pb = ProgressBar::new(...); 156 | /// pb.format("[=>_]"); 157 | /// ``` 158 | pub fn format(&mut self, fmt: &str) { 159 | if fmt.len() >= 5 { 160 | let v: Vec<&str> = fmt.split("").collect(); 161 | self.bar_start = v[1].to_owned(); 162 | self.bar_current = v[2].to_owned(); 163 | self.bar_current_n = v[3].to_owned(); 164 | self.bar_remain = v[4].to_owned(); 165 | self.bar_end = v[5].to_owned(); 166 | } 167 | } 168 | 169 | /// Set message to display in the prefix, call with "" to stop printing a message. 170 | /// 171 | /// All newlines are replaced with spaces. 172 | /// 173 | /// # Examples 174 | /// ```ignore 175 | /// let mut pb = ProgressBar::new(20); 176 | /// 177 | /// for x in 0..20 { 178 | /// match x { 179 | /// 0 => pb.message("Doing 1st Quarter"), 180 | /// 5 => pb.message("Doing 2nd Quarter"), 181 | /// 10 => pb.message("Doing 3rd Quarter"), 182 | /// 15 => pb.message("Doing 4th Quarter"), 183 | /// } 184 | /// pb.inc(). 185 | /// } 186 | /// 187 | /// ``` 188 | pub fn message(&mut self, message: &str) { 189 | self.message = message.replace(['\n', '\r'], " ") 190 | } 191 | 192 | /// Set tick format for the progressBar, default is \\|/- 193 | /// 194 | /// Format is not limited to 4 characters, any string can 195 | /// be used as a tick format (the tick will successively 196 | /// take the value of each char but won't loop backwards). 197 | /// 198 | /// 199 | /// # Examples 200 | /// ```ignore 201 | /// let mut pb = ProgressBar::new(...); 202 | /// pb.tick_format("▀▐▄▌") 203 | /// ``` 204 | pub fn tick_format(&mut self, tick_fmt: &str) { 205 | if tick_fmt != TICK_FORMAT { 206 | self.show_tick = true; 207 | } 208 | self.tick = tick_fmt 209 | .split("") 210 | .map(|x| x.to_owned()) 211 | .filter(|x| !x.is_empty()) 212 | .collect(); 213 | } 214 | 215 | /// Set width, or `None` for default. 216 | /// 217 | /// # Examples 218 | /// 219 | /// ```ignore 220 | /// let mut pb = ProgressBar::new(...); 221 | /// pb.set_width(Some(80)); 222 | /// ``` 223 | pub fn set_width(&mut self, w: Option) { 224 | self.width = w; 225 | } 226 | 227 | /// Set max refresh rate, above which the progress bar will not redraw, or `None` for none. 228 | /// 229 | /// # Examples 230 | /// 231 | /// ```ignore 232 | /// let mut pb = ProgressBar::new(...); 233 | /// pb.set_max_refresh_rate(Some(Duration::from_millis(100))); 234 | /// ``` 235 | pub fn set_max_refresh_rate(&mut self, w: Option) { 236 | self.max_refresh_rate = w; 237 | if let Some(dur) = self.max_refresh_rate { 238 | self.last_refresh_time = self.last_refresh_time - dur; 239 | } 240 | } 241 | 242 | /// Update progress bar even though no progress are made 243 | /// Useful to see if a program is bricked or just 244 | /// not doing any progress. 245 | /// 246 | /// tick is not needed with add or inc 247 | /// as performed operation take place 248 | /// in draw function. 249 | /// 250 | /// # Examples 251 | /// ```ignore 252 | /// let mut pb = ProgressBar::new(...); 253 | /// pb.inc(); 254 | /// for _ in ... { 255 | /// ...do something 256 | /// pb.tick(); 257 | /// } 258 | /// pb.finish(); 259 | /// ``` 260 | pub fn tick(&mut self) { 261 | self.tick_state = (self.tick_state + 1) % self.tick.len(); 262 | if self.current <= self.total { 263 | self.draw() 264 | } 265 | } 266 | 267 | /// Add to current value 268 | /// 269 | /// # Examples 270 | /// 271 | /// ```no_run 272 | /// use pbr::ProgressBar; 273 | /// 274 | /// let mut pb = ProgressBar::new(10); 275 | /// pb.add(5); 276 | /// pb.finish(); 277 | /// ``` 278 | pub fn add(&mut self, i: u64) -> u64 { 279 | self.current += i; 280 | self.tick(); 281 | self.current 282 | } 283 | 284 | /// Manually set the current value of the bar 285 | /// 286 | /// # Examples 287 | /// ```no_run 288 | /// use pbr::ProgressBar; 289 | /// 290 | /// let mut pb = ProgressBar::new(10); 291 | /// pb.set(8); 292 | /// pb.finish(); 293 | pub fn set(&mut self, i: u64) -> u64 { 294 | self.current = i; 295 | self.tick(); 296 | self.current 297 | } 298 | 299 | /// Increment current value 300 | pub fn inc(&mut self) -> u64 { 301 | self.add(1) 302 | } 303 | 304 | /// Resets the start time to now 305 | pub fn reset_start_time(&mut self) { 306 | self.start_time = Instant::now(); 307 | } 308 | 309 | fn draw(&mut self) { 310 | let now = Instant::now(); 311 | if let Some(mrr) = self.max_refresh_rate { 312 | if now - self.last_refresh_time < mrr && self.current < self.total { 313 | return; 314 | } 315 | } 316 | 317 | let mut time_elapsed = now - self.start_time; 318 | if time_elapsed.is_zero() { 319 | time_elapsed = Duration::from_nanos(1); 320 | } 321 | let speed = self.current as f64 / time_elapsed.as_secs_f64(); 322 | let width = self.width(); 323 | 324 | let mut out; 325 | let mut parts = Vec::new(); 326 | let mut base = String::new(); 327 | let mut prefix = String::new(); 328 | let mut suffix = String::from(" "); 329 | 330 | // precent box 331 | if self.show_percent { 332 | let percent = self.current as f64 / (self.total as f64 / 100f64); 333 | parts.push(format!( 334 | "{:.*} %", 335 | 2, 336 | if percent.is_nan() { 0.0 } else { percent } 337 | )); 338 | } 339 | // speed box 340 | if self.show_speed { 341 | match self.units { 342 | Units::Default => parts.push(format!("{:.*}/s", 2, speed)), 343 | Units::Bytes => parts.push(format!("{}/s", kb_fmt!(speed))), 344 | }; 345 | } 346 | // time left box 347 | if self.show_time_left && self.current > 0 && self.total > self.current { 348 | let left = 1. / speed * (self.total - self.current) as f64; 349 | if left < 60. { 350 | parts.push(format!("{:.0}s", left)); 351 | } else { 352 | parts.push(format!("{:.0}m", left / 60.)); 353 | }; 354 | } 355 | suffix += &parts.join(" "); 356 | // message box 357 | if self.show_message { 358 | prefix = prefix + &self.message; 359 | } 360 | // counter box 361 | if self.show_counter { 362 | let (c, t) = (self.current as f64, self.total as f64); 363 | prefix = prefix 364 | + &match self.units { 365 | Units::Default => format!("{} / {} ", c, t), 366 | Units::Bytes => format!("{} / {} ", kb_fmt!(c), kb_fmt!(t)), 367 | }; 368 | } 369 | // tick box 370 | if self.show_tick { 371 | prefix = prefix + &format!("{} ", self.tick[self.tick_state]); 372 | } 373 | // bar box 374 | if self.show_bar { 375 | let p = prefix.chars().count() + suffix.chars().count() + 3; 376 | if p < width { 377 | let size = width - p; 378 | let curr_count = 379 | ((self.current as f64 / self.total as f64) * size as f64).ceil() as usize; 380 | if size >= curr_count { 381 | let rema_count = size - curr_count; 382 | base = self.bar_start.clone(); 383 | if rema_count > 0 && curr_count > 0 { 384 | base = 385 | base + &self.bar_current.repeat(curr_count - 1) + &self.bar_current_n; 386 | } else { 387 | base = base + &self.bar_current.repeat(curr_count); 388 | } 389 | base = base + &self.bar_remain.repeat(rema_count) + &self.bar_end; 390 | } 391 | } 392 | } 393 | out = prefix + &base + &suffix; 394 | // pad 395 | if out.len() < width { 396 | let gap = width - out.len(); 397 | out = out + &" ".repeat(gap); 398 | } 399 | // print 400 | printfl!(self.handle, "\r{}", out); 401 | 402 | self.last_refresh_time = Instant::now(); 403 | } 404 | 405 | // finish_draw ensure that the progress bar is reached to its end, and do the 406 | // last drawing if needed. 407 | fn finish_draw(&mut self) { 408 | let mut redraw = false; 409 | 410 | if let Some(mrr) = self.max_refresh_rate { 411 | if Instant::now() - self.last_refresh_time < mrr { 412 | self.max_refresh_rate = None; 413 | redraw = true; 414 | } 415 | } 416 | 417 | if self.current < self.total { 418 | self.current = self.total; 419 | redraw = true; 420 | } 421 | 422 | if redraw { 423 | self.draw(); 424 | } 425 | self.is_finish = true; 426 | } 427 | 428 | /// Calling finish manually will set current to total and draw 429 | /// the last time 430 | pub fn finish(&mut self) { 431 | self.finish_draw(); 432 | self.handle.write(b"").expect("write() failed"); 433 | } 434 | 435 | /// Call finish and write string `s` that will replace the progress bar. 436 | pub fn finish_print(&mut self, s: &str) { 437 | self.finish_draw(); 438 | let width = self.width(); 439 | let mut out = s.to_owned(); 440 | if s.len() < width { 441 | out += &" ".repeat(width - s.len()); 442 | }; 443 | printfl!(self.handle, "\r{}", out); 444 | self.finish(); 445 | } 446 | 447 | /// Call finish and write string `s` below the progress bar. 448 | /// 449 | /// If the ProgressBar is part of MultiBar instance, you should use 450 | /// `finish_print` to print message. 451 | pub fn finish_println(&mut self, s: &str) { 452 | // `finish_println` does not allow in MultiBar mode, because printing 453 | // new line will break the multiBar output. 454 | if self.is_multibar { 455 | return self.finish_print(s); 456 | } 457 | self.finish_draw(); 458 | printfl!(self.handle, "\n{}", s); 459 | } 460 | 461 | /// Get terminal width, from configuration, terminal size, or default(80) 462 | fn width(&mut self) -> usize { 463 | if let Some(w) = self.width { 464 | w 465 | } else if let Some((Width(w), _)) = terminal_size() { 466 | w as usize 467 | } else { 468 | 80 469 | } 470 | } 471 | } 472 | 473 | // Implement io::Writer 474 | impl Write for ProgressBar { 475 | fn write(&mut self, buf: &[u8]) -> io::Result { 476 | let n = buf.len(); 477 | self.add(n as u64); 478 | Ok(n) 479 | } 480 | fn flush(&mut self) -> io::Result<()> { 481 | Ok(()) 482 | } 483 | } 484 | 485 | #[cfg(test)] 486 | mod test { 487 | use crate::{ProgressBar, Units}; 488 | use std::time::Duration; 489 | 490 | #[test] 491 | fn add() { 492 | let mut pb = ProgressBar::new(10); 493 | pb.add(2); 494 | assert!(pb.current == 2, "should add the given `n` to current"); 495 | assert!( 496 | pb.add(2) == pb.current, 497 | "add should return the current value" 498 | ); 499 | } 500 | 501 | #[test] 502 | fn inc() { 503 | let mut pb = ProgressBar::new(10); 504 | pb.inc(); 505 | assert!(pb.current == 1, "should increment current by 1"); 506 | } 507 | 508 | #[test] 509 | fn format() { 510 | let fmt = "[~> ]"; 511 | let mut pb = ProgressBar::new(1); 512 | pb.format(fmt); 513 | assert!( 514 | pb.bar_start + &pb.bar_current + &pb.bar_current_n + &pb.bar_remain + &pb.bar_end 515 | == fmt 516 | ); 517 | } 518 | 519 | #[test] 520 | fn finish() { 521 | let mut pb = ProgressBar::new(10); 522 | pb.finish(); 523 | assert!(pb.current == pb.total, "should set current to total"); 524 | assert!(pb.is_finish, "should set is_finish to true"); 525 | } 526 | 527 | #[test] 528 | fn kb_fmt() { 529 | let kb = 1024f64; 530 | let mb = kb.powf(2f64); 531 | let gb = kb.powf(3f64); 532 | let tb = kb.powf(4f64); 533 | assert_eq!(kb_fmt!(kb), "1.00 KB"); 534 | assert_eq!(kb_fmt!(mb), "1.00 MB"); 535 | assert_eq!(kb_fmt!(gb), "1.00 GB"); 536 | assert_eq!(kb_fmt!(tb), "1.00 TB"); 537 | } 538 | 539 | #[test] 540 | fn disable_speed_percent() { 541 | let mut out = Vec::new(); 542 | let mut pb = ProgressBar::on(&mut out, 10); 543 | pb.show_speed = false; 544 | pb.show_percent = false; 545 | pb.set_width(Some(80)); 546 | pb.add(2); 547 | assert_eq!( 548 | std::str::from_utf8(&out).unwrap(), 549 | "\r2 / 10 [=============>-----------------------------------------------------] 0s ", 550 | ); 551 | } 552 | 553 | #[test] 554 | fn disable_speed_time_left() { 555 | let mut out = Vec::new(); 556 | let mut pb = ProgressBar::on(&mut out, 10); 557 | pb.show_speed = false; 558 | pb.show_time_left = false; 559 | pb.set_width(Some(65)); 560 | pb.add(1); 561 | assert_eq!( 562 | std::str::from_utf8(&out).unwrap(), 563 | "\r1 / 10 [====>------------------------------------------] 10.00 % ", 564 | ); 565 | } 566 | 567 | #[test] 568 | fn disable_percent_time_left() { 569 | let mut out = Vec::new(); 570 | let mut pb = ProgressBar::on(&mut out, 10); 571 | pb.show_percent = false; 572 | pb.show_time_left = false; 573 | pb.set_units(Units::Bytes); 574 | pb.set_width(Some(65)); 575 | pb.draw(); 576 | assert_eq!( 577 | std::str::from_utf8(&out).unwrap(), 578 | "\r0 B / 10 B [---------------------------------------------] 0 B/s ", 579 | ); 580 | } 581 | 582 | #[test] 583 | fn disable_suffix() { 584 | let mut out = Vec::new(); 585 | let mut pb = ProgressBar::on(&mut out, 10); 586 | pb.show_speed = false; 587 | pb.show_percent = false; 588 | pb.show_time_left = false; 589 | pb.set_units(Units::Bytes); 590 | pb.set_width(Some(65)); 591 | pb.draw(); 592 | assert_eq!( 593 | std::str::from_utf8(&out).unwrap(), 594 | "\r0 B / 10 B [--------------------------------------------------] ", 595 | ); 596 | } 597 | 598 | #[test] 599 | fn max_refresh_rate_finish() { 600 | let count = 500; 601 | let mut out = Vec::new(); 602 | let mut pb = ProgressBar::on(&mut out, count); 603 | pb.format("╢▌▌░╟"); 604 | pb.set_width(Some(80)); 605 | pb.set_max_refresh_rate(Some(Duration::from_millis(100))); 606 | pb.show_speed = false; 607 | pb.show_time_left = false; 608 | pb.add(count / 2); 609 | pb.add(count / 2); 610 | let mut split = std::str::from_utf8(&out) 611 | .unwrap() 612 | .trim_start_matches('\r') 613 | .split('\r'); 614 | assert_eq!( 615 | split.next(), 616 | Some("250 / 500 ╢▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌░░░░░░░░░░░░░░░░░░░░░░░░░░░░░╟ 50.00 %") 617 | ); 618 | assert_eq!( 619 | split.next(), 620 | Some("500 / 500 ╢▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌╟ 100.00 %") 621 | ); 622 | } 623 | } 624 | --------------------------------------------------------------------------------