use std::io::Write;
use std::sync::atomic::{AtomicU8, Ordering};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
static VERBOSITY: AtomicU8 = AtomicU8::new(2);
pub fn set_verbosity(level: u8) {
VERBOSITY.store(level, Ordering::Relaxed);
}
#[must_use]
pub const fn is_enabled_for(level: u8) -> bool {
level >= 2
}
#[must_use]
pub fn enabled() -> bool {
if !is_enabled_for(VERBOSITY.load(Ordering::Relaxed)) {
return false;
}
if std::env::var_os("KAZE_TESTING").is_some() && std::env::var_os("KAZE_LOGS").is_none() {
return false;
}
true
}
pub fn info(msg: impl AsRef<str>) {
if !enabled() {
return;
}
let mut stream = StandardStream::stdout(ColorChoice::Auto);
if write_info(&mut stream, msg.as_ref()).is_err()
&& writeln!(stream, "kaze: {}", msg.as_ref()).is_err()
{
drop(stream);
}
}
pub fn error(msg: impl AsRef<str>) {
let mut stream = StandardStream::stderr(ColorChoice::Auto);
if write_error(&mut stream, msg.as_ref()).is_err()
&& writeln!(stream, "{}", msg.as_ref()).is_err()
{
drop(stream);
}
}
fn write_info(out: &mut dyn WriteColor, msg: &str) -> std::io::Result<()> {
let mut spec = ColorSpec::new();
spec.set_fg(Some(Color::Green));
out.set_color(&spec)?;
writeln!(out, "kaze: {msg}")?;
out.reset()?;
Ok(())
}
fn write_error(out: &mut dyn WriteColor, msg: &str) -> std::io::Result<()> {
let mut spec = ColorSpec::new();
spec.set_fg(Some(Color::Red));
out.set_color(&spec)?;
writeln!(out, "{msg}")?;
out.reset()?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::ffi::OsString;
use std::sync::{Mutex, OnceLock};
use super::*;
fn test_lock() -> std::sync::MutexGuard<'static, ()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
.lock()
.expect("lock log tests")
}
fn restore_env(key: &str, value: Option<OsString>) {
match value {
Some(val) => {
unsafe {
std::env::set_var(key, val);
}
}
None => {
unsafe {
std::env::remove_var(key);
}
}
}
}
#[test]
fn write_info_includes_color_when_ansi_enabled() {
let mut buf = termcolor::Buffer::ansi();
write_info(&mut buf, "hello").expect("write info");
let output = std::str::from_utf8(buf.as_slice()).expect("utf8");
assert!(output.contains("kaze: hello"));
assert!(output.contains("\u{1b}["));
}
#[test]
fn write_info_no_color_when_disabled() {
let mut buf = termcolor::Buffer::no_color();
write_info(&mut buf, "hello").expect("write info");
let output = std::str::from_utf8(buf.as_slice()).expect("utf8");
assert_eq!(output, "kaze: hello\n");
}
#[test]
fn write_error_includes_color_when_ansi_enabled() {
let mut buf = termcolor::Buffer::ansi();
write_error(&mut buf, "error: boom").expect("write error");
let output = std::str::from_utf8(buf.as_slice()).expect("utf8");
assert!(output.contains("error: boom"));
assert!(output.contains("\u{1b}["));
}
#[test]
fn write_error_no_color_when_disabled() {
let mut buf = termcolor::Buffer::no_color();
write_error(&mut buf, "error: boom").expect("write error");
let output = std::str::from_utf8(buf.as_slice()).expect("utf8");
assert_eq!(output, "error: boom\n");
}
#[test]
fn info_and_error_cover_output_paths() {
let _guard = test_lock();
let old_testing = std::env::var_os("KAZE_TESTING");
let old_logs = std::env::var_os("KAZE_LOGS");
unsafe {
std::env::set_var("KAZE_TESTING", "1");
std::env::set_var("KAZE_LOGS", "1");
}
set_verbosity(2);
info("hello");
error("oops");
restore_env("KAZE_TESTING", old_testing);
restore_env("KAZE_LOGS", old_logs);
}
#[test]
fn enabled_false_when_verbosity_low() {
let _guard = test_lock();
set_verbosity(1);
assert!(!enabled());
set_verbosity(2);
}
#[test]
fn enabled_respects_testing_env_vars() {
let _guard = test_lock();
let old_testing = std::env::var_os("KAZE_TESTING");
let old_logs = std::env::var_os("KAZE_LOGS");
unsafe {
std::env::set_var("KAZE_TESTING", "1");
std::env::remove_var("KAZE_LOGS");
}
set_verbosity(2);
assert!(!enabled());
unsafe {
std::env::set_var("KAZE_LOGS", "1");
}
assert!(enabled());
restore_env("KAZE_TESTING", old_testing);
restore_env("KAZE_LOGS", old_logs);
}
}