howudoin/consumers/
term_line.rs1use crate::*;
2use indicatif::*;
3use report::*;
4
5pub struct TermLine {
17 debounce: Duration,
18 bars: flat_tree::FlatTree<Id, ProgressBar>,
19 mp: MultiProgress,
20}
21
22impl Consume for TermLine {
23 fn debounce(&self) -> Duration {
24 self.debounce
25 }
26
27 fn rpt(&mut self, rpt: &report::Report, id: Id, parent: Option<Id>, _: &Controller) {
28 match self.bars.get(&id) {
29 Some(x) => update_bar(x, rpt),
30 None => update_bar(&self.add_bar(id, parent), rpt),
31 };
32 }
33
34 fn closed(&mut self, id: Id) {
35 if let Some(bar) = self.bars.remove(&id) {
36 bar.finish_and_clear();
37 self.mp.remove(&bar);
38 }
39 }
40}
41
42impl TermLine {
43 pub fn new() -> Self {
45 Self {
46 debounce: Duration::from_millis(50),
47 mp: MultiProgress::new(),
48 bars: Default::default(),
49 }
50 }
51
52 pub fn with_debounce(debounce: Duration) -> Self {
54 Self {
55 debounce,
56 ..Self::new()
57 }
58 }
59
60 fn add_bar(&mut self, id: Id, parent: Option<Id>) -> ProgressBar {
61 match parent.and_then(|x| self.bars.get(&x)).cloned() {
62 None => {
63 let bar = self.mp.add(pb());
64 self.bars.insert_root(id, bar.clone());
65 bar
66 }
67 Some(parent) => {
68 let bar = self.mp.insert_after(&parent, pb());
69 self.bars.insert(id, bar.clone());
70 bar
71 }
72 }
73 }
74}
75
76impl Default for TermLine {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82fn update_bar(pb: &ProgressBar, rpt: &Report) {
83 let Report {
84 label,
85 desc,
86 state,
87 accums,
88 } = rpt;
89
90 pb.set_prefix(label.clone());
91 pb.set_message(desc.clone());
92
93 match state {
94 State::InProgress {
95 len,
96 pos,
97 bytes,
98 remaining: _,
99 } => {
100 pb.set_length(len.unwrap_or(!0));
101 pb.set_position(*pos);
102 match len.is_some() {
103 true => pb.set_style(bar_style(*bytes)),
104 false => pb.set_style(spinner_style(*bytes)),
105 }
106 }
107
108 State::Completed { duration } => {
109 pb.finish_with_message(format!(
110 "finished in {}",
111 HumanDuration(Duration::try_from_secs_f32(*duration).unwrap_or_default())
112 ));
113 }
114
115 State::Cancelled => {
116 pb.abandon_with_message("cancelled");
117 }
118 }
119
120 for Message { severity, msg } in accums {
121 pb.println(format!("{severity}: {msg}"));
122 }
123}
124
125fn pb() -> ProgressBar {
126 let pb = ProgressBar::hidden().with_style(spinner_style(false));
127 pb.enable_steady_tick(std::time::Duration::from_millis(250));
128 pb
129}
130
131fn spinner_style(fmt_bytes: bool) -> ProgressStyle {
132 let tmp = if fmt_bytes {
133 format!(
134 " {} {}: {} {} {}",
135 SPINNER, PREFIX, BYTES, BYTES_PER_SEC, MSG
136 )
137 } else {
138 format!(" {} {}: {} {}", SPINNER, PREFIX, POS, MSG)
139 };
140 ProgressStyle::default_bar()
141 .template(&tmp)
142 .expect("template should be fine")
143 .progress_chars("=> ")
144 .tick_chars(r#"|/-\|"#)
145}
146
147fn bar_style(fmt_bytes: bool) -> ProgressStyle {
148 let tmp = if fmt_bytes {
149 format!(
150 " {} {} {} {}
151 {} {} ({}/{}) {}",
152 SPINNER, PREFIX, BYTES_PER_SEC, ETA, BAR, PCT, BYTES, BYTES_TOTAL, MSG
153 )
154 } else {
155 format!(
156 " {} {} {}
157 {} {} ({}/{}) {}",
158 SPINNER, PREFIX, ETA, BAR, PCT, POS, LEN, MSG
159 )
160 };
161
162 ProgressStyle::default_bar()
163 .template(&tmp)
164 .expect("template should be fine")
165 .progress_chars("=> ")
166 .tick_chars(r#"|/-\|"#)
167}
168
169const SPINNER: &str = "{spinner:.red.bold}";
170const PREFIX: &str = "{prefix:.cyan.bold}";
171const BYTES: &str = "{bytes}";
172const BYTES_TOTAL: &str = "{total_bytes}";
173const BYTES_PER_SEC: &str = "<{binary_bytes_per_sec:.yellow.bold}>";
174const POS: &str = "{pos}";
175const LEN: &str = "{len}";
176const ETA: &str = "({eta:.green.bold.italic})";
177const BAR: &str = "[{bar:30}]";
178const PCT: &str = "{percent:>03}%";
179const MSG: &str = "{wide_msg:.cyan}";