1use std::io::{self, Write};
2use std::str::FromStr;
3use std::fmt::Display;
4
5pub struct Colors;
7impl Colors {
8 pub const RESET: &'static str = "\x1b[0m";
9 pub const BOLD: &'static str = "\x1b[1m";
10 pub const DIM: &'static str = "\x1b[2m";
11
12 pub const RED: &'static str = "\x1b[31m";
14 pub const GREEN: &'static str = "\x1b[32m";
15 pub const YELLOW: &'static str = "\x1b[33m";
16 pub const BLUE: &'static str = "\x1b[34m";
17 pub const MAGENTA: &'static str = "\x1b[35m";
18 pub const CYAN: &'static str = "\x1b[36m";
19 pub const WHITE: &'static str = "\x1b[37m";
20
21 pub const BG_RED: &'static str = "\x1b[41m";
23 pub const BG_GREEN: &'static str = "\x1b[42m";
24 pub const BG_YELLOW: &'static str = "\x1b[43m";
25 pub const BG_BLUE: &'static str = "\x1b[44m";
26}
27
28#[derive(Debug, Clone)]
30pub enum SmartValue {
31 Integer(i64),
32 Float(f64),
33 Boolean(bool),
34 Text(String),
35}
36
37impl Display for SmartValue {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 SmartValue::Integer(n) => write!(f, "{}", n),
41 SmartValue::Float(fl) => write!(f, "{}", fl),
42 SmartValue::Boolean(b) => write!(f, "{}", b),
43 SmartValue::Text(s) => write!(f, "{}", s),
44 }
45 }
46}
47
48pub fn input(prompt: &str) -> String {
50 print!("{}{}{}", Colors::CYAN, prompt, Colors::RESET);
51 io::stdout().flush().unwrap();
52
53 let mut input = String::new();
54 io::stdin().read_line(&mut input).expect("Failed to read input");
55 input.trim().to_string()
56}
57
58pub fn input_parse<T>(prompt: &str) -> T
60where
61T: FromStr,
62T::Err: std::fmt::Debug,
63{
64 loop {
65 let input_str = input(prompt);
66 match input_str.parse::<T>() {
67 Ok(value) => return value,
68 Err(_) => {
69 error(&format!("Invalid input '{}'. Please try again.", input_str));
70 }
71 }
72 }
73}
74
75pub fn input_default(prompt: &str, default: &str) -> String {
77 print!("{}{} [default: {}{}{}]: {}",
78 Colors::CYAN, prompt, Colors::DIM, default, Colors::RESET, Colors::RESET);
79 io::stdout().flush().unwrap();
80
81 let mut input = String::new();
82 io::stdin().read_line(&mut input).unwrap();
83 let input = input.trim();
84
85 if input.is_empty() {
86 default.to_string()
87 } else {
88 input.to_string()
89 }
90}
91
92pub fn input_smart(prompt: &str) -> SmartValue {
94 let input_str = input(prompt);
95
96 match input_str.to_lowercase().as_str() {
98 "true" | "yes" | "y" | "1" => return SmartValue::Boolean(true),
99 "false" | "no" | "n" | "0" => return SmartValue::Boolean(false),
100 _ => {}
101 }
102
103 if let Ok(int_val) = input_str.parse::<i64>() {
105 return SmartValue::Integer(int_val);
106 }
107
108 if let Ok(float_val) = input_str.parse::<f64>() {
110 return SmartValue::Float(float_val);
111 }
112
113 SmartValue::Text(input_str)
115}
116
117pub fn input_choice(prompt: &str, choices: &[&str]) -> String {
119 println!("{}{}{}", Colors::CYAN, prompt, Colors::RESET);
120
121 for (i, choice) in choices.iter().enumerate() {
122 println!(" {}{}. {}{}", Colors::YELLOW, i + 1, choice, Colors::RESET);
123 }
124
125 loop {
126 let choice_num: usize = input_parse("Enter choice number: ");
127 if choice_num > 0 && choice_num <= choices.len() {
128 return choices[choice_num - 1].to_string();
129 } else {
130 error(&format!("Please enter a number between 1 and {}", choices.len()));
131 }
132 }
133}
134
135pub fn confirm(prompt: &str) -> bool {
137 loop {
138 let response = input(&format!("{} (y/n): ", prompt));
139 match response.to_lowercase().as_str() {
140 "y" | "yes" | "true" => return true,
141 "n" | "no" | "false" => return false,
142 _ => error("Please enter 'y' or 'n'"),
143 }
144 }
145}
146
147pub fn success(message: &str) {
151 println!("{}{}✓{} {}", Colors::BOLD, Colors::GREEN, Colors::RESET, message);
152}
153
154pub fn error(message: &str) {
156 println!("{}{}✗{} {}", Colors::BOLD, Colors::RED, Colors::RESET, message);
157}
158
159pub fn warning(message: &str) {
161 println!("{}{}⚠{} {}", Colors::BOLD, Colors::YELLOW, Colors::RESET, message);
162}
163
164pub fn info(message: &str) {
166 println!("{}{}ℹ{} {}", Colors::BOLD, Colors::BLUE, Colors::RESET, message);
167}
168
169pub fn highlight(message: &str) {
171 println!("{}{}{}{}", Colors::BOLD, Colors::MAGENTA, message, Colors::RESET);
172}
173
174pub fn print_colored(message: &str, color: &str) {
176 println!("{}{}{}", color, message, Colors::RESET);
177}
178
179pub fn print_banner(title: &str) {
181 let width = title.len() + 4;
182 let border = "═".repeat(width);
183
184 println!("{}{}", Colors::CYAN, Colors::BOLD);
185 println!("╔{}╗", border);
186 println!("║ {} ║", title);
187 println!("╚{}╝", border);
188 println!("{}", Colors::RESET);
189}
190
191pub fn print_divider() {
193 println!("{}{}{}", Colors::DIM, "─".repeat(50), Colors::RESET);
194}
195
196pub fn print_table(data: Vec<Vec<&str>>) {
198 if data.is_empty() {
199 return;
200 }
201
202 let mut col_widths = vec![0; data[0].len()];
204 for row in &data {
205 for (i, cell) in row.iter().enumerate() {
206 col_widths[i] = col_widths[i].max(cell.len());
207 }
208 }
209
210 print!("{}{}┌", Colors::BLUE, Colors::BOLD);
212 for (i, width) in col_widths.iter().enumerate() {
213 print!("{}", "─".repeat(width + 2));
214 if i < col_widths.len() - 1 {
215 print!("┬");
216 }
217 }
218 println!("┐{}", Colors::RESET);
219
220 for (row_idx, row) in data.iter().enumerate() {
222 print!("{}│{}", Colors::BLUE, Colors::RESET);
223 for (i, cell) in row.iter().enumerate() {
224 if row_idx == 0 {
225 print!(" {}{}{:width$} {}│", Colors::BOLD, cell, "", Colors::RESET, width = col_widths[i]);
226 } else {
227 print!(" {:width$} │", cell, width = col_widths[i]);
228 }
229 }
230 println!();
231
232 if row_idx == 0 && data.len() > 1 {
234 print!("{}├", Colors::BLUE);
235 for (i, width) in col_widths.iter().enumerate() {
236 print!("{}", "─".repeat(width + 2));
237 if i < col_widths.len() - 1 {
238 print!("┼");
239 }
240 }
241 println!("┤{}", Colors::RESET);
242 }
243 }
244
245 print!("{}└", Colors::BLUE);
247 for (i, width) in col_widths.iter().enumerate() {
248 print!("{}", "─".repeat(width + 2));
249 if i < col_widths.len() - 1 {
250 print!("┴");
251 }
252 }
253 println!("┘{}", Colors::RESET);
254}
255
256pub fn print_list(items: &[&str]) {
258 for item in items {
259 println!("{}{}•{} {}", Colors::YELLOW, Colors::BOLD, Colors::RESET, item);
260 }
261}
262
263pub fn print_numbered_list(items: &[&str]) {
265 for (i, item) in items.iter().enumerate() {
266 println!("{}{}{}. {}{}", Colors::YELLOW, Colors::BOLD, i + 1, Colors::RESET, item);
267 }
268}
269
270pub struct ProgressBar {
272 total: usize,
273 current: usize,
274 prefix: String,
275}
276
277impl ProgressBar {
278 pub fn new(total: usize) -> Self {
279 Self {
280 total,
281 current: 0,
282 prefix: String::new(),
283 }
284 }
285
286 pub fn with_prefix(total: usize, prefix: &str) -> Self {
287 Self {
288 total,
289 current: 0,
290 prefix: prefix.to_string(),
291 }
292 }
293
294 pub fn update(&mut self, current: usize) {
295 self.current = current;
296 self.draw();
297 }
298
299 pub fn increment(&mut self) {
300 self.current += 1;
301 self.draw();
302 }
303
304 fn draw(&self) {
305 let percentage = if self.total > 0 {
306 (self.current as f64 / self.total as f64 * 100.0) as usize
307 } else {
308 0
309 };
310
311 let filled = percentage / 2; let empty = 50 - filled;
313
314 print!("\r{}{} [{}{}{}] {}%{}",
315 Colors::CYAN,
316 self.prefix,
317 "█".repeat(filled),
318 "░".repeat(empty),
319 Colors::CYAN,
320 percentage,
321 Colors::RESET);
322
323 io::stdout().flush().unwrap();
324 }
325
326 pub fn finish(&self, message: &str) {
327 println!();
328 success(message);
329 }
330}
331
332pub fn loading_spinner(message: &str, duration_ms: u64) {
334 let frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
335 let sleep_duration = std::time::Duration::from_millis(100);
336 let total_iterations = (duration_ms / 100) as usize;
337
338 for i in 0..total_iterations {
339 let frame = frames[i % frames.len()];
340 print!("\r{}{} {}{}", Colors::CYAN, frame, message, Colors::RESET);
341 io::stdout().flush().unwrap();
342 std::thread::sleep(sleep_duration);
343 }
344
345 print!("\r");
346 io::stdout().flush().unwrap();
347}
348
349pub fn print_json_like<T: std::fmt::Debug>(data: &T) {
351 let debug_str = format!("{:#?}", data);
352
353 for line in debug_str.lines() {
354 let trimmed = line.trim();
355
356 if trimmed.contains(':') {
357 let parts: Vec<&str> = trimmed.splitn(2, ':').collect();
358 if parts.len() == 2 {
359 print!(" {}{}{}: {}",
360 Colors::BLUE, parts[0], Colors::RESET, parts[1]);
361 } else {
362 print!(" {}", line);
363 }
364 } else {
365 print!(" {}", line);
366 }
367 println!();
368 }
369}
370
371#[cfg(debug_assertions)]
373pub fn debug(message: &str) {
374 println!("{}{}[DEBUG]{} {}", Colors::DIM, Colors::MAGENTA, Colors::RESET, message);
375}
376
377#[cfg(not(debug_assertions))]
378pub fn debug(_message: &str) {
379 }
381
382pub fn clear_screen() {
384 print!("\x1b[2J\x1b[H");
385 io::stdout().flush().unwrap();
386}
387
388pub fn move_cursor(row: u16, col: u16) {
390 print!("\x1b[{};{}H", row, col);
391 io::stdout().flush().unwrap();
392}