//! 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<bool> = 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}");
}