use std::{
env,
io::{self, IsTerminal as _, Write as _},
str::FromStr,
sync::atomic::{AtomicBool, AtomicU8, Ordering},
};
use anyhow::{Error, Result, bail, format_err};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor as _};
#[derive(Clone, Copy, PartialEq)]
#[repr(u8)]
pub(crate) enum Coloring {
Auto = 0,
Always,
Never,
}
impl Coloring {
const AUTO: u8 = Self::Auto as u8;
const ALWAYS: u8 = Self::Always as u8;
const NEVER: u8 = Self::Never as u8;
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Auto => "auto",
Self::Always => "always",
Self::Never => "never",
}
}
}
impl FromStr for Coloring {
type Err = Error;
fn from_str(color: &str) -> Result<Self, Self::Err> {
match color {
"auto" => Ok(Self::Auto),
"always" => Ok(Self::Always),
"never" => Ok(Self::Never),
other => bail!("must be auto, always, or never, but found `{other}`"),
}
}
}
static COLORING: AtomicU8 = AtomicU8::new(Coloring::AUTO);
pub(crate) fn init_coloring() {
if !io::stderr().is_terminal() {
COLORING.store(Coloring::NEVER, Ordering::Relaxed);
}
}
pub(crate) fn set_coloring(color: Option<Coloring>) -> Result<()> {
let new = match color {
Some(color) => color,
None => match env::var_os("CARGO_TERM_COLOR") {
Some(color) => {
color.to_string_lossy().parse().map_err(|e| format_err!("CARGO_TERM_COLOR {e}"))?
}
None => Coloring::Auto,
},
};
if new == Coloring::Auto && coloring() == ColorChoice::Never {
} else {
COLORING.store(new as u8, Ordering::Relaxed);
}
Ok(())
}
fn coloring() -> ColorChoice {
match COLORING.load(Ordering::Relaxed) {
Coloring::AUTO => ColorChoice::Auto,
Coloring::ALWAYS => ColorChoice::Always,
Coloring::NEVER => ColorChoice::Never,
_ => unreachable!(),
}
}
macro_rules! global_flag {
($name:ident: $value:ty = $ty:ident::new($($default:expr)?)) => {
pub(crate) mod $name {
use super::*;
pub(super) static VALUE: $ty = $ty::new($($default)?);
#[allow(dead_code)]
pub(crate) fn set(value: $value) {
VALUE.store(value, Ordering::Relaxed);
}
}
pub(crate) fn $name() -> $value {
$name::VALUE.load(Ordering::Relaxed)
}
};
}
global_flag!(verbose: bool = AtomicBool::new(false));
global_flag!(error: bool = AtomicBool::new(false));
global_flag!(warn: bool = AtomicBool::new(false));
pub(crate) fn print_status(status: &str, color: Option<Color>) -> StandardStream {
let mut stream = StandardStream::stderr(coloring());
let _ = stream.set_color(ColorSpec::new().set_bold(true).set_fg(color));
let _ = write!(stream, "{status}");
let _ = stream.set_color(ColorSpec::new().set_bold(true));
let _ = write!(stream, ":");
let _ = stream.reset();
let _ = write!(stream, " ");
stream
}
macro_rules! error {
($($msg:expr),* $(,)?) => {{
use std::io::Write as _;
crate::term::error::set(true);
let mut stream = crate::term::print_status("error", Some(termcolor::Color::Red));
let _ = writeln!(stream, $($msg),*);
}};
}
macro_rules! info {
($($msg:expr),* $(,)?) => {{
use std::io::Write as _;
let mut stream = crate::term::print_status("info", None);
let _ = writeln!(stream, $($msg),*);
}};
}