scirs2_core/logging/progress/
renderer.rs1use std::io::{self, Write};
7
8use super::statistics::{format_duration, format_rate, ProgressStats};
9use super::tracker::ProgressSymbols;
10
11pub struct ProgressRenderer {
13 last_length: usize,
15 spinner_index: usize,
17}
18
19impl ProgressRenderer {
20 pub fn new() -> Self {
22 Self {
23 last_length: 0,
24 spinner_index: 0,
25 }
26 }
27
28 pub fn init(&mut self) {
30 print!("\x1b[?25l");
32 let _ = io::stdout().flush();
33 }
34
35 pub fn finalize(&mut self) {
37 print!("\x1b[?25h");
39 println!();
40 let _ = io::stdout().flush();
41 }
42
43 pub fn renderpercentage(&self, description: &str, stats: &ProgressStats) {
45 let output = format!(
46 "{description}: {percentage:.1}%",
47 percentage = stats.percentage
48 );
49 self.print_progress(&output);
50 }
51
52 pub fn render_basic(
54 &self,
55 description: &str,
56 stats: &ProgressStats,
57 width: usize,
58 show_eta: bool,
59 symbols: &ProgressSymbols,
60 ) {
61 let percentage = stats.percentage;
62 let filled_width = ((percentage / 100.0) * width as f64) as usize;
63 let empty_width = width.saturating_sub(filled_width);
64
65 let progress_bar = format!(
66 "{}{}{}{}",
67 symbols.start,
68 symbols.fill.repeat(filled_width),
69 symbols.empty.repeat(empty_width),
70 symbols.end,
71 );
72
73 let mut output = format!("{description}: {progress_bar} {percentage:.1}%");
74
75 if show_eta && stats.processed < stats.total {
76 output.push_str(&format!(" eta: {}", format_duration(&stats.eta)));
77 }
78
79 self.print_progress(&output);
80 }
81
82 pub fn render_spinner(
84 &mut self,
85 description: &str,
86 stats: &ProgressStats,
87 show_eta: bool,
88 symbols: &ProgressSymbols,
89 ) {
90 self.spinner_index = (self.spinner_index + 1) % symbols.spinner.len();
91 let spinner = &symbols.spinner[self.spinner_index];
92
93 let mut output = format!(
94 "{} {} {}/{} ({:.1}%)",
95 spinner, description, stats.processed, stats.total, stats.percentage
96 );
97
98 if show_eta && stats.processed < stats.total {
99 output.push_str(&format!(" eta: {}", format_duration(&stats.eta)));
100 }
101
102 self.print_progress(&output);
103 }
104
105 pub fn render_detailed(
107 &self,
108 description: &str,
109 stats: &ProgressStats,
110 width: usize,
111 show_speed: bool,
112 show_eta: bool,
113 show_statistics: bool,
114 symbols: &ProgressSymbols,
115 ) {
116 let percentage = stats.percentage;
117 let filled_width = ((percentage / 100.0) * width as f64) as usize;
118 let empty_width = width.saturating_sub(filled_width);
119
120 let progress_bar = format!(
121 "{}{}{}{}",
122 symbols.start,
123 symbols.fill.repeat(filled_width),
124 symbols.empty.repeat(empty_width),
125 symbols.end,
126 );
127
128 let mut output = format!(
129 "{}: {} {:.1}% ({}/{})",
130 description, progress_bar, percentage, stats.processed, stats.total
131 );
132
133 if show_speed {
134 output.push_str(&format!(
135 " [{rate}]",
136 rate = format_rate(stats.items_per_second)
137 ));
138 }
139
140 if show_eta && stats.processed < stats.total {
141 output.push_str(&format!(" eta: {}", format_duration(&stats.eta)));
142 }
143
144 if show_statistics {
145 output.push_str(&format!(
146 " | Elapsed: {elapsed}",
147 elapsed = format_duration(&stats.elapsed)
148 ));
149
150 if stats.max_speed > 0.0 {
151 output.push_str(&format!(
152 " | Peak: {peak}",
153 peak = format_rate(stats.max_speed)
154 ));
155 }
156 }
157
158 self.print_progress(&output);
159 }
160
161 pub fn render_compact(&self, description: &str, stats: &ProgressStats) {
163 let output = format!(
164 "{}: {}/{} ({:.1}%) - {} - ETA: {}",
165 description,
166 stats.processed,
167 stats.total,
168 stats.percentage,
169 format_rate(stats.items_per_second),
170 format_duration(&stats.eta)
171 );
172 self.print_progress(&output);
173 }
174
175 fn print_progress(&self, output: &str) {
177 let output_width = console_width(output);
179
180 if self.last_length > output_width {
182 let clear_length = self.last_length - output_width;
183 print!("\r{}{}", output, " ".repeat(clear_length));
184 } else {
185 print!("\r{output}");
186 }
187
188 let _ = io::stdout().flush();
189 }
190
191 pub fn update_length(&mut self, length: usize) {
193 self.last_length = length;
194 }
195}
196
197impl Default for ProgressRenderer {
198 fn default() -> Self {
199 Self::new()
200 }
201}
202
203#[allow(dead_code)]
205fn console_width(s: &str) -> usize {
206 s.chars().count()
209}
210
211#[allow(dead_code)]
213pub fn colored_progress_bar(percentage: f64, width: usize) -> String {
214 let filled_width = ((percentage / 100.0) * width as f64) as usize;
215 let empty_width = width.saturating_sub(filled_width);
216
217 let color = if percentage >= 90.0 {
219 "\x1b[32m" } else if percentage >= 50.0 {
221 "\x1b[33m" } else {
223 "\x1b[31m" };
225
226 let reset = "\x1b[0m";
227
228 format!(
229 "│{}{}{}{}│",
230 color,
231 "█".repeat(filled_width),
232 reset,
233 " ".repeat(empty_width)
234 )
235}
236
237#[allow(dead_code)]
239pub fn ascii_art_progress(percentage: f64) -> String {
240 let blocks = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"];
241 let width = 20;
242 let progress = percentage / 100.0 * width as f64;
243 let full_blocks = progress.floor() as usize;
244 let partial_block = ((progress - progress.floor()) * (blocks.len() - 1) as f64) as usize;
245
246 let mut result = String::new();
247 result.push_str(&"█".repeat(full_blocks));
248
249 if full_blocks < width && partial_block > 0 {
250 result.push_str(blocks[partial_block]);
251 }
252
253 let remaining = width - full_blocks - if partial_block > 0 { 1 } else { 0 };
254 result.push_str(&" ".repeat(remaining));
255
256 format!("│{result}│")
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 #[test]
264 fn test_renderer_creation() {
265 let renderer = ProgressRenderer::new();
266 assert_eq!(renderer.last_length, 0);
267 assert_eq!(renderer.spinner_index, 0);
268 }
269
270 #[test]
271 fn test_colored_progress_bar() {
272 let bar_low = colored_progress_bar(25.0, 10);
273 assert!(bar_low.contains("\x1b[31m")); let bar_high = colored_progress_bar(95.0, 10);
276 assert!(bar_high.contains("\x1b[32m")); }
278
279 #[test]
280 fn test_ascii_art_progress() {
281 let art = ascii_art_progress(50.0);
282 assert!(art.contains("│"));
283 assert!(art.contains("█"));
284 }
285
286 #[test]
287 fn test_console_width() {
288 assert_eq!(console_width("hello"), 5);
289 assert_eq!(console_width("test 123"), 8);
290 }
291}