mtlog_progress/lib.rs
1
2//! # mtlog-progress
3//! A progress bar implementation working gracefully with mtlog's logger.
4//!
5//! ## Usage with std threads
6//! ```toml
7//! // Cargo.toml
8//! ...
9//! [dependencies]
10//! mtlog-progress = "0.1.0"
11//! mtlog = "0.1.4"
12//! ```
13//!
14//! ```rust
15//! use mtlog::logger_config;
16//! use mtlog_progress::LogProgressBar;
17//!
18//! logger_config()
19//! .init_global();
20//!
21//! let h = std::thread::spawn(|| {
22//! let pb = LogProgressBar::new(100, "My Progress Bar");
23//! for i in 0..100 {
24//! pb.inc(1);
25//! if i == 50 {
26//! log::info!("Halfway there!");
27//! }
28//! }
29//! pb.finish();
30//! });
31//! log::info!("This log goes below the progress bar");
32//! h.join().unwrap(); // the progress bar continue to work at it's line position
33//!
34//! ```
35//! ## Usage with tokio tasks
36//!
37//! ## Usage
38//! ```toml
39//! // Cargo.toml
40//! ...
41//! [dependencies]
42//! mtlog-progress = "0.1.0"
43//! mtlog-tokio = "0.1.0"
44//! tokio = { version = "1.40.0", features = ["full"] }
45//! ```
46//!
47//! ```rust
48//! use mtlog_tokio::logger_config;
49//! use mtlog_progress::LogProgressBar;
50//!
51//! #[tokio::main]
52//! async fn main() {
53//! logger_config()
54//! .scope_global(async move {
55//! let h = tokio::spawn(async move {
56//! logger_config()
57//! .scope_local(async move {
58//! let pb = LogProgressBar::new(100, "My Progress Bar");
59//! for i in 0..100 {
60//! pb.inc(1);
61//! if i == 50 {
62//! log::info!("Halfway there!");
63//! }
64//! }
65//! pb.finish();
66//! }).await;
67//! });
68//! log::info!("This log goes below the progress bar");
69//! h.await.unwrap(); // the progress bar continue to work at it's line position
70//! }).await;
71//! }
72//! ```
73
74
75use std::sync::{Arc, Mutex};
76use colored::Colorize;
77use uuid::Uuid;
78
79
80#[derive(Clone)]
81pub struct LogProgressBar {
82 n_iter: Arc<usize>,
83 name: Arc<str>,
84 current_iter: Arc<Mutex<usize>>,
85 id: Arc<Uuid>,
86 finished: Arc<Mutex<bool>>
87}
88
89impl LogProgressBar {
90 pub fn new(n_iter: usize, name: &str) -> Self {
91 let pb = Self {
92 n_iter: Arc::new(n_iter.max(1)),
93 name: name.into(),
94 current_iter: Arc::new(Mutex::new(0usize)),
95 id: Arc::new(Uuid::new_v4()),
96 finished: Arc::new(Mutex::new(false))
97 };
98 pb.send();
99 pb
100 }
101
102 pub fn send(&self) {
103 if *self.finished.lock().unwrap() {
104 log::info!("___PROGRESS___{}___FINISHED",self.id)
105 } else {
106 log::info!("___PROGRESS___{}___{}",self.id,self.format())
107 }
108 }
109
110 pub fn set_progress(&self, n: usize) {
111 *self.current_iter.lock().unwrap() = n;
112 self.send();
113 }
114
115 pub fn inc(&self, n: usize) {
116 *self.current_iter.lock().unwrap() += n;
117 self.send();
118 }
119
120 fn format(&self) -> String {
121 let current_iter = *self.current_iter.lock().unwrap();
122 let percentage = (current_iter as f64 / *self.n_iter as f64 * 100.0) as usize;
123 let bar_length = 20; // Length of the progress bar
124 let filled_length = (bar_length * current_iter / *self.n_iter).min(bar_length);
125 let bar = "#".repeat(filled_length) + &".".repeat(bar_length - filled_length);
126 let n_iter_str = self.n_iter.to_string();
127 format!(
128 "Progress {name}: [{bar}] {current:>len$}/{n_iter_str} {percentage:>3}%",
129 name=self.name.cyan(),
130 bar=bar.cyan(),
131 current=current_iter,
132 len=n_iter_str.len(),
133 )
134 }
135
136 pub fn finish(&self) {
137 if *self.finished.lock().unwrap() {
138 return
139 }
140 *self.finished.lock().unwrap() = true;
141 *self.current_iter.lock().unwrap() = *self.n_iter;
142 self.send();
143 }
144}
145
146impl Drop for LogProgressBar {
147 fn drop(&mut self) {
148 *self.finished.lock().unwrap() = true;
149 self.send();
150 }
151}
152
153
154#[test]
155fn test_progress_bar() {
156 use mtlog::logger_config;
157 logger_config()
158 .init_global();
159 let pb = LogProgressBar::new(100, "Test");
160 for _ in 0..50 {
161 pb.inc(1);
162 }
163 pb.finish();
164 std::thread::sleep(std::time::Duration::from_millis(1));
165}