use colored::{ColoredString, Colorize};
use tabled::builder::Builder;
use tabled::settings::{object::Columns, Alignment, Modify, Style};
pub fn accent(s: &str) -> ColoredString {
s.bright_cyan()
}
pub fn ok(s: &str) -> ColoredString {
s.green()
}
const BOX_MIN_WIDTH: usize = 60;
pub fn box_header(title: &str) {
let inner = format!(" {} ", title);
let width = inner.chars().count().max(BOX_MIN_WIDTH);
let pad = width - inner.chars().count();
println!("\n{}", format!("╭{}╮", "─".repeat(width)).bright_black());
println!(
"{}{}{}{}",
"│".bright_black(),
inner.bold(),
" ".repeat(pad),
"│".bright_black()
);
println!("{}", format!("╰{}╯", "─".repeat(width)).bright_black());
}
pub fn section(name: &str) {
println!("{}", name.bold());
}
pub fn kv(key: &str, value: &str) {
println!(" {:<22} {}", format!("{key}:").dimmed(), value);
}
pub fn tip(msg: &str) {
println!(" {} {}", "•".green(), msg);
}
pub fn warn(msg: &str) {
println!(" {} {}", "!".yellow(), msg.yellow());
}
pub fn bar(frac: f64, width: usize) -> String {
let frac = frac.clamp(0.0, 1.0);
let filled = (frac * width as f64).round() as usize;
let filled = filled.min(width);
format!("{}{}", "█".repeat(filled), "░".repeat(width - filled))
}
pub fn format_num(n: i64) -> String {
if n < 0 {
return format!("-{}", format_num(-n));
}
let s = n.to_string();
let mut result = String::new();
for (i, c) in s.chars().rev().enumerate() {
if i > 0 && i % 3 == 0 {
result.push(',');
}
result.push(c);
}
result.chars().rev().collect()
}
pub fn table(headers: &[&str], rows: &[Vec<String>], right: &[usize]) -> String {
let mut builder = Builder::default();
builder.push_record(headers.iter().map(|h| h.to_string()));
for row in rows {
builder.push_record(row.iter().cloned());
}
let mut t = builder.build();
t.with(Style::rounded());
for &col in right {
t.with(Modify::new(Columns::one(col)).with(Alignment::right()));
}
t.to_string()
}
pub fn print_table(headers: &[&str], rows: &[Vec<String>], right: &[usize]) {
for line in table(headers, rows, right).lines() {
println!(" {line}");
}
}