//! Tiny terminal helpers: colored, prefixed output that degrades to plain text //! when piped or when NO_COLOR is set. use std::io::IsTerminal; use std::sync::OnceLock; fn color() -> bool { static ON: OnceLock = OnceLock::new(); *ON.get_or_init(|| std::io::stdout().is_terminal() && std::env::var_os("NO_COLOR").is_none()) } fn paint(code: &str, text: &str) -> String { if color() { format!("\x1b[{code}m{text}\x1b[0m") } else { text.to_string() } } pub fn banner(name: &str, version: &str) { println!("{}", paint("36", &format!("{name} {version}"))); } pub fn info(msg: &str) { println!("{} {}", paint("36", "[info]"), msg); } pub fn ok(msg: &str) { println!("{} {}", paint("32", "[ ok ]"), msg); } pub fn warn(msg: &str) { println!("{} {}", paint("33", "[warn]"), msg); } pub fn error(msg: &str) { eprintln!("{} {}", paint("31", "[fail]"), msg); } pub fn server(line: &str) { println!("{} {}", paint("37", " [server]"), line); } pub fn tunnel(line: &str) { println!("{} {}", paint("35", "[tunnel]"), line); } pub fn tunnel_ok(line: &str) { println!("{} {}", paint("32", "[tunnel]"), line); } pub fn bridge(line: &str) { println!("{} {}", paint("33", "[bridge]"), line); } pub fn bridge_ok(line: &str) { println!("{} {}", paint("32", "[bridge]"), line); } pub fn client(line: &str) { println!("{} {}", paint("34", "[connect]"), line); } pub fn client_ok(line: &str) { println!("{} {}", paint("32", "[connect]"), line); } /// A highlighted, hard-to-miss block for the address players would use. pub fn highlight(lines: &[String]) { let width = lines .iter() .map(|l| l.chars().count()) .max() .unwrap_or(0) .max(40); let bar = paint("36", &"─".repeat(width + 4)); println!("{bar}"); for line in lines { let pad = width - line.chars().count(); println!( "{} {}{}{}", paint("36", "│"), line, " ".repeat(pad), paint("36", "│") ); } println!("{bar}"); }