debug_et_diagnostics/
color.rs

1use std::fmt::Display;
2
3const DEFAULT_COLUMNS: usize = 130;
4
5/// reset the ANSI colors of the given test
6pub fn reset<T: Display>(text: T) -> String {
7    format!("{}\x1b[0m", text)
8}
9
10/// colorize the foreground of the given text without resetting (ANSI 256 colors)
11pub fn fg<T: Display>(text: T, fg: usize) -> String {
12    format!("\x1b[1;38;5;{}m{}", wrap(fg), text)
13}
14/// colorize the background of the given text without resetting (ANSI 256 colors)
15pub fn bg<T: Display>(text: T, bg: usize) -> String {
16    format!("\x1b[1;48;5;{}m{}", wrap(bg), text)
17}
18/// colorize the foreground and backrground of the given text without resetting
19///
20/// > shorthand combination of [bg] and [fg]
21pub fn bgfg<T: Display>(text: T, fore: usize, back: usize) -> String {
22    bg(fg(text, wrap(fore) as usize), wrap(back) as usize)
23}
24/// colorize the foreground and backrground of the given text and reset the colors afterwards
25pub fn ansi<T: Display>(text: T, fore: usize, back: usize) -> String {
26    reset(bgfg(text, fore as usize, back as usize))
27}
28/// pad text by the number of columns determined by [term_cols]
29pub fn pad_columns<T: Display>(text: T) -> String {
30    let text = text.to_string();
31    let cols = term_cols();
32    pad(text, cols)
33}
34/// pad text
35pub fn pad<T: Display>(text: T, length: usize) -> String {
36    let text = text.to_string();
37    let len = text
38        .as_bytes()
39        .iter()
40        .map(|c| char::from(*c))
41        .map(|c| {
42            u32::from(c)
43                .to_ne_bytes()
44                .iter()
45                .map(Clone::clone)
46                .filter(|c| *c > 0)
47                .collect::<Vec<u8>>()
48        })
49        .flatten()
50        .count();
51
52    format!(
53        "{}{}",
54        text,
55        " ".repeat(if length > len {
56            length - len
57        } else if len < length {
58            0
59        } else {
60            0
61        })
62    )
63}
64/// clear the screen
65pub fn ansi_clear() -> String {
66    "\x1b[2J\x1b[3J\x1b[H".to_string()
67}
68/// colorize the foreground of the given text and reset afterwards
69pub fn fore<T: Display>(text: T, fore: usize) -> String {
70    let (fore, back) = couple(fore);
71    ansi(text, fore as usize, back as usize)
72}
73/// colorize the backrground of the given text and reset afterwards
74pub fn back<T: Display>(text: T, back: usize) -> String {
75    let (back, fore) = couple(back);
76    ansi(text, fore as usize, back as usize)
77}
78/// auto-colorize the given text with the color determined by [from_display]
79pub fn auto<T: Display>(word: T) -> String {
80    fore(
81        word.to_string(),
82        bright(
83            u8::from_str_radix(&word.to_string(), 10)
84                .unwrap_or_else(|_| from_display(word.to_string()))
85                .into(),
86        )
87        .into(),
88    )
89}
90/// auto-colorize the underlying bytes of given text with the color determined by [from_bytes]
91pub fn from_display<T: Display>(word: T) -> u8 {
92    from_bytes(word.to_string().as_bytes())
93}
94/// determine a triple of RGB colors of a string determined by [rgb_from_bytes]
95pub fn rgb_from_display<T: Display>(word: T) -> [u8; 3] {
96    rgb_from_bytes(word.to_string().as_bytes())
97}
98
99/// determine an ANSI-256 color determined by [rgb_from_bytes]
100pub fn from_bytes(bytes: &[u8]) -> u8 {
101    let mut color: u8 = 0;
102    for rgb in rgb_from_bytes(bytes) {
103        color ^= rgb
104    }
105    color
106}
107/// simple and naive algorithm to determine a triple of RGB colors
108/// based on XOR'ing the given slice of bytes;
109pub fn rgb_from_bytes(bytes: &[u8]) -> [u8; 3] {
110    let mut color: [u8; 3] = [0, 0, 0];
111    let mut iter = 0;
112    while iter < 3 {
113        for (index, byte) in bytes.iter().enumerate() {
114            color[index % 3] ^= *byte
115        }
116        iter += 1;
117    }
118    color
119}
120
121/// returns a tuple of (foreground, backrground) color by taking one
122/// unsigned integer, wrapping that around [u8::MAX] to determine the
123/// foreground color then uses [invert_bw] to
124/// determine the background color.
125pub fn couple(color: usize) -> (u8, u8) {
126    let fore = bright(wrap(color));
127    let back = invert_bw(fore);
128    (fore, back)
129}
130
131/// naive heuristic to return the brightest opposite of the given color.
132pub fn invert_bw(color: u8) -> u8 {
133    match color {
134        0 | 8 | 16..21 | 52..61 | 88..93 | 232..239 => 231,
135        _ => 16,
136    }
137}
138/// naive heuristic to return a brighter color near the given one.
139pub fn bright(color: u8) -> u8 {
140    match color {
141        0 | 8 => color + 100,
142        16..21 => color + 100,
143        52..61 => color + 40,
144        88..93 => color + 50,
145        232..239 => 249,
146        _ => color,
147    }
148}
149
150/// wraps the given usize via remainder
151pub fn wrap(color: usize) -> u8 {
152    (if color > 0 { color % 255 } else { color }) as u8
153}
154
155/// naive function for unix terminals that calls stty to determine the
156/// number of columns of the terminal.
157///
158/// The rationale of the approach is to avoid linking to libc in order
159/// to do ioctl FFI calls and keeping the rust crate lightweight.
160///
161/// This function might be rewritten using a more sophisticated
162/// approach in the future.
163fn io_term_cols() -> std::io::Result<usize> {
164    if let Ok(cols) = std::env::var("COLUMNS") {
165        if let Ok(cols) = usize::from_str_radix(&cols, 10) {
166            return Ok(cols);
167        }
168    }
169    use std::process::{Command, Stdio};
170    let mut cmd = Command::new("/bin/stty");
171    let cmd = cmd.args(vec!["-a"]);
172    let cmd = cmd.stdin(Stdio::inherit());
173    let cmd = cmd.stdout(Stdio::piped());
174    let cmd = cmd.stderr(Stdio::piped());
175    let child = cmd.spawn()?;
176    let output = child.wait_with_output()?;
177    let lines = String::from_utf8_lossy(&output.stdout)
178        .to_string()
179        .lines()
180        .map(String::from)
181        .collect::<Vec<String>>();
182    let lines = lines[0]
183        .split(';')
184        .map(String::from)
185        .collect::<Vec<String>>();
186    if lines.len() > 2 {
187        let fields = lines[2]
188            .split(' ')
189            .map(String::from)
190            .collect::<Vec<String>>();
191        if let Ok(cols) = usize::from_str_radix(&fields[1], 10) {
192            return Ok(cols);
193        }
194    }
195    Ok(DEFAULT_COLUMNS)
196}
197
198/// tries to obtain the number of columns of the terminal via
199/// [io_term_cols] and falls back to
200/// [DEFAULT_COLUMNS] in case of error.
201pub fn term_cols() -> usize {
202    io_term_cols().unwrap_or_else(|_| DEFAULT_COLUMNS)
203}
204
205/// determine an ANSI-256 color determined by [`from_bytes(&[byte])`]
206pub fn from_byte(byte: u8) -> u8 {
207    from_bytes(&[byte]).into()
208}
209
210/// auto-colorize the given byte with the color determined by [from_byte]
211pub fn byte(byte: u8) -> String {
212    let (fg, bg) = couple(from_byte(byte).into());
213    ansi(byte, fg as usize, bg as usize)
214}
215
216/// auto-colorize the given byte in hex format with the color determined by [from_byte]
217pub fn byte_hex(byte: u8) -> String {
218    let (fg, bg) = couple(from_byte(byte).into());
219    ansi(format!("0x{byte:02x}"), fg as usize, bg as usize)
220}
221
222/// auto-colorize the given byte in bin format with the color determined by [from_byte]
223pub fn byte_bin(byte: u8) -> String {
224    let (fg, bg) = couple(from_byte(byte).into());
225    ansi(format!("0b{byte:08b}"), fg as usize, bg as usize)
226}