promptio 0.1.0

A simple and beautiful I/O library for Rust CLI applications
Documentation
use std::io::{self, Write};
use std::str::FromStr;
use std::fmt::Display;

// ANSI Color codes for terminal styling
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";

    // Foreground colors
    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";

    // Background colors
    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";
}

// Smart value enum for auto-type detection
#[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),
        }
    }
}

// Basic input function
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()
}

// Input with type parsing
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));
            }
        }
    }
}

// Input with default value
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()
    }
}

// Smart input with automatic type detection
pub fn input_smart(prompt: &str) -> SmartValue {
    let input_str = input(prompt);

    // Try boolean first
    match input_str.to_lowercase().as_str() {
        "true" | "yes" | "y" | "1" => return SmartValue::Boolean(true),
        "false" | "no" | "n" | "0" => return SmartValue::Boolean(false),
        _ => {}
    }

    // Try integer
    if let Ok(int_val) = input_str.parse::<i64>() {
        return SmartValue::Integer(int_val);
    }

    // Try float
    if let Ok(float_val) = input_str.parse::<f64>() {
        return SmartValue::Float(float_val);
    }

    // Default to text
    SmartValue::Text(input_str)
}

// Choice selection
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()));
        }
    }
}

// Confirmation input
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'"),
        }
    }
}

// ============ OUTPUT FUNCTIONS ============

// Success message with green checkmark
pub fn success(message: &str) {
    println!("{}{}{} {}", Colors::BOLD, Colors::GREEN, Colors::RESET, message);
}

// Error message with red X
pub fn error(message: &str) {
    println!("{}{}{} {}", Colors::BOLD, Colors::RED, Colors::RESET, message);
}

// Warning message with yellow triangle
pub fn warning(message: &str) {
    println!("{}{}{} {}", Colors::BOLD, Colors::YELLOW, Colors::RESET, message);
}

// Info message with blue info icon
pub fn info(message: &str) {
    println!("{}{}{} {}", Colors::BOLD, Colors::BLUE, Colors::RESET, message);
}

// Highlighted text
pub fn highlight(message: &str) {
    println!("{}{}{}{}", Colors::BOLD, Colors::MAGENTA, message, Colors::RESET);
}

// Print with custom color
pub fn print_colored(message: &str, color: &str) {
    println!("{}{}{}", color, message, Colors::RESET);
}

// Print banner with decorative border
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);
}

// Print divider
pub fn print_divider() {
    println!("{}{}{}", Colors::DIM, "".repeat(50), Colors::RESET);
}

// Print table
pub fn print_table(data: Vec<Vec<&str>>) {
    if data.is_empty() {
        return;
    }

    // Calculate column widths
    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 header
    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);

    // Print data rows
    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!();

        // Print separator after header
        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 bottom border
    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 bulleted list
pub fn print_list(items: &[&str]) {
    for item in items {
        println!("{}{}{} {}", Colors::YELLOW, Colors::BOLD, Colors::RESET, item);
    }
}

// Print numbered list
pub fn print_numbered_list(items: &[&str]) {
    for (i, item) in items.iter().enumerate() {
        println!("{}{}{}. {}{}", Colors::YELLOW, Colors::BOLD, i + 1, Colors::RESET, item);
    }
}

// Simple progress indicator
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; // 50 chars max
        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);
    }
}

// Loading spinner
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();
}

// Print JSON-like formatted data
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!();
    }
}

// Debug output (only in debug builds)
#[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) {
    // Do nothing in release builds
}

// Clear screen
pub fn clear_screen() {
    print!("\x1b[2J\x1b[H");
    io::stdout().flush().unwrap();
}

// Move cursor
pub fn move_cursor(row: u16, col: u16) {
    print!("\x1b[{};{}H", row, col);
    io::stdout().flush().unwrap();
}