use std::fmt;
pub fn print_success(msg: &str) {
eprintln!("[ok] {msg}");
}
pub fn print_error(msg: &str) {
eprintln!("[error] {msg}");
}
pub fn print_warn(msg: &str) {
eprintln!("[warn] {msg}");
}
pub fn print_info(msg: &str) {
eprintln!("[info] {msg}");
}
pub fn print_kv(key: &str, value: &dyn fmt::Display) {
eprintln!(" {key:<16} {value}");
}
pub fn print_table(headers: &[&str], rows: &[Vec<String>]) {
if headers.is_empty() {
return;
}
let mut widths: Vec<usize> = headers.iter().map(|h| h.len()).collect();
for row in rows {
for (i, cell) in row.iter().enumerate() {
if i < widths.len() {
widths[i] = widths[i].max(cell.len());
}
}
}
let header_line: Vec<String> = headers
.iter()
.zip(&widths)
.map(|(h, w)| format!("{h:<w$}"))
.collect();
eprintln!(" {}", header_line.join(" "));
let sep_line: Vec<String> = widths.iter().map(|w| "-".repeat(*w)).collect();
eprintln!(" {}", sep_line.join(" "));
for row in rows {
let cells: Vec<String> = row
.iter()
.zip(&widths)
.map(|(c, w)| format!("{c:<w$}"))
.collect();
eprintln!(" {}", cells.join(" "));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_print_table_empty_headers() {
print_table(&[], &[]);
}
#[test]
fn test_print_table_formats() {
let headers = &["Name", "Value"];
let rows = vec![
vec!["key1".to_string(), "val1".to_string()],
vec!["longer_key".to_string(), "v".to_string()],
];
print_table(headers, &rows);
}
}