Skip to main content

easy_io/
lib.rs

1use std::io::{self, Write};
2use std::str::FromStr;
3use std::fmt::Display;
4
5// ANSI Color codes for terminal styling
6pub 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    // Foreground colors
13    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    // Background colors
22    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// Smart value enum for auto-type detection
29#[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
48// Basic input function
49pub 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
58// Input with type parsing
59pub 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
75// Input with default value
76pub 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
92// Smart input with automatic type detection
93pub fn input_smart(prompt: &str) -> SmartValue {
94    let input_str = input(prompt);
95
96    // Try boolean first
97    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    // Try integer
104    if let Ok(int_val) = input_str.parse::<i64>() {
105        return SmartValue::Integer(int_val);
106    }
107
108    // Try float
109    if let Ok(float_val) = input_str.parse::<f64>() {
110        return SmartValue::Float(float_val);
111    }
112
113    // Default to text
114    SmartValue::Text(input_str)
115}
116
117// Choice selection
118pub 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
135// Confirmation input
136pub 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
147// ============ OUTPUT FUNCTIONS ============
148
149// Success message with green checkmark
150pub fn success(message: &str) {
151    println!("{}{}✓{} {}", Colors::BOLD, Colors::GREEN, Colors::RESET, message);
152}
153
154// Error message with red X
155pub fn error(message: &str) {
156    println!("{}{}✗{} {}", Colors::BOLD, Colors::RED, Colors::RESET, message);
157}
158
159// Warning message with yellow triangle
160pub fn warning(message: &str) {
161    println!("{}{}⚠{} {}", Colors::BOLD, Colors::YELLOW, Colors::RESET, message);
162}
163
164// Info message with blue info icon
165pub fn info(message: &str) {
166    println!("{}{}ℹ{} {}", Colors::BOLD, Colors::BLUE, Colors::RESET, message);
167}
168
169// Highlighted text
170pub fn highlight(message: &str) {
171    println!("{}{}{}{}", Colors::BOLD, Colors::MAGENTA, message, Colors::RESET);
172}
173
174// Print with custom color
175pub fn print_colored(message: &str, color: &str) {
176    println!("{}{}{}", color, message, Colors::RESET);
177}
178
179// Print banner with decorative border
180pub 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
191// Print divider
192pub fn print_divider() {
193    println!("{}{}{}", Colors::DIM, "─".repeat(50), Colors::RESET);
194}
195
196// Print table
197pub fn print_table(data: Vec<Vec<&str>>) {
198    if data.is_empty() {
199        return;
200    }
201
202    // Calculate column widths
203    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 header
211    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    // Print data rows
221    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        // Print separator after header
233        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 bottom border
246    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
256// Print bulleted list
257pub fn print_list(items: &[&str]) {
258    for item in items {
259        println!("{}{}•{} {}", Colors::YELLOW, Colors::BOLD, Colors::RESET, item);
260    }
261}
262
263// Print numbered list
264pub 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
270// Simple progress indicator
271pub 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; // 50 chars max
312        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
332// Loading spinner
333pub 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
349// Print JSON-like formatted data
350pub 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// Debug output (only in debug builds)
372#[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    // Do nothing in release builds
380}
381
382// Clear screen
383pub fn clear_screen() {
384    print!("\x1b[2J\x1b[H");
385    io::stdout().flush().unwrap();
386}
387
388// Move cursor
389pub fn move_cursor(row: u16, col: u16) {
390    print!("\x1b[{};{}H", row, col);
391    io::stdout().flush().unwrap();
392}