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
97pub fn address(bytes: &[u8]) -> String {
102 match <[u8; 32]>::try_from(bytes) {
103 Ok(key) => umsh::core::PublicKey(key).to_string(),
104 Err(_) => hex(bytes),
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn color_choice_resolves_the_unconditional_forms() {
114 assert!(ColorChoice::Always.enabled());
115 assert!(!ColorChoice::Never.enabled());
116 }
117
118 #[test]
119 fn styling_is_inert_without_color() {
120 assert_eq!(styled("x", "1;97", false), "x");
121 assert_eq!(styled("x", "1;97", true), "\x1b[1;97mx\x1b[0m");
122 }
123
124 #[test]
125 fn hex_has_no_separators() {
126 assert_eq!(hex(&[0x0a, 0xff]), "0aff");
127 assert_eq!(hex(&[]), "");
128 }
129
130 #[test]
131 fn addresses_are_base58_and_fall_back_to_hex() {
132 assert_eq!(
133 address(&[0xc4; 32]),
134 umsh::core::PublicKey([0xc4; 32]).to_string()
135 );
136 assert_eq!(address(&[0x0a, 0xff]), "0aff");
137 }
138}