use colored::Colorize;
use std::sync::atomic::{AtomicBool, Ordering};
static JSON_MODE: AtomicBool = AtomicBool::new(false);
pub fn set_json_mode(enabled: bool) {
JSON_MODE.store(enabled, Ordering::Relaxed);
}
pub fn is_json() -> bool {
JSON_MODE.load(Ordering::Relaxed)
}
pub fn print_json(value: &serde_json::Value) {
match serde_json::to_string_pretty(value) {
Ok(s) => println!("{s}"),
Err(_) => println!("{value}"),
}
}
pub fn detail(key: &str, value: &str) {
println!("{:>14} {}", key.bold(), value);
}
pub fn header(text: &str) {
println!("\n{}", text.bold().underline());
}
pub fn success(msg: &str) {
println!("{} {msg}", "✓".green().bold());
}
pub fn warn(msg: &str) {
eprintln!("{} {msg}", "!".yellow().bold());
}
pub fn error(msg: &str) {
eprintln!(" {} {}", "✗".red().bold(), msg.red());
}
pub fn info(msg: &str) {
println!("{} {msg}", "·".blue());
}
pub fn format_time(ts: &str) -> String {
if ts.len() > 19 {
ts[..19].replace('T', " ")
} else {
ts.to_string()
}
}
pub fn colorize_status(status: &str) -> String {
match status.to_lowercase().as_str() {
"open" | "running" | "active" | "online" => status.green().to_string(),
"closed" | "failed" | "error" | "offline" => status.red().to_string(),
"merged" | "passed" | "success" => status.magenta().to_string(),
"pending" | "queued" | "waiting" => status.yellow().to_string(),
"draft" => status.dimmed().to_string(),
_ => status.to_string(),
}
}
pub fn print_table(headers: &[&str], rows: &[Vec<String>]) {
if rows.is_empty() {
println!(" (none)");
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()
.enumerate()
.map(|(i, h)| format!("{:<width$}", h, width = widths[i]))
.collect();
println!(" {}", header_line.join(" ").bold());
for row in rows {
let line: Vec<String> = row
.iter()
.enumerate()
.map(|(i, cell)| {
let w = widths.get(i).copied().unwrap_or(cell.len());
format!("{:<width$}", cell, width = w)
})
.collect();
println!(" {}", line.join(" "));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn json_mode_off_by_default() {
set_json_mode(false);
assert!(!is_json());
}
#[test]
fn set_json_mode_round_trip() {
set_json_mode(true);
assert!(is_json());
set_json_mode(false);
assert!(!is_json());
}
#[test]
fn print_json_pretty_prints() {
let v = serde_json::json!({"foo": "bar", "n": 42, "arr": [1, 2, 3]});
print_json(&v);
let v_array = serde_json::json!([1, 2, 3]);
print_json(&v_array);
let v_null = serde_json::Value::Null;
print_json(&v_null);
}
}