use std::io::{self, Write};
use std::str::FromStr;
use std::fmt::Display;
pub struct Colors;
impl Colors {
pub const RESET: &'static str = "\x1b[0m";
pub const BOLD: &'static str = "\x1b[1m";
pub const DIM: &'static str = "\x1b[2m";
pub const RED: &'static str = "\x1b[31m";
pub const GREEN: &'static str = "\x1b[32m";
pub const YELLOW: &'static str = "\x1b[33m";
pub const BLUE: &'static str = "\x1b[34m";
pub const MAGENTA: &'static str = "\x1b[35m";
pub const CYAN: &'static str = "\x1b[36m";
pub const WHITE: &'static str = "\x1b[37m";
pub const BG_RED: &'static str = "\x1b[41m";
pub const BG_GREEN: &'static str = "\x1b[42m";
pub const BG_YELLOW: &'static str = "\x1b[43m";
pub const BG_BLUE: &'static str = "\x1b[44m";
}
#[derive(Debug, Clone)]
pub enum SmartValue {
Integer(i64),
Float(f64),
Boolean(bool),
Text(String),
}
impl Display for SmartValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SmartValue::Integer(n) => write!(f, "{}", n),
SmartValue::Float(fl) => write!(f, "{}", fl),
SmartValue::Boolean(b) => write!(f, "{}", b),
SmartValue::Text(s) => write!(f, "{}", s),
}
}
}
pub fn input(prompt: &str) -> String {
print!("{}{}{}", Colors::CYAN, prompt, Colors::RESET);
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Failed to read input");
input.trim().to_string()
}
pub fn input_parse<T>(prompt: &str) -> T
where
T: FromStr,
T::Err: std::fmt::Debug,
{
loop {
let input_str = input(prompt);
match input_str.parse::<T>() {
Ok(value) => return value,
Err(_) => {
error(&format!("Invalid input '{}'. Please try again.", input_str));
}
}
}
}
pub fn input_default(prompt: &str, default: &str) -> String {
print!("{}{} [default: {}{}{}]: {}",
Colors::CYAN, prompt, Colors::DIM, default, Colors::RESET, Colors::RESET);
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
let input = input.trim();
if input.is_empty() {
default.to_string()
} else {
input.to_string()
}
}
pub fn input_smart(prompt: &str) -> SmartValue {
let input_str = input(prompt);
match input_str.to_lowercase().as_str() {
"true" | "yes" | "y" | "1" => return SmartValue::Boolean(true),
"false" | "no" | "n" | "0" => return SmartValue::Boolean(false),
_ => {}
}
if let Ok(int_val) = input_str.parse::<i64>() {
return SmartValue::Integer(int_val);
}
if let Ok(float_val) = input_str.parse::<f64>() {
return SmartValue::Float(float_val);
}
SmartValue::Text(input_str)
}
pub fn input_choice(prompt: &str, choices: &[&str]) -> String {
println!("{}{}{}", Colors::CYAN, prompt, Colors::RESET);
for (i, choice) in choices.iter().enumerate() {
println!(" {}{}. {}{}", Colors::YELLOW, i + 1, choice, Colors::RESET);
}
loop {
let choice_num: usize = input_parse("Enter choice number: ");
if choice_num > 0 && choice_num <= choices.len() {
return choices[choice_num - 1].to_string();
} else {
error(&format!("Please enter a number between 1 and {}", choices.len()));
}
}
}
pub fn confirm(prompt: &str) -> bool {
loop {
let response = input(&format!("{} (y/n): ", prompt));
match response.to_lowercase().as_str() {
"y" | "yes" | "true" => return true,
"n" | "no" | "false" => return false,
_ => error("Please enter 'y' or 'n'"),
}
}
}
pub fn success(message: &str) {
println!("{}{}✓{} {}", Colors::BOLD, Colors::GREEN, Colors::RESET, message);
}
pub fn error(message: &str) {
println!("{}{}✗{} {}", Colors::BOLD, Colors::RED, Colors::RESET, message);
}
pub fn warning(message: &str) {
println!("{}{}⚠{} {}", Colors::BOLD, Colors::YELLOW, Colors::RESET, message);
}
pub fn info(message: &str) {
println!("{}{}ℹ{} {}", Colors::BOLD, Colors::BLUE, Colors::RESET, message);
}
pub fn highlight(message: &str) {
println!("{}{}{}{}", Colors::BOLD, Colors::MAGENTA, message, Colors::RESET);
}
pub fn print_colored(message: &str, color: &str) {
println!("{}{}{}", color, message, Colors::RESET);
}
pub fn print_banner(title: &str) {
let width = title.len() + 4;
let border = "═".repeat(width);
println!("{}{}", Colors::CYAN, Colors::BOLD);
println!("╔{}╗", border);
println!("║ {} ║", title);
println!("╚{}╝", border);
println!("{}", Colors::RESET);
}
pub fn print_divider() {
println!("{}{}{}", Colors::DIM, "─".repeat(50), Colors::RESET);
}
pub fn print_table(data: Vec<Vec<&str>>) {
if data.is_empty() {
return;
}
let mut col_widths = vec![0; data[0].len()];
for row in &data {
for (i, cell) in row.iter().enumerate() {
col_widths[i] = col_widths[i].max(cell.len());
}
}
print!("{}{}┌", Colors::BLUE, Colors::BOLD);
for (i, width) in col_widths.iter().enumerate() {
print!("{}", "─".repeat(width + 2));
if i < col_widths.len() - 1 {
print!("┬");
}
}
println!("┐{}", Colors::RESET);
for (row_idx, row) in data.iter().enumerate() {
print!("{}│{}", Colors::BLUE, Colors::RESET);
for (i, cell) in row.iter().enumerate() {
if row_idx == 0 {
print!(" {}{}{:width$} {}│", Colors::BOLD, cell, "", Colors::RESET, width = col_widths[i]);
} else {
print!(" {:width$} │", cell, width = col_widths[i]);
}
}
println!();
if row_idx == 0 && data.len() > 1 {
print!("{}├", Colors::BLUE);
for (i, width) in col_widths.iter().enumerate() {
print!("{}", "─".repeat(width + 2));
if i < col_widths.len() - 1 {
print!("┼");
}
}
println!("┤{}", Colors::RESET);
}
}
print!("{}└", Colors::BLUE);
for (i, width) in col_widths.iter().enumerate() {
print!("{}", "─".repeat(width + 2));
if i < col_widths.len() - 1 {
print!("┴");
}
}
println!("┘{}", Colors::RESET);
}
pub fn print_list(items: &[&str]) {
for item in items {
println!("{}{}•{} {}", Colors::YELLOW, Colors::BOLD, Colors::RESET, item);
}
}
pub fn print_numbered_list(items: &[&str]) {
for (i, item) in items.iter().enumerate() {
println!("{}{}{}. {}{}", Colors::YELLOW, Colors::BOLD, i + 1, Colors::RESET, item);
}
}
pub struct ProgressBar {
total: usize,
current: usize,
prefix: String,
}
impl ProgressBar {
pub fn new(total: usize) -> Self {
Self {
total,
current: 0,
prefix: String::new(),
}
}
pub fn with_prefix(total: usize, prefix: &str) -> Self {
Self {
total,
current: 0,
prefix: prefix.to_string(),
}
}
pub fn update(&mut self, current: usize) {
self.current = current;
self.draw();
}
pub fn increment(&mut self) {
self.current += 1;
self.draw();
}
fn draw(&self) {
let percentage = if self.total > 0 {
(self.current as f64 / self.total as f64 * 100.0) as usize
} else {
0
};
let filled = percentage / 2; let empty = 50 - filled;
print!("\r{}{} [{}{}{}] {}%{}",
Colors::CYAN,
self.prefix,
"█".repeat(filled),
"░".repeat(empty),
Colors::CYAN,
percentage,
Colors::RESET);
io::stdout().flush().unwrap();
}
pub fn finish(&self, message: &str) {
println!();
success(message);
}
}
pub fn loading_spinner(message: &str, duration_ms: u64) {
let frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
let sleep_duration = std::time::Duration::from_millis(100);
let total_iterations = (duration_ms / 100) as usize;
for i in 0..total_iterations {
let frame = frames[i % frames.len()];
print!("\r{}{} {}{}", Colors::CYAN, frame, message, Colors::RESET);
io::stdout().flush().unwrap();
std::thread::sleep(sleep_duration);
}
print!("\r");
io::stdout().flush().unwrap();
}
pub fn print_json_like<T: std::fmt::Debug>(data: &T) {
let debug_str = format!("{:#?}", data);
for line in debug_str.lines() {
let trimmed = line.trim();
if trimmed.contains(':') {
let parts: Vec<&str> = trimmed.splitn(2, ':').collect();
if parts.len() == 2 {
print!(" {}{}{}: {}",
Colors::BLUE, parts[0], Colors::RESET, parts[1]);
} else {
print!(" {}", line);
}
} else {
print!(" {}", line);
}
println!();
}
}
#[cfg(debug_assertions)]
pub fn debug(message: &str) {
println!("{}{}[DEBUG]{} {}", Colors::DIM, Colors::MAGENTA, Colors::RESET, message);
}
#[cfg(not(debug_assertions))]
pub fn debug(_message: &str) {
}
pub fn clear_screen() {
print!("\x1b[2J\x1b[H");
io::stdout().flush().unwrap();
}
pub fn move_cursor(row: u16, col: u16) {
print!("\x1b[{};{}H", row, col);
io::stdout().flush().unwrap();
}