Production grade is hard…
Real colors on real terminals
I’m writing some fun utility, but with serious implementation. I decided to add colors to errors.
What can be easier to do?
[dependencies]
colored = "2"
and
Err(e) => {
eprintln!("{}", e.to_string().red());
}
Colored support automatic detection of NO_COLOR
, so did I got a production grade ‘red’ message?
Nope
foo
(colored output in stderr)
but
foo > /dev/null
(no colors in stderr)
Why? Because colored
can’t see if I’m printing to stdout
or stderr
, and it’s checking stdout
for been a tty
. It’s not, therefore, message should have no colors. But! I have tty
for stderr
, although there is no tty
for stdout
.
Bum!
Who said production is simple?
The next lib (termcolor
) I’m trying is much harder to use:
use std::io::{self, Write};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
fn write_green() -> io::Result<()> {
let mut stdout = StandardStream::stdout(ColorChoice::Always);
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
writeln!(&mut stdout, "green text!")
}
and it does not detect atty. Plainly in docs. Huh. No. Next is owo-colors
.
foo > /dev/null
give me colors, good. But foo 2> foobar
got me esc-codes in the file. Not good. Do they have any helper code to detect if colors are needed?
println!(
"{}",
"colored blue if a supported terminal".
.if_supports_color(Stream::Stdout, |text| text.bright_blue())
);
But it does not work! A direct copy-paste from examples does not even compile.
Oh, we need a feature “supports-color”. I enabled it, but it still do not compile. No Stream found.
I poked around as much as I can, nope, thanks. Next.
colour
crate even don’t mentioning auto-detection…
Okay, let’s turn back. There is colored
, which lacks proper detection for stderr
. But there is the crate supports-color
I found earlier inside owo-colors
. Let’s bolt together color
and supports-color
:
let msg = if let Some(support) = supports_color::on(Stream::Stdout){
e.to_string().red()
}else{
e.to_string()
};
Aghr… String
and ColoredString
are different types. Can I make ColoredString
without colors? Nope.
Okay, let’s do it straightforward way:
if supports_color::on(Stream::Stderr).is_some(){
eprintln!("{}", e.to_string().red());
}else{
eprintln!("{}", e);
};
But it fails foo >/dev/null
test, because ‘red’ is thinking stdout
is not a tty
, therefore, no colors.
Ahrrr…
colored::control::set_override(
supports_color::on(Stream::Stderr).is_some()
);eprintln!("{}", e.to_string().red());
Okay, usual tests passed.
All redirects works. Environment variable NO_COLOR
is respected.
But can I force color? Yes, there is FORCE_COLOR
!
Bingo. Now I have production-grade error message.
It took me about one hour in Rust to write a proper colored message on the screen. Huh. Production-grade is hard…
P.S.
I found that it does not respect TERM fully. I’ve tried vt100 and it still showing colors! Uh…