retrogress/progress/
mod.rs

1use console::{Key, Term};
2use std::sync::atomic::{AtomicUsize, Ordering};
3use std::sync::mpsc::{self, Receiver, Sender};
4use std::sync::{Arc, Mutex};
5use std::thread::{self, JoinHandle};
6use std::time::Duration;
7
8/// A reference to a specific progress bar.
9#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
10pub struct Ref(usize);
11
12impl Ref {
13    /// May be used by implementers of new progress bar behaviors
14    /// when a new progress bar is appended to the console.
15    pub fn new() -> Self {
16        static COUNTER: AtomicUsize = AtomicUsize::new(1);
17        Self(COUNTER.fetch_add(1, Ordering::Relaxed))
18    }
19}
20
21impl Default for Ref {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl std::fmt::Display for Ref {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        write!(f, "#Ref<{}>", self.0)
30    }
31}
32
33/// Progress is a trait that may be implemented to create new progress bar
34/// behaviors. Its normal usage is as a `Box<dyn Progress>` that can be
35/// put into a `ProgressBar`.
36pub trait Progress: Send + Sync {
37    /// Append a new progress bar to the console. Returns a `usize` that
38    /// serves as a reference to the progress bar in other functions.
39    fn append(&mut self, msg: &str) -> Ref;
40    /// Called after prompt input is received.
41    fn clear_prompt(&mut self);
42    /// Mark the given progress bar as failed.
43    fn failed(&mut self, references: Ref);
44    /// Hides the given progress bar.
45    fn hide(&mut self, reference: Ref);
46    /// Prints a line of text above a progress bar, without interrupted it.
47    /// Helpful when capturing output from commands to show to users.
48    fn println(&mut self, reference: Ref, msg: &str);
49    /// Prints a line above all progress bars.
50    fn print_inline(&mut self, msg: &str);
51    /// Prints out the message as a prompt. Ensures that the entire prompt
52    /// is printed.
53    fn prompt(&mut self, msg: &str);
54    /// Function to rerender the progress bar. This will be called on a
55    /// regular interval.
56    fn render(&mut self);
57    /// Update the message shown for a progress bar.
58    fn set_message(&mut self, reference: Ref, msg: String);
59    /// When input is received during a prompt, the current state of the
60    /// received input will be passed into this on each key press.
61    fn set_prompt_input(&mut self, input: String);
62    /// Shows the given progress bar.
63    fn show(&mut self, reference: Ref);
64    /// Mark the given progress bar as succeeded.
65    fn succeeded(&mut self, reference: Ref);
66}
67
68enum ProgressMessage {
69    Tick,
70    Shutdown,
71}
72
73pub struct ProgressBar {
74    progress: Arc<Mutex<Box<dyn Progress>>>,
75    renderer: Arc<Mutex<Option<JoinHandle<()>>>>,
76    sender: Arc<Mutex<Sender<ProgressMessage>>>,
77    ticker: Arc<Mutex<Option<JoinHandle<()>>>>,
78    counter: *mut Counter,
79}
80
81struct Counter(AtomicUsize);
82
83unsafe impl std::marker::Send for ProgressBar {}
84unsafe impl std::marker::Sync for ProgressBar {}
85
86impl ProgressBar {
87    fn counter(&self) -> &Counter {
88        unsafe { &*self.counter }
89    }
90
91    pub fn new(bar: Box<dyn Progress>) -> Self {
92        let _ = Term::stdout().hide_cursor();
93        let _ = Term::stderr().hide_cursor();
94
95        let (sender, receiver) = mpsc::channel();
96        let counter = Box::into_raw(Box::new(Counter(AtomicUsize::new(1))));
97        let progress = Arc::new(Mutex::new(bar));
98
99        let progress_bar = Self {
100            progress: progress.clone(),
101            renderer: Arc::new(Mutex::new(None)),
102            sender: Arc::new(Mutex::new(sender.clone())),
103            ticker: Arc::new(Mutex::new(None)),
104            counter,
105        };
106
107        let renderer = thread::spawn(move || {
108            Self::start_renderer(progress, receiver);
109        });
110
111        let ticker_sender = sender.clone();
112        let ticker = thread::spawn(move || loop {
113            thread::sleep(Duration::from_millis(80));
114            if ticker_sender.send(ProgressMessage::Tick).is_err() {
115                break;
116            }
117        });
118
119        {
120            let mut render_handle = progress_bar.renderer.lock().unwrap();
121            *render_handle = Some(renderer);
122        }
123        {
124            let mut ticker_handle = progress_bar.ticker.lock().unwrap();
125            *ticker_handle = Some(ticker);
126        }
127
128        progress_bar
129    }
130
131    pub fn append(&mut self, msg: &str) -> Ref {
132        self.progress.lock().unwrap().append(msg)
133    }
134    pub fn failed(&mut self, reference: Ref) {
135        self.progress.lock().unwrap().failed(reference)
136    }
137    pub fn hide(&mut self, reference: Ref) {
138        self.progress.lock().unwrap().hide(reference)
139    }
140    pub fn println(&mut self, reference: Ref, msg: &str) {
141        self.progress.lock().unwrap().println(reference, msg)
142    }
143    pub fn print_inline(&mut self, msg: &str) {
144        let mut progress = self.progress.lock().unwrap();
145        progress.print_inline(msg);
146        progress.render();
147    }
148    pub fn prompt(&mut self, msg: &str) -> String {
149        self.progress.lock().unwrap().prompt(msg);
150        let input = self.read_input();
151        self.progress.lock().unwrap().clear_prompt();
152        input.trim().into()
153    }
154    pub fn set_message(&mut self, reference: Ref, msg: String) {
155        self.progress.lock().unwrap().set_message(reference, msg)
156    }
157    pub fn show(&mut self, reference: Ref) {
158        self.progress.lock().unwrap().show(reference)
159    }
160    pub fn succeeded(&mut self, reference: Ref) {
161        self.progress.lock().unwrap().succeeded(reference)
162    }
163
164    fn start_renderer(
165        progress: Arc<Mutex<Box<dyn Progress>>>,
166        receiver: Receiver<ProgressMessage>,
167    ) {
168        while let Ok(message) = receiver.recv() {
169            match message {
170                ProgressMessage::Tick => progress.lock().unwrap().render(),
171                ProgressMessage::Shutdown => {
172                    break;
173                }
174            }
175        }
176    }
177
178    fn read_input(&mut self) -> String {
179        let term = Term::stdout();
180        let mut input = String::new();
181
182        loop {
183            match term.read_key().unwrap() {
184                Key::Enter => break,
185                Key::Char(c) => {
186                    input.push(c);
187                }
188                Key::Backspace => {
189                    if !input.is_empty() {
190                        input.pop();
191                    }
192                }
193                _ => {}
194            }
195            self.progress
196                .lock()
197                .unwrap()
198                .set_prompt_input(input.clone());
199        }
200        input
201    }
202}
203
204impl Clone for ProgressBar {
205    fn clone(&self) -> Self {
206        self.counter().0.fetch_add(1, Ordering::Relaxed);
207        Self {
208            progress: Arc::clone(&self.progress),
209            renderer: Arc::clone(&self.renderer),
210            sender: Arc::clone(&self.sender),
211            ticker: Arc::clone(&self.ticker),
212            counter: self.counter,
213        }
214    }
215}
216
217impl Drop for ProgressBar {
218    fn drop(&mut self) {
219        let counter = self.counter().0.fetch_sub(1, Ordering::AcqRel);
220        if counter == 1 {
221            let _ = self.sender.lock().unwrap().send(ProgressMessage::Shutdown);
222
223            let mut join_handle = self.ticker.lock().unwrap();
224            if let Some(ticker) = join_handle.take() {
225                let _ = ticker.join();
226            }
227
228            let mut join_handle = self.renderer.lock().unwrap();
229            if let Some(renderer) = join_handle.take() {
230                let _ = renderer.join();
231            }
232
233            let _ = Term::stdout().show_cursor();
234            let _ = Term::stderr().show_cursor();
235        }
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242    use std::sync::{Arc, Mutex};
243
244    #[derive(Default)]
245    struct MockProgress {
246        appended: Arc<Mutex<Vec<(Ref, String)>>>,
247        failed_refs: Arc<Mutex<Vec<Ref>>>,
248        hidden_refs: Arc<Mutex<Vec<Ref>>>,
249        println_calls: Arc<Mutex<Vec<(Ref, String)>>>,
250        prompt_calls: Arc<Mutex<Vec<String>>>,
251        set_message_calls: Arc<Mutex<Vec<(Ref, String)>>>,
252        shown_refs: Arc<Mutex<Vec<Ref>>>,
253        inline_calls: Arc<Mutex<Vec<String>>>,
254        succeeded_refs: Arc<Mutex<Vec<Ref>>>,
255    }
256
257    impl MockProgress {
258        fn new() -> Self {
259            Self::default()
260        }
261    }
262
263    impl Progress for MockProgress {
264        fn append(&mut self, msg: &str) -> Ref {
265            let reference = Ref::new();
266            self.appended
267                .lock()
268                .unwrap()
269                .push((reference, msg.to_string()));
270            reference
271        }
272
273        fn clear_prompt(&mut self) {}
274
275        fn failed(&mut self, reference: Ref) {
276            self.failed_refs.lock().unwrap().push(reference);
277        }
278
279        fn hide(&mut self, reference: Ref) {
280            self.hidden_refs.lock().unwrap().push(reference);
281        }
282
283        fn println(&mut self, reference: Ref, msg: &str) {
284            self.println_calls
285                .lock()
286                .unwrap()
287                .push((reference, msg.to_string()));
288        }
289
290        fn print_inline(&mut self, msg: &str) {
291            self.inline_calls.lock().unwrap().push(msg.to_string());
292        }
293
294        fn prompt(&mut self, msg: &str) {
295            self.prompt_calls.lock().unwrap().push(msg.to_string());
296        }
297
298        fn render(&mut self) {}
299
300        fn set_message(&mut self, reference: Ref, msg: String) {
301            self.set_message_calls
302                .lock()
303                .unwrap()
304                .push((reference, msg));
305        }
306
307        fn set_prompt_input(&mut self, _input: String) {}
308
309        fn show(&mut self, reference: Ref) {
310            self.shown_refs.lock().unwrap().push(reference);
311        }
312
313        fn succeeded(&mut self, reference: Ref) {
314            self.succeeded_refs.lock().unwrap().push(reference);
315        }
316    }
317
318    #[test]
319    fn ref_new_generates_unique_ids() {
320        let ref1 = Ref::new();
321        let ref2 = Ref::new();
322        let ref3 = Ref::new();
323
324        assert_ne!(ref1, ref2);
325        assert_ne!(ref2, ref3);
326        assert_ne!(ref1, ref3);
327    }
328
329    #[test]
330    fn ref_default_creates_new_ref() {
331        let ref1 = Ref::default();
332        let ref2 = Ref::default();
333
334        assert_ne!(ref1, ref2);
335    }
336
337    #[test]
338    fn ref_is_copy_and_clone() {
339        let ref1 = Ref::new();
340        let ref2 = ref1; // Copy
341        let ref3 = ref1; // Clone
342
343        assert_eq!(ref1, ref2);
344        assert_eq!(ref1, ref3);
345        assert_eq!(ref2, ref3);
346    }
347
348    #[test]
349    fn progress_bar_append_delegates_to_implementation() {
350        let mock = MockProgress::new();
351        let appended_calls = Arc::clone(&mock.appended);
352        let mut progress_bar = ProgressBar::new(Box::new(mock));
353
354        let ref1 = progress_bar.append("test message 1");
355        let ref2 = progress_bar.append("test message 2");
356
357        let calls = appended_calls.lock().unwrap();
358        assert_eq!(
359            *calls,
360            vec![
361                (ref1, "test message 1".to_string()),
362                (ref2, "test message 2".to_string())
363            ]
364        );
365    }
366
367    #[test]
368    fn progress_bar_failed_delegates_to_implementation() {
369        let mock = MockProgress::new();
370        let failed_calls = Arc::clone(&mock.failed_refs);
371        let mut progress_bar = ProgressBar::new(Box::new(mock));
372
373        let reference = Ref::new();
374        progress_bar.failed(reference);
375
376        let calls = failed_calls.lock().unwrap();
377        assert_eq!(*calls, vec![reference]);
378    }
379
380    #[test]
381    fn progress_bar_succeeded_delegates_to_implementation() {
382        let mock = MockProgress::new();
383        let succeeded_calls = Arc::clone(&mock.succeeded_refs);
384        let mut progress_bar = ProgressBar::new(Box::new(mock));
385
386        let reference = Ref::new();
387        progress_bar.succeeded(reference);
388
389        let calls = succeeded_calls.lock().unwrap();
390        assert_eq!(*calls, vec![reference]);
391    }
392
393    #[test]
394    fn progress_bar_hide_delegates_to_implementation() {
395        let mock = MockProgress::new();
396        let hidden_calls = Arc::clone(&mock.hidden_refs);
397        let mut progress_bar = ProgressBar::new(Box::new(mock));
398
399        let reference = Ref::new();
400        progress_bar.hide(reference);
401
402        let calls = hidden_calls.lock().unwrap();
403        assert_eq!(*calls, vec![reference]);
404    }
405
406    #[test]
407    fn progress_bar_show_delegates_to_implementation() {
408        let mock = MockProgress::new();
409        let shown_calls = Arc::clone(&mock.shown_refs);
410        let mut progress_bar = ProgressBar::new(Box::new(mock));
411
412        let reference = Ref::new();
413        progress_bar.show(reference);
414
415        let calls = shown_calls.lock().unwrap();
416        assert_eq!(*calls, vec![reference]);
417    }
418
419    #[test]
420    fn progress_bar_println_delegates_to_implementation() {
421        let mock = MockProgress::new();
422        let println_calls = Arc::clone(&mock.println_calls);
423        let mut progress_bar = ProgressBar::new(Box::new(mock));
424
425        let reference = Ref::new();
426        progress_bar.println(reference, "test output");
427
428        let calls = println_calls.lock().unwrap();
429        assert_eq!(*calls, vec![(reference, "test output".to_string())]);
430    }
431
432    #[test]
433    fn progress_bar_set_message_delegates_to_implementation() {
434        let mock = MockProgress::new();
435        let set_message_calls = Arc::clone(&mock.set_message_calls);
436        let mut progress_bar = ProgressBar::new(Box::new(mock));
437
438        let reference = Ref::new();
439        progress_bar.set_message(reference, "updated message".to_string());
440
441        let calls = set_message_calls.lock().unwrap();
442        assert_eq!(*calls, vec![(reference, "updated message".to_string())]);
443    }
444
445    #[test]
446    fn progress_bar_is_send_and_sync() {
447        fn assert_send<T: Send>() {}
448        fn assert_sync<T: Sync>() {}
449
450        assert_send::<ProgressBar>();
451        assert_sync::<ProgressBar>();
452    }
453
454    #[test]
455    fn progress_bar_clone() {
456        let mock = MockProgress::new();
457        let progress_bar = ProgressBar::new(Box::new(mock));
458        let cloned_progress_bar = progress_bar.clone();
459
460        drop(progress_bar);
461        drop(cloned_progress_bar);
462    }
463}