pub mod console;
pub mod denial;
pub mod progress;
pub mod rich_theme;
pub mod tables;
pub mod test;
pub mod theme;
pub mod tree;
pub use console::{DcgConsole, console, init_console};
pub use denial::DenialBox;
pub use progress::{
MaybeProgress, NoopProgress, SCAN_PROGRESS_THRESHOLD, ScanProgress, ScanProgressStyle, spinner,
spinner_if_tty,
};
#[cfg(feature = "rich-output")]
pub use progress::{RichProgressStyle, render_progress_bar_rich};
pub use rich_theme::{RichThemeExt, color_to_markup, severity_badge_markup, severity_panel_title};
pub use tables::{ScanResultRow, ScanResultsTable, TableStyle};
pub use test::{AllowedReason, TestOutcome, TestResultBox};
pub use theme::{BorderStyle, Severity, SeverityColors, Theme, ThemePalette};
pub use tree::{DcgTree, DcgTreeGuides, ExplainTreeBuilder, TreeNode};
use crate::config::Config;
use std::sync::OnceLock;
static FORCE_PLAIN: OnceLock<bool> = OnceLock::new();
static SUGGESTIONS_ENABLED: OnceLock<bool> = OnceLock::new();
pub fn init(force_plain: bool) {
let _ = FORCE_PLAIN.set(force_plain);
}
pub fn init_suggestions(enabled: bool) {
let _ = SUGGESTIONS_ENABLED.set(enabled);
}
#[must_use]
pub fn should_use_rich_output() -> bool {
if FORCE_PLAIN.get().copied().unwrap_or(false) {
return false;
}
if std::env::var("NO_COLOR").is_ok() || std::env::var("DCG_NO_COLOR").is_ok() {
return false;
}
if std::env::var("CI").is_ok() {
return false;
}
if !::console::Term::stdout().is_term() {
return false;
}
if let Ok(term) = std::env::var("TERM") {
if term == "dumb" {
return false;
}
}
true
}
#[must_use]
pub fn auto_theme() -> Theme {
if should_use_rich_output() {
if env_flag_enabled("DCG_HIGH_CONTRAST") {
Theme::high_contrast()
} else {
Theme::default()
}
} else {
Theme::no_color()
}
}
#[must_use]
pub fn auto_theme_with_config(config: &Config) -> Theme {
if !should_use_rich_output() {
return Theme::no_color();
}
let palette = if env_flag_enabled("DCG_HIGH_CONTRAST") || config.output.high_contrast_enabled()
{
ThemePalette::HighContrast
} else if let Some(palette) = config
.theme
.palette
.as_deref()
.and_then(|value| value.parse::<ThemePalette>().ok())
{
palette
} else {
ThemePalette::Default
};
let mut theme = Theme::from_palette(palette);
if let Some(use_color) = config.theme.use_color {
if !use_color {
theme = theme.without_colors();
}
}
if let Some(use_unicode) = config.theme.use_unicode {
if palette != ThemePalette::HighContrast {
theme.border_style = if use_unicode {
BorderStyle::Unicode
} else {
BorderStyle::Ascii
};
}
}
theme
}
fn env_flag_enabled(var: &str) -> bool {
std::env::var(var).is_ok_and(|value| {
!matches!(
value.trim().to_lowercase().as_str(),
"" | "0" | "false" | "no" | "off"
)
})
}
#[must_use]
pub fn supports_256_colors() -> bool {
if !should_use_rich_output() {
return false;
}
if let Ok(colorterm) = std::env::var("COLORTERM") {
if colorterm == "truecolor" || colorterm == "24bit" {
return true;
}
}
if let Ok(term) = std::env::var("TERM") {
if term.contains("256color") || term.contains("truecolor") {
return true;
}
}
true
}
#[must_use]
pub fn terminal_width() -> u16 {
::console::Term::stdout()
.size_checked()
.map_or(80, |(_, w)| w)
}
#[must_use]
pub fn terminal_height() -> u16 {
::console::Term::stdout()
.size_checked()
.map_or(24, |(h, _)| h)
}
#[must_use]
pub fn suggestions_enabled() -> bool {
suggestions_requested() && should_use_rich_output()
}
#[must_use]
pub fn suggestions_requested() -> bool {
SUGGESTIONS_ENABLED.get().copied().unwrap_or(true)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auto_theme_returns_theme() {
let theme = auto_theme();
assert!(matches!(
theme.border_style,
BorderStyle::Unicode | BorderStyle::Ascii | BorderStyle::None
));
}
#[test]
fn test_terminal_dimensions_have_defaults() {
let width = terminal_width();
let height = terminal_height();
assert!(width > 0);
assert!(height > 0);
}
#[test]
fn test_supports_256_colors_does_not_panic() {
let _ = supports_256_colors();
}
#[test]
fn test_env_flag_enabled_true_values() {
assert!(!env_flag_enabled("DCG_NONEXISTENT_TEST_VAR_12345"));
}
#[test]
fn test_env_flag_enabled_false_for_unset() {
assert!(!env_flag_enabled("DCG_DEFINITELY_NOT_SET_EVER"));
}
#[test]
fn test_suggestions_enabled_default() {
let result = suggestions_enabled();
assert!(!result);
}
#[test]
fn test_init_idempotent() {
init(false);
init(true);
}
#[test]
fn test_init_suggestions_idempotent() {
init_suggestions(true);
init_suggestions(false);
}
#[test]
fn test_should_use_rich_output_in_test_env() {
let result = should_use_rich_output();
assert!(!result);
}
#[test]
fn test_auto_theme_no_color_in_test_env() {
let theme = auto_theme();
assert!(!theme.colors_enabled);
}
#[test]
fn test_terminal_width_reasonable_range() {
let width = terminal_width();
assert!(width >= 1);
assert!(width <= 500);
}
#[test]
fn test_terminal_height_reasonable_range() {
let height = terminal_height();
assert!(height >= 1);
assert!(height <= 200);
}
}