use std::cell::RefCell;
use std::io::Write;
use crate::output::OutputMode;
use crate::theme::Theme;
thread_local! {
static WARNINGS: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
}
pub fn push_warning(message: impl Into<String>) {
WARNINGS.with(|w| w.borrow_mut().push(message.into()));
}
pub fn drain_warnings() -> Vec<String> {
WARNINGS.with(|w| std::mem::take(&mut *w.borrow_mut()))
}
pub fn has_warnings() -> bool {
WARNINGS.with(|w| !w.borrow().is_empty())
}
pub const WARNING_BANNER_STYLE: &str = "standout_warning_banner";
pub const WARNING_ITEM_STYLE: &str = "standout_warning_item";
const BANNER_TEXT: &str = " Standout :: Warnings ";
pub fn flush_to_stderr(theme: &Theme, output_mode: OutputMode) {
let warnings = drain_warnings();
if warnings.is_empty() {
return;
}
let use_color = should_style_stderr(output_mode);
let styles = theme.resolve_styles(None);
let stderr = std::io::stderr();
let mut out = stderr.lock();
let _ = writeln!(out);
let _ = writeln!(
out,
"{}",
style_for_stderr(&styles, WARNING_BANNER_STYLE, BANNER_TEXT, use_color)
);
for w in warnings {
let _ = writeln!(
out,
"\t{}",
style_for_stderr(&styles, WARNING_ITEM_STYLE, &w, use_color)
);
}
}
fn style_for_stderr(
styles: &crate::style::Styles,
style_name: &str,
text: &str,
use_color: bool,
) -> String {
if !use_color {
return text.to_string();
}
match styles.resolve(style_name) {
Some(style) => style
.clone()
.for_stderr()
.force_styling(true)
.apply_to(text)
.to_string(),
None => text.to_string(),
}
}
fn should_style_stderr(output_mode: OutputMode) -> bool {
if matches!(output_mode, OutputMode::Text) {
return false;
}
console::Term::stderr().features().colors_supported()
}
#[cfg(test)]
mod tests {
use super::*;
use console::Style;
fn reset() {
let _ = drain_warnings();
}
#[test]
fn push_and_drain_roundtrip() {
reset();
assert!(!has_warnings());
push_warning("first");
push_warning(String::from("second"));
assert!(has_warnings());
let drained = drain_warnings();
assert_eq!(drained, vec!["first".to_string(), "second".to_string()]);
assert!(!has_warnings());
assert!(drain_warnings().is_empty());
}
#[test]
fn default_theme_registers_warning_styles() {
let theme = Theme::default();
let styles = theme.resolve_styles(None);
assert!(
styles.has(WARNING_BANNER_STYLE),
"Theme::default missing '{}'",
WARNING_BANNER_STYLE
);
assert!(
styles.has(WARNING_ITEM_STYLE),
"Theme::default missing '{}'",
WARNING_ITEM_STYLE
);
}
#[test]
fn style_for_stderr_plain_when_color_disabled() {
let mut styles = crate::style::Styles::new();
styles = styles.add("some_style", Style::new().red());
let out = style_for_stderr(&styles, "some_style", "hello", false);
assert_eq!(out, "hello");
}
#[test]
fn style_for_stderr_plain_when_style_missing() {
let styles = crate::style::Styles::new();
let out = style_for_stderr(&styles, "no_such_style", "hello", true);
assert_eq!(out, "hello");
}
#[test]
fn style_for_stderr_emits_ansi_when_enabled() {
let styles = crate::style::Styles::new().add("warn", Style::new().red().bold());
let out = style_for_stderr(&styles, "warn", "hello", true);
assert!(
out.contains("\x1b["),
"expected ANSI escape in styled output, got: {:?}",
out
);
assert!(out.contains("hello"));
}
}