modcli/output/
progress.rs1use std::io::{stdout, Write};
2use std::thread;
3use std::time::Duration;
4use crossterm::style::{Stylize, Color};
5
6#[derive(Clone)]
8pub struct ProgressStyle {
9 pub fill: char,
10 pub start_cap: char,
11 pub end_cap: char,
12 pub done_label: &'static str,
13 pub show_percent: bool,
14 pub color: Option<Color>,
15}
16
17impl Default for ProgressStyle {
18 fn default() -> Self {
19 Self {
20 fill: '#',
21 start_cap: '[',
22 end_cap: ']',
23 done_label: "Done!",
24 show_percent: true,
25 color: None,
26 }
27 }
28}
29
30pub struct ProgressBar {
32 pub total_steps: usize,
33 pub current: usize,
34 pub label: Option<String>,
35 pub style: ProgressStyle,
36}
37
38impl ProgressBar {
39 pub fn new(total_steps: usize, style: ProgressStyle) -> Self {
40 Self {
41 total_steps,
42 current: 0,
43 label: None,
44 style,
45 }
46 }
47
48 pub fn set_label(&mut self, label: &str) {
49 self.label = Some(label.to_string());
50 }
51
52 pub fn set_progress(&mut self, value: usize) {
53 self.current = value.min(self.total_steps);
54 self.render();
55 }
56
57 pub fn tick(&mut self) {
58 self.current += 1;
59 if self.current > self.total_steps {
60 self.current = self.total_steps;
61 }
62 self.render();
63 }
64
65 pub fn start_auto(&mut self, duration_ms: u64) {
66 let interval = duration_ms / self.total_steps.max(1) as u64;
67 for _ in 0..self.total_steps {
68 self.tick();
69 thread::sleep(Duration::from_millis(interval));
70 }
71 println!(" {}", self.style.done_label);
72 }
73
74 fn render(&self) {
75 let percent = if self.style.show_percent {
76 format!(" {:>3}%", self.current * 100 / self.total_steps.max(1))
77 } else {
78 "".to_string()
79 };
80
81 let fill_count = self.current;
82 let empty_count = self.total_steps - self.current;
83
84 let mut bar = format!(
85 "{}{}{}{}",
86 self.style.start_cap,
87 self.style.fill.to_string().repeat(fill_count),
88 " ".repeat(empty_count),
89 self.style.end_cap
90 );
91
92 if let Some(color) = self.style.color {
93 bar = bar.with(color).to_string();
94 }
95 print!("\r");
96
97 if let Some(ref label) = self.label {
98 print!("{} {}", label, bar);
99 } else {
100 print!("{}", bar);
101 }
102
103 print!("{}", percent);
104 stdout().flush().unwrap();
105 }
106}
107
108pub fn show_progress_bar(label: &str, total_steps: usize, duration_ms: u64) {
111 let mut bar = ProgressBar::new(total_steps, ProgressStyle::default());
112 bar.set_label(label);
113 bar.start_auto(duration_ms);
114}
115
116pub fn show_percent_progress(label: &str, percent: usize) {
117 let clamped = percent.clamp(0, 100);
118 print!("\r{}: {:>3}% complete", label, clamped);
119 stdout().flush().unwrap();
120}
121
122pub fn show_spinner(label: &str, cycles: usize, delay_ms: u64) {
123 let spinner = vec!['|', '/', '-', '\\'];
124 let mut stdout = stdout();
125 print!("{} ", label);
126
127 for i in 0..cycles {
128 let frame = spinner[i % spinner.len()];
129 print!("\r{} {}", label, frame);
130 stdout.flush().unwrap();
131 thread::sleep(Duration::from_millis(delay_ms));
132 }
133
134 println!("{} ✓", label);
135}