debug_et_diagnostics/
color.rs

1use std::fmt::Display;
2
3const DEFAULT_COLUMNS: usize = 130;
4
5
6/// reset the ANSI colors of the given test
7pub fn reset<T: Display>(text: T) -> String {
8    format!("{}\x1b[0m", text)
9}
10
11/// colorize the foreground of the given text without resetting (ANSI 256 colors)
12pub fn fg<T: Display>(text: T, fg: usize) -> String {
13    format!("\x1b[1;38;5;{}m{}", wrap(fg), text)
14}
15/// colorize the background of the given text without resetting (ANSI 256 colors)
16pub fn bg<T: Display>(text: T, bg: usize) -> String {
17    format!("\x1b[1;48;5;{}m{}", wrap(bg), text)
18}
19/// colorize the foreground and backrground of the given text without resetting
20///
21/// > shorthand combination of [bg] and [fg]
22pub fn bgfg<T: Display>(text: T, fore: usize, back: usize) -> String {
23    bg(fg(text, wrap(fore) as usize), wrap(back) as usize)
24}
25/// colorize the foreground and backrground of the given text and reset the colors afterwards
26pub fn ansi<T: Display>(text: T, fore: usize, back: usize) -> String {
27    reset(bgfg(text, fore as usize, back as usize))
28}
29/// pad text by the number of columns determined by [term_cols]
30pub fn pad_columns<T: Display>(text: T) -> String {
31    let text = text.to_string();
32    let len = text.len();
33    let cols = term_cols();
34    format!(
35        "{}{}",
36        text,
37        " ".repeat(if cols > len { cols - len } else { 0 })
38    )
39}
40/// clear the screen
41pub fn ansi_clear() -> String {
42    "\x1b[2J\x1b[3J\x1b[H".to_string()
43}
44/// colorize the foreground of the given text and reset afterwards
45pub fn fore<T: Display>(text: T, fore: usize) -> String {
46    let (fore, back) = couple(fore);
47    ansi(text, fore as usize, back as usize)
48}
49/// colorize the backrground of the given text and reset afterwards
50pub fn back<T: Display>(text: T, back: usize) -> String {
51    let (back, fore) = couple(back);
52    ansi(text, fore as usize, back as usize)
53}
54/// auto-colorize the given text with the color determined by [from_string]
55pub fn auto<T: Display>(word: T) -> String {
56    fore(
57        word.to_string(),
58        u8::from_str_radix(&word.to_string(), 10)
59            .unwrap_or_else(|_| from_string(word.to_string()))
60            .into(),
61    )
62}
63/// auto-colorize the underlying bytes of given text with the color determined by [from_bytes]
64pub fn from_string<T: Display>(word: T) -> u8 {
65    from_bytes(word.to_string().as_bytes())
66}
67/// determine a triple of RGB colors of a string determined by [rgb_from_bytes]
68pub fn rgb_from_string<T: Display>(word: T) -> [u8; 3] {
69    rgb_from_bytes(word.to_string().as_bytes())
70}
71
72/// determine an ANSI-256 color determined by [rgb_from_bytes]
73pub fn from_bytes(bytes: &[u8]) -> u8 {
74    let mut color: u8 = 0;
75    for rgb in rgb_from_bytes(bytes) {
76        color ^= rgb
77    }
78    color
79}
80/// simple and naive algorithm to determine a triple of RGB colors
81/// based on XOR'ing the given slice of bytes;
82pub fn rgb_from_bytes(bytes: &[u8]) -> [u8; 3] {
83    let mut color: [u8; 3] = [0, 0, 0];
84    let mut iter = 0;
85    while iter < 3 {
86        for (index, byte) in bytes.iter().enumerate() {
87            color[index % 3] ^= *byte
88        }
89        iter += 1;
90    }
91    color
92}
93
94/// returns a tuple of (foreground, backrground) color by taking one
95/// unsigned integer, wrapping that around [u8::MAX] to determine the
96/// foreground color then uses [invert_bw] to
97/// determine the background color.
98pub fn couple(color: usize) -> (u8, u8) {
99    let fore = wrap(color);
100    let back = invert_bw(fore);
101    (fore, back)
102}
103
104/// naive heuristic to return the brightest opposite of the given color.
105pub fn invert_bw(color: u8) -> u8 {
106    match color {
107        0 | 8 | 16..21 | 52..61 | 88..93 | 232..239 => 231,
108        _ => 16,
109    }
110}
111
112/// wraps the given usize via remainder
113pub fn wrap(color: usize) -> u8 {
114    (if color > 0 { color % 255 } else { color }) as u8
115}
116
117/// naive function for unix terminals that calls stty to determine the
118/// number of columns of the terminal.
119///
120/// The rationale of the approach is to avoid linking to libc in order
121/// to do ioctl FFI calls and keeping the rust crate lightweight.
122///
123/// This function might be rewritten using a more sophisticated
124/// approach in the future.
125fn io_term_cols() -> std::io::Result<usize> {
126    use std::process::{Command, Stdio};
127    let mut cmd = Command::new("/bin/stty");
128    let cmd = cmd.args(vec!["-a"]);
129    let cmd = cmd.stdin(Stdio::inherit());
130    let cmd = cmd.stdout(Stdio::piped());
131    let cmd = cmd.stderr(Stdio::piped());
132    let child = cmd.spawn()?;
133    let output = child.wait_with_output()?;
134    let lines = String::from_utf8_lossy(&output.stdout)
135        .to_string()
136        .lines()
137        .map(String::from)
138        .collect::<Vec<String>>();
139    let lines = lines[0]
140        .split(';')
141        .map(String::from)
142        .collect::<Vec<String>>();
143    if lines.len() > 2 {
144        let fields = lines[2]
145            .split(' ')
146            .map(String::from)
147            .collect::<Vec<String>>();
148        if let Ok(cols) = usize::from_str_radix(&fields[1], 10) {
149            return Ok(cols);
150        }
151    }
152    Ok(DEFAULT_COLUMNS)
153}
154
155/// tries to obtain the number of columns of the terminal via
156/// [io_term_cols] and falls back to
157/// [DEFAULT_COLUMNS] in case of error.
158pub fn term_cols() -> usize {
159    io_term_cols().unwrap_or_else(|_| DEFAULT_COLUMNS)
160}