Skip to main content

termal_core/progress/
dots.rs

1use std::{
2    fmt::{Display, Write as _},
3    io::{Write as _, stdout},
4    time::Duration,
5};
6
7use crate::{
8    codes,
9    progress::{ProgressFormatter, duration_to_string},
10};
11
12/// A progress tracker that uses triple dots.
13pub struct Dots {
14    pub log: bool,
15    buf: String,
16}
17
18/// A simple tracker that tracks only completion.
19pub fn dots<T>(task: &str, f: impl FnOnce() -> T) -> T {
20    let mut d = Dots::default();
21    d.start(task, "");
22    let res = f();
23    d.finish(task, "", Duration::default());
24    res
25}
26
27/// A simple tracker that tracks only completion and failure.
28pub fn dots_try<T, E: Display>(
29    task: &str,
30    f: impl FnOnce() -> Result<T, E>,
31) -> Result<T, E> {
32    let mut d = Dots::default();
33    d.start(task, "");
34    match f() {
35        res @ Ok(_) => {
36            d.finish(task, "", Duration::default());
37            res
38        }
39        Err(e) => {
40            d.fail(None, task, "", Duration::default(), &format!("{e}"));
41            Err(e)
42        }
43    }
44}
45
46impl Dots {
47    fn show_progress(
48        &mut self,
49        done: Option<f32>,
50        task: &str,
51        info: &str,
52        eta: Option<Duration>,
53    ) {
54        self.buf.clear();
55        self.buf += codes::ERASE_TO_END;
56        self.buf += codes::CUR_SAVE;
57        self.format_progress(done, task, info, eta);
58        self.buf += codes::CUR_LOAD;
59        print!("{}", self.buf);
60        _ = stdout().flush();
61        if self.log {
62            print!("{}", codes::ERASE_TO_END);
63        }
64    }
65
66    fn format_progress(
67        &mut self,
68        done: Option<f32>,
69        task: &str,
70        info: &str,
71        eta: Option<Duration>,
72    ) {
73        self.buf += task;
74        self.buf += "...";
75
76        if let Some(done) = done {
77            _ = write!(self.buf, " \x1b[96m{:.2} %\x1b[0m", done * 100.);
78        }
79
80        if let Some(time) = eta {
81            self.buf += " [\x1b[95m";
82            duration_to_string(time, true, &mut self.buf);
83            self.buf += "\x1b[0m]"
84        }
85
86        if !info.is_empty() {
87            self.buf.push(' ');
88            self.buf += info;
89        }
90    }
91}
92
93impl ProgressFormatter for Dots {
94    fn start(&mut self, task: &str, info: &str) {
95        self.show_progress(None, task, info, None);
96    }
97
98    fn update(
99        &mut self,
100        done: Option<f32>,
101        task: &str,
102        info: &str,
103        time: Duration,
104    ) {
105        self.show_progress(done, task, info, Some(time));
106    }
107
108    fn finish(&mut self, task: &str, _: &str, _: Duration) {
109        self.buf.clear();
110        self.buf += codes::ERASE_TO_END;
111        self.format_progress(None, task, "\x1b[92mDone!\x1b[0m", None);
112        println!("{}", self.buf);
113    }
114
115    fn fail(
116        &mut self,
117        _: Option<f32>,
118        task: &str,
119        _: &str,
120        _: Duration,
121        err: &str,
122    ) {
123        self.buf.clear();
124        self.buf += codes::ERASE_TO_END;
125        self.format_progress(None, task, "\x1b[91mFailed!\x1b[0m", None);
126        println!("{}\n\x1b[91merror:\x1b[0m {err}", self.buf);
127    }
128}
129
130impl Default for Dots {
131    fn default() -> Self {
132        Self {
133            log: true,
134            buf: String::new(),
135        }
136    }
137}