use std::fmt::Debug;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
use termcolor::{BufferedStandardStream, Color, ColorChoice, ColorSpec, WriteColor};
#[cfg(windows)]
mod windbg;
const NIH_LOG_ENV: &str = "NIH_LOG";
pub enum OutputTargetImpl {
#[cfg(windows)]
StderrOrWinDbg(BufferedStandardStream, windbg::WinDbgWriter),
Stderr(BufferedStandardStream),
#[cfg(windows)]
WinDbg(windbg::WinDbgWriter),
File(BufWriter<File>),
}
impl Debug for OutputTargetImpl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#[cfg(windows)]
OutputTargetImpl::StderrOrWinDbg(stderr, windbg) => f
.debug_tuple("StderrOrWinDbg")
.field(if stderr.supports_color() {
&"<stderr stream with color support>"
} else {
&"<stderr stream>"
})
.field(windbg)
.finish(),
OutputTargetImpl::Stderr(stderr) => f
.debug_tuple("Stderr")
.field(if stderr.supports_color() {
&"<stderr stream with color support>"
} else {
&"<stderr stream>"
})
.finish(),
#[cfg(windows)]
OutputTargetImpl::WinDbg(windbg) => f.debug_tuple("WinDbg").field(windbg).finish(),
OutputTargetImpl::File(file) => f.debug_tuple("File").field(file).finish(),
}
}
}
pub trait WriteExt: Write {
fn set_fg_color(&mut self, color: Color);
fn reset_colors(&mut self);
}
impl WriteExt for BufferedStandardStream {
fn set_fg_color(&mut self, color: Color) {
let _ = self.set_color(ColorSpec::new().set_fg(Some(color)));
}
fn reset_colors(&mut self) {
let _ = self.reset();
}
}
#[cfg(windows)]
impl WriteExt for windbg::WinDbgWriter {
fn set_fg_color(&mut self, _color: Color) {}
fn reset_colors(&mut self) {}
}
impl WriteExt for BufWriter<File> {
fn set_fg_color(&mut self, _color: Color) {}
fn reset_colors(&mut self) {}
}
impl OutputTargetImpl {
#[cfg(windows)]
pub fn new_stderr_or_windbg() -> Self {
OutputTargetImpl::StderrOrWinDbg(
BufferedStandardStream::stderr(stderr_color_support()),
windbg::WinDbgWriter::default(),
)
}
pub fn new_stderr() -> Self {
OutputTargetImpl::Stderr(BufferedStandardStream::stderr(stderr_color_support()))
}
#[cfg(windows)]
pub fn new_windbg() -> Self {
OutputTargetImpl::WinDbg(windbg::WinDbgWriter::default())
}
pub fn new_file_path<P: AsRef<Path>>(path: P) -> Result<Self, std::io::Error> {
let file = File::options().create(true).append(true).open(path)?;
Ok(Self::File(BufWriter::new(file)))
}
pub fn writer(&mut self) -> &mut dyn WriteExt {
match self {
#[cfg(windows)]
OutputTargetImpl::StderrOrWinDbg(_, ref mut windbg) if windbg::attached() => windbg,
#[cfg(windows)]
OutputTargetImpl::StderrOrWinDbg(ref mut stderr, _) => stderr,
OutputTargetImpl::Stderr(ref mut stderr) => stderr,
#[cfg(windows)]
OutputTargetImpl::WinDbg(ref mut windbg) => windbg,
OutputTargetImpl::File(ref mut file) => file,
}
}
pub fn default_from_environment() -> Self {
let nih_log_env = std::env::var(NIH_LOG_ENV);
let nih_log_env_str = nih_log_env.as_deref().unwrap_or("");
if nih_log_env_str.eq_ignore_ascii_case("stderr") {
return Self::new_stderr();
}
#[cfg(windows)]
if nih_log_env_str.eq_ignore_ascii_case("windbg") {
return Self::new_windbg();
}
if !nih_log_env_str.is_empty() {
match Self::new_file_path(nih_log_env_str) {
Ok(target) => return target,
Err(err) => eprintln!(
"Could not open '{nih_log_env_str}' from NIH_LOG for logging, falling back to \
STDERR: {err}"
),
}
}
#[cfg(windows)]
return Self::new_stderr_or_windbg();
#[cfg(not(windows))]
return Self::new_stderr();
}
}
fn stderr_color_support() -> ColorChoice {
if let Ok(value) = std::env::var("CLICOLOR_FORCE") {
if value.trim() != "0" {
return ColorChoice::Always;
}
}
if let Ok(value) = std::env::var("NO_COLOR") {
if value.trim() != "0" {
return ColorChoice::Never;
}
}
if let Ok(value) = std::env::var("CLICOLOR") {
if value.trim() == "0" {
return ColorChoice::Never;
}
}
if atty::is(atty::Stream::Stderr) {
ColorChoice::Auto
} else {
ColorChoice::Never
}
}