use colored::*;
use once_cell::sync::Lazy;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::find_similar;
static DEBUG_ENABLED: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false));
pub fn enable_debug() {
DEBUG_ENABLED.store(true, Ordering::Relaxed);
}
pub fn disable_debug() {
DEBUG_ENABLED.store(false, Ordering::Relaxed);
}
pub fn is_debug_enabled() -> bool {
DEBUG_ENABLED.load(Ordering::Relaxed)
}
pub fn debug_print(label: &str, message: &str) {
if is_debug_enabled() {
eprintln!("{} {}: {}",
"[DEBUG]".bright_magenta().bold(),
label.cyan(),
message.white()
);
}
}
pub fn debug_struct<T: std::fmt::Debug>(label: &str, data: &T) {
if is_debug_enabled() {
eprintln!("{} {}:\n{:#?}",
"[DEBUG]".bright_magenta().bold(),
label.cyan(),
data
);
}
}
#[derive(Clone)]
pub struct TableStyle {
pub header_color: Color,
pub border_color: Color,
pub padding: usize,
pub show_borders: bool,
}
impl Default for TableStyle {
fn default() -> Self {
Self {
header_color: Color::Blue,
border_color: Color::White,
padding: 2,
show_borders: true,
}
}
}
pub fn print_table(
headers: &[&str],
rows: &[Vec<&str>],
style: Option<TableStyle>,
) {
let style = style.unwrap_or_default();
for (idx, row) in rows.iter().enumerate() {
if row.len() != headers.len() {
panic!(
"Row {} has {} columns, but headers have {}",
idx, row.len(), headers.len()
);
}
}
let mut col_widths: Vec<usize> = headers.iter()
.map(|h| h.len())
.collect();
for row in rows {
for (idx, cell) in row.iter().enumerate() {
col_widths[idx] = col_widths[idx].max(cell.len());
}
}
print_table_row(headers, &col_widths, style.padding, Some(style.header_color));
if style.show_borders {
print_separator(&col_widths, style.padding, style.border_color);
}
for row in rows {
print_table_row(row, &col_widths, style.padding, None);
}
}
fn print_table_row(cells: &[&str], widths: &[usize], padding: usize, color: Option<Color>) {
let pad = " ".repeat(padding);
print!("{}", pad);
for (idx, cell) in cells.iter().enumerate() {
let formatted = format!("{:<width$}", cell, width = widths[idx]);
if let Some(c) = color {
print!("{}", formatted.color(c));
} else {
print!("{}", formatted);
}
if idx < cells.len() - 1 {
print!(" | ");
}
}
println!();
}
fn print_separator(widths: &[usize], padding: usize, color: Color) {
let pad = " ".repeat(padding);
print!("{}", pad);
for (idx, width) in widths.iter().enumerate() {
print!("{}", "─".repeat(*width).color(color));
if idx < widths.len() - 1 {
print!("─┼─");
}
}
println!();
}
pub fn print_section(title: &str) {
println!("\n{}", title.bold().blue());
}
pub fn print_usage(app_name: &str, pattern: &str) {
println!("{}: {} {}",
"Usage".bold().yellow(),
app_name.green(),
pattern.white()
);
}
pub fn print_app_header(name: &str, version: &str, description: &str) {
println!("{}: {}", "Name".bold().green(), name);
println!("{}: {}", "Version".bold().green(), version);
println!("{}: {}", "Description".bold().blue(), description);
}
pub fn print_item_list(items: &[(&str, &str)], title: Option<&str>) {
if let Some(t) = title {
print_section(t);
}
let max_width = items.iter()
.map(|(name, _)| name.len())
.max()
.unwrap_or(0);
for (name, desc) in items {
println!(" {:<width$} {}",
name.cyan(),
desc.white(),
width = max_width
);
}
}
pub fn print_error(message: &str) {
eprintln!("{} {}", "Error:".bold().red(), message.red());
}
pub fn print_error_detailed(title: &str, message: &str, hint: Option<&str>) {
eprintln!();
eprintln!("{}", "═".repeat(60).red());
eprintln!("{} {}", "ERROR:".bold().red(), title.bright_red());
eprintln!("{}", "═".repeat(60).red());
eprintln!();
eprintln!(" {}", message.red());
if let Some(h) = hint {
eprintln!();
eprintln!("{} {}", "Hint:".bold().yellow(), h.white());
}
eprintln!();
eprintln!("{}", "═".repeat(60).red());
eprintln!();
}
pub fn print_success(message: &str) {
println!("{} {}", "✓".bold().green(), message.green());
}
pub fn print_info(message: &str) {
println!("{} {}", "ℹ".bold().blue(), message.white());
}
pub fn print_warning(message: &str) {
eprintln!("{} {}", "⚠".bold().yellow(), message.yellow());
}
pub fn print_suggestions(unknown: &str, suggestions: &[String]) {
if suggestions.is_empty() {
return;
}
eprintln!();
eprintln!("{} '{}'", "Unknown command:".red(), unknown.bright_red());
eprintln!();
eprintln!("{}", "Did you mean:".bold().yellow());
for suggestion in suggestions {
eprintln!(" {} {}", "•".cyan(), suggestion.green());
}
eprintln!();
}
pub fn print_progress(step: usize, total: usize, message: &str) {
let percentage = (step as f64 / total as f64 * 100.0) as usize;
let filled = percentage / 5;
let empty = 20 - filled;
print!("\r{} [{}{}>] {}% - {}",
"Progress:".bold().blue(),
"█".repeat(filled).green(),
" ".repeat(empty),
percentage,
message
);
if step == total {
println!(); }
}
pub fn print_box(text: &str, color: Color) {
let width = text.len() + 4;
let top = format!("┌{}┐", "─".repeat(width - 2));
let middle = format!("│ {} │", text);
let bottom = format!("└{}┘", "─".repeat(width - 2));
println!("{}", top.color(color));
println!("{}", middle.color(color).bold());
println!("{}", bottom.color(color));
}
pub fn print_divider(width: usize, style: char, color: Option<Color>) {
let line = style.to_string().repeat(width);
if let Some(c) = color {
println!("{}", line.color(c));
} else {
println!("{}", line);
}
}
pub fn print_key_value(pairs: &[(&str, &str)]) {
let max_key_width = pairs.iter()
.map(|(k, _)| k.len())
.max()
.unwrap_or(0);
for (key, value) in pairs {
println!(" {:<width$}: {}",
key.bold().cyan(),
value.white(),
width = max_key_width
);
}
}
pub fn print_did_you_mean(unknown: &str, available: &[String]) {
let suggestions = find_similar(unknown, available, 2);
let suggestion_vec: Vec<String> = suggestions.into_iter().cloned().collect();
print_suggestions(unknown, &suggestion_vec);
}