1use std::io::IsTerminal;
6use std::sync::atomic::{AtomicBool, Ordering};
7
8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, clap::ValueEnum)]
10pub enum ColorChoice {
11 #[default]
12 Auto,
13 #[value(alias = "yes")]
14 Always,
15 #[value(alias = "no")]
16 Never,
17}
18
19impl ColorChoice {
20 pub fn enabled(self) -> bool {
24 match self {
25 Self::Always => true,
26 Self::Never => false,
27 Self::Auto => {
28 std::io::stdout().is_terminal()
29 && std::env::var_os("NO_COLOR").is_none()
30 && std::env::var("TERM").is_ok_and(|term| term != "dumb")
31 }
32 }
33 }
34}
35
36static COLOR: AtomicBool = AtomicBool::new(false);
40
41pub fn set_color(enabled: bool) {
42 COLOR.store(enabled, Ordering::Relaxed);
43}
44
45pub fn color() -> bool {
46 COLOR.load(Ordering::Relaxed)
47}
48
49pub fn styled(text: &str, sgr: &str, color: bool) -> String {
52 if color {
53 format!("\x1b[{sgr}m{text}\x1b[0m")
54 } else {
55 text.to_owned()
56 }
57}
58
59const LABEL_WIDTH: usize = 14;
62
63pub fn field(label: &str, value: impl std::fmt::Display) {
66 println!("{:<LABEL_WIDTH$}{value}", format!("{label}:"));
67}
68
69pub fn subfield(label: &str, value: impl std::fmt::Display) {
71 println!(" {:<width$}{value}", format!("{label}:"), width = 14);
72}
73
74pub fn note(text: impl std::fmt::Display) {
77 println!("note: {text}");
78}
79
80pub fn warn(text: impl std::fmt::Display) {
83 eprintln!("warning: {text}");
84}
85
86pub fn hex(bytes: &[u8]) -> String {
89 use std::fmt::Write as _;
90 let mut text = String::with_capacity(bytes.len() * 2);
91 for byte in bytes {
92 let _ = write!(text, "{byte:02x}");
93 }
94 text
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn color_choice_resolves_the_unconditional_forms() {
103 assert!(ColorChoice::Always.enabled());
104 assert!(!ColorChoice::Never.enabled());
105 }
106
107 #[test]
108 fn styling_is_inert_without_color() {
109 assert_eq!(styled("x", "1;97", false), "x");
110 assert_eq!(styled("x", "1;97", true), "\x1b[1;97mx\x1b[0m");
111 }
112
113 #[test]
114 fn hex_has_no_separators() {
115 assert_eq!(hex(&[0x0a, 0xff]), "0aff");
116 assert_eq!(hex(&[]), "");
117 }
118}