1use console::style;
2use std::fmt;
3
4pub struct BufferedOutput {
6 lines: Vec<String>,
7}
8
9impl BufferedOutput {
10 pub fn new() -> Self {
12 Self { lines: Vec::new() }
13 }
14
15 pub fn add_line(&mut self, line: String) {
17 self.lines.push(line);
18 }
19
20 pub fn add_lines(&mut self, lines: Vec<String>) {
22 self.lines.extend(lines);
23 }
24
25 pub fn add_formatted(&mut self, line: String) {
27 self.lines.push(line);
28 }
29
30 pub fn content(&self) -> String {
32 self.lines.join("\n")
33 }
34
35 pub fn flush(&self) {
37 if !self.lines.is_empty() {
38 println!("{}", self.content());
39 }
40 }
41
42 pub fn flush_err(&self) {
44 if !self.lines.is_empty() {
45 eprintln!("{}", self.content());
46 }
47 }
48
49 pub fn len(&self) -> usize {
51 self.lines.len()
52 }
53
54 pub fn is_empty(&self) -> bool {
56 self.lines.is_empty()
57 }
58}
59
60impl Default for BufferedOutput {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl fmt::Display for BufferedOutput {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 write!(f, "{}", self.lines.join("\n"))
69 }
70}
71
72pub struct Format;
74
75impl Format {
76 pub fn error(msg: &str) -> String {
78 format!("{} {}", style("❌").bold(), msg)
79 }
80
81 pub fn success(msg: &str) -> String {
83 format!("{} {}", style("✅").bold(), msg)
84 }
85
86 pub fn info(msg: &str) -> String {
88 format!("{} {}", style("ℹ️").bold(), msg)
89 }
90
91 pub fn warning(msg: &str) -> String {
93 format!("{} {}", style("⚠️").bold().yellow(), msg)
94 }
95
96 pub fn bold(text: &str) -> String {
98 style(text).bold().to_string()
99 }
100
101 pub fn colored(text: &str, color: console::Color) -> String {
103 style(text).fg(color).to_string()
104 }
105}
106
107pub struct TableFormatter {
109 headers: Vec<String>,
110 rows: Vec<Vec<String>>,
111}
112
113impl TableFormatter {
114 pub fn new(headers: Vec<String>) -> Self {
115 Self {
116 headers,
117 rows: Vec::new(),
118 }
119 }
120
121 pub fn add_row(&mut self, row: Vec<String>) {
122 self.rows.push(row);
123 }
124
125 pub fn format(&self) -> String {
126 if self.rows.is_empty() {
127 return "No data to display".to_string();
128 }
129
130 let mut output = String::new();
131
132 let mut widths: Vec<usize> = self.headers.iter().map(|h| h.len()).collect();
134 for row in &self.rows {
135 for (i, cell) in row.iter().enumerate() {
136 if i < widths.len() {
137 widths[i] = widths[i].max(cell.len());
138 }
139 }
140 }
141
142 for (i, header) in self.headers.iter().enumerate() {
144 if i > 0 {
145 output.push_str(" ");
146 }
147 output.push_str(&format!(
148 "{:<width$}",
149 header,
150 width = widths.get(i).unwrap_or(&0)
151 ));
152 }
153 output.push('\n');
154
155 for (i, width) in widths.iter().enumerate() {
157 if i > 0 {
158 output.push_str(" ");
159 }
160 output.push_str(&"-".repeat(*width));
161 }
162 output.push('\n');
163
164 for row in &self.rows {
166 for (i, cell) in row.iter().enumerate() {
167 if i > 0 {
168 output.push_str(" ");
169 }
170 output.push_str(&format!(
171 "{:<width$}",
172 cell,
173 width = widths.get(i).unwrap_or(&0)
174 ));
175 }
176 output.push('\n');
177 }
178
179 output
180 }
181}
182
183pub struct ProgressIndicator {
185 message: String,
186 current: usize,
187 total: Option<usize>,
188}
189
190impl ProgressIndicator {
191 pub fn new(message: String) -> Self {
192 Self {
193 message,
194 current: 0,
195 total: None,
196 }
197 }
198
199 pub fn with_total(message: String, total: usize) -> Self {
200 Self {
201 message,
202 current: 0,
203 total: Some(total),
204 }
205 }
206
207 pub fn increment(&mut self) {
208 self.current += 1;
209 self.display();
210 }
211
212 pub fn set_current(&mut self, current: usize) {
213 self.current = current;
214 self.display();
215 }
216
217 pub fn finish(&self) {
218 println!("{} ✅ Done", self.message);
219 }
220
221 fn display(&self) {
222 if let Some(total) = self.total {
223 let percentage = (self.current as f64 / total as f64 * 100.0) as usize;
224 print!(
225 "\r{} [{}/{}] {}%",
226 self.message, self.current, total, percentage
227 );
228 } else {
229 print!("\r{} [{}]", self.message, self.current);
230 }
231 use std::io::{self, Write};
233 let _ = io::stdout().flush();
234 }
235}