1use std::{io::IsTerminal, sync::OnceLock};
7use termcolor::{ColorSpec, StandardStream, StandardStreamLock, WriteColor};
8
9static COLOR_OUT: OnceLock<StandardStream> = OnceLock::new();
10static COLOR_ERR: OnceLock<StandardStream> = OnceLock::new();
11
12pub struct ColorText(StandardStreamLock<'static>);
14
15impl ColorText {
16 pub fn stdout(spec: ColorSpec) -> ColorText {
18 let stream =
19 COLOR_OUT.get_or_init(|| StandardStream::stdout(color_choice(std::io::stdout())));
20 set_color(stream, spec)
21 }
22
23 pub fn stderr(spec: ColorSpec) -> ColorText {
25 let stream =
26 COLOR_ERR.get_or_init(|| StandardStream::stderr(color_choice(std::io::stderr())));
27 set_color(stream, spec)
28 }
29}
30
31impl std::io::Write for ColorText {
32 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
33 self.0.write(buf)
34 }
35
36 fn flush(&mut self) -> std::io::Result<()> {
37 self.0.flush()
38 }
39}
40
41impl WriteColor for ColorText {
42 fn supports_color(&self) -> bool {
43 self.0.supports_color()
44 }
45
46 fn set_color(&mut self, spec: &ColorSpec) -> std::io::Result<()> {
47 self.0.set_color(spec)
48 }
49
50 fn reset(&mut self) -> std::io::Result<()> {
51 self.0.reset()
52 }
53}
54
55impl Drop for ColorText {
56 fn drop(&mut self) {
57 let _ = self.reset();
58 }
59}
60
61fn set_color(stream: &'static StandardStream, spec: ColorSpec) -> ColorText {
62 let mut lock = stream.lock();
63 let _ = lock.set_color(&spec);
64 ColorText(lock)
65}
66
67fn color_choice(stream: impl IsTerminal) -> termcolor::ColorChoice {
68 if stream.is_terminal() {
69 termcolor::ColorChoice::Auto
70 } else {
71 termcolor::ColorChoice::Never
72 }
73}
74
75#[macro_export]
76macro_rules! step {
77 ($step:expr, $($arg:tt)*) => {{
78 $crate::cprint!($crate::colors::bold_green(), $step);
79 print!(" ");
80 println!($($arg)*);
81 }};
82}
83
84#[macro_export]
85macro_rules! warn {
86 ($($arg:tt)*) => {{
87 $crate::ceprint!($crate::colors::bold_yellow(), "Warning");
88 eprint!(": ");
89 eprintln!($($arg)*);
90 }};
91}
92
93#[macro_export]
94macro_rules! error {
95 ($($arg:tt)*) => {{
96 $crate::ceprint!($crate::colors::bold_red(), "Error");
97 eprint!(": ");
98 eprintln!($($arg)*);
99 }};
100}
101
102#[macro_export]
103macro_rules! einfo {
104 ($highlight:expr, $($arg:tt)*) => {{
105 $crate::ceprint!($crate::colors::bold_cyan(), $highlight);
106 eprint!(" ");
107 eprintln!($($arg)*);
108 }};
109}
110
111#[macro_export]
112macro_rules! cprint {
113 ($color:expr, $($arg:tt)*) => {
114 use std::io::Write;
115 let mut out = $crate::ColorText::stdout($color);
116 let _ = write!(out, $($arg)*);
117 drop(out); };
119}
120
121#[macro_export]
122macro_rules! ceprint {
123 ($color:expr, $($arg:tt)*) => {
124 use std::io::Write;
125 let mut out = $crate::ColorText::stderr($color);
126 let _ = write!(out, $($arg)*);
127 drop(out); };
129}
130
131#[macro_export]
132macro_rules! ceprintln {
133 ($color:expr, $($arg:tt)*) => {
134 use std::io::Write;
135 let mut out = $crate::ColorText::stderr($color);
136 let _ = writeln!(out, $($arg)*);
137 drop(out); };
139}
140
141pub mod colors {
142 use termcolor::{Color, ColorSpec};
143
144 pub fn bold_red() -> ColorSpec {
145 new(Color::Red, true)
146 }
147
148 pub fn bold_green() -> ColorSpec {
149 new(Color::Green, true)
150 }
151
152 pub fn bold_cyan() -> ColorSpec {
153 new(Color::Cyan, true)
154 }
155
156 pub fn bold_yellow() -> ColorSpec {
157 new(Color::Yellow, true)
158 }
159
160 fn new(color: Color, bold: bool) -> ColorSpec {
161 let mut s = ColorSpec::new();
162 s.set_fg(Some(color)).set_bold(bold);
163 s
164 }
165}