canic_host/terminal/
style.rs1use std::io::{self, IsTerminal};
8
9const RESET: &str = "\u{1b}[0m";
10const BOLD_CYAN: &str = "\u{1b}[1;36m";
11const DIM: &str = "\u{1b}[2m";
12const GREEN: &str = "\u{1b}[32m";
13const YELLOW: &str = "\u{1b}[33m";
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub struct TerminalStyle {
17 interactive: bool,
18 color: bool,
19}
20
21impl TerminalStyle {
22 #[must_use]
23 pub fn detected() -> Self {
24 let interactive =
25 io::stdout().is_terminal() && std::env::var("TERM").map_or(true, |term| term != "dumb");
26 Self {
27 interactive,
28 color: interactive && std::env::var_os("NO_COLOR").is_none(),
29 }
30 }
31
32 #[must_use]
33 pub const fn interactive(self) -> bool {
34 self.interactive
35 }
36
37 #[must_use]
38 pub fn heading(self, text: &str) -> String {
39 self.paint(BOLD_CYAN, text)
40 }
41
42 #[must_use]
43 pub fn success(self, text: &str) -> String {
44 self.paint(GREEN, text)
45 }
46
47 #[must_use]
48 pub fn warning(self, text: &str) -> String {
49 self.paint(YELLOW, text)
50 }
51
52 #[must_use]
53 pub fn muted(self, text: &str) -> String {
54 self.paint(DIM, text)
55 }
56
57 pub fn print_section(self, title: &str, detail: &str) {
58 println!(
59 "{} {} {}",
60 self.heading("==>"),
61 self.heading(title),
62 self.muted(detail)
63 );
64 }
65
66 fn paint(self, code: &str, text: &str) -> String {
67 if self.color {
68 format!("{code}{text}{RESET}")
69 } else {
70 text.to_string()
71 }
72 }
73}
74
75#[cfg(test)]
76impl TerminalStyle {
77 const fn ansi_for_tests() -> Self {
78 Self {
79 interactive: true,
80 color: true,
81 }
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn ansi_style_colors_status_without_changing_text() {
91 let style = TerminalStyle::ansi_for_tests();
92
93 assert_eq!(style.success("done"), "\u{1b}[32mdone\u{1b}[0m");
94 }
95}