use crate::coverage::LineRange;
use crate::delta::{DeltaEntry, DeltaStatus};
use comfy_table::Color;
use std::sync::atomic::{AtomicBool, Ordering};
static COLOR_ENABLED: AtomicBool = AtomicBool::new(false);
pub fn set_color_enabled(enabled: bool) {
COLOR_ENABLED.store(enabled, Ordering::Relaxed);
}
pub(crate) fn color_enabled() -> bool {
COLOR_ENABLED.load(Ordering::Relaxed)
}
pub(crate) fn styled(
text: &str,
style: owo_colors::Style,
) -> String {
use owo_colors::OwoColorize;
if color_enabled() {
text.style(style).to_string()
} else {
text.to_string()
}
}
pub(crate) fn apply_table_styling(table: &mut comfy_table::Table) {
if color_enabled() {
table.enforce_styling();
} else {
table.force_no_tty();
}
}
pub(crate) enum Grade {
Clean,
Moderate,
Crappy,
}
impl Grade {
pub(crate) fn of(
score: f64,
threshold: f64,
) -> Self {
if score > threshold {
Self::Crappy
} else if score > threshold / 3.0 {
Self::Moderate
} else {
Self::Clean
}
}
pub(crate) fn icon(&self) -> &'static str {
match self {
Self::Clean => "✓",
Self::Moderate => "▲",
Self::Crappy => "✗",
}
}
pub(crate) fn color(&self) -> Color {
match self {
Self::Clean => Color::Green,
Self::Moderate => Color::Yellow,
Self::Crappy => Color::Red,
}
}
}
pub(crate) fn coverage_bar(pct: Option<f64>) -> String {
match pct {
None => format!("{:░<10} —", ""),
Some(p) => {
let filled = ((p / 100.0) * 10.0).round() as usize;
let filled = filled.min(10);
format!(
"{}{} {:>5.1}%",
"█".repeat(filled),
"░".repeat(10 - filled),
p
)
},
}
}
const UNCOVERED_DISPLAY_CAP: usize = 3;
pub(crate) fn uncovered_display(ranges: &[LineRange]) -> String {
let shown: Vec<String> = ranges
.iter()
.take(UNCOVERED_DISPLAY_CAP)
.map(|r| {
if r.start == r.end {
r.start.to_string()
} else {
format!("{}–{}", r.start, r.end)
}
})
.collect();
let hidden = ranges.len().saturating_sub(UNCOVERED_DISPLAY_CAP);
if hidden > 0 {
format!("{} +{hidden} more", shown.join(", "))
} else {
shown.join(", ")
}
}
pub(crate) fn uncovered_header_suffix(uncovered_hints: bool) -> (&'static str, &'static str) {
if uncovered_hints {
(" Uncovered |", "---|")
} else {
("", "")
}
}
pub(crate) fn uncovered_cell_suffix(
uncovered_hints: bool,
ranges: &[LineRange],
) -> String {
if uncovered_hints {
format!(" {} |", uncovered_display(ranges))
} else {
String::new()
}
}
pub(crate) fn write_abs_gfm_header(
out: &mut dyn std::io::Write,
uncovered_hints: bool,
) -> anyhow::Result<()> {
let (head_extra, sep_extra) = uncovered_header_suffix(uncovered_hints);
writeln!(
out,
"| | CRAP | CC | Cov % | Function | Location |{head_extra}"
)?;
writeln!(out, "|---|---:|---:|---:|---|---|{sep_extra}")?;
Ok(())
}
pub(crate) fn write_delta_gfm_header(
out: &mut dyn std::io::Write,
uncovered_hints: bool,
) -> anyhow::Result<()> {
let (head_extra, sep_extra) = uncovered_header_suffix(uncovered_hints);
writeln!(
out,
"| | CRAP | Δ | CC | Cov % | Function | Location |{head_extra}"
)?;
writeln!(out, "|---|---:|---:|---:|---:|---|---|{sep_extra}")?;
Ok(())
}
pub(crate) fn visible_delta_entries(
entries: &[DeltaEntry],
show_unchanged: bool,
) -> Vec<&DeltaEntry> {
entries
.iter()
.filter(|e| show_unchanged || e.status != DeltaStatus::Unchanged)
.collect()
}
pub(crate) fn delta_display(de: &DeltaEntry) -> String {
match de.status {
DeltaStatus::Regressed | DeltaStatus::Improved => signed_delta(de.delta.unwrap()),
DeltaStatus::New => "NEW".to_string(),
DeltaStatus::Unchanged | DeltaStatus::Moved => String::new(),
}
}
fn signed_delta(delta: f64) -> String {
for decimals in 1..=3 {
let s = format!("{delta:+.decimals$}");
if s.bytes().any(|b| b.is_ascii_digit() && b != b'0') {
return s;
}
}
format!("{delta:+.3}")
}
pub(crate) fn format_location_with_prev(
file: &std::path::Path,
line: usize,
previous_file: Option<&std::path::Path>,
) -> String {
match previous_file {
Some(prev) => format!("`{}:{}` ← `{}`", file.display(), line, prev.display()),
None => format!("`{}:{}`", file.display(), line),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn coverage_bar_is_all_empty_for_zero_percent() {
let bar = coverage_bar(Some(0.0));
assert!(
bar.starts_with("░░░░░░░░░░"),
"0% must start with 10 empty blocks, got: {bar}"
);
assert!(bar.contains("0.0%"), "0% must include numeric label");
}
#[test]
fn coverage_bar_is_all_full_for_100_percent() {
let bar = coverage_bar(Some(100.0));
assert!(
bar.starts_with("██████████"),
"100% must start with 10 full blocks, got: {bar}"
);
assert!(bar.contains("100.0%"), "100% must include numeric label");
}
#[test]
fn coverage_bar_is_half_full_for_50_percent() {
let bar = coverage_bar(Some(50.0));
assert!(
bar.starts_with("█████░░░░░"),
"50% must have 5 full then 5 empty blocks, got: {bar}"
);
}
#[test]
fn coverage_bar_none_is_all_empty_with_dash() {
let bar = coverage_bar(None);
assert!(
bar.contains("░░░░░░░░░░"),
"None must render with all-empty bar, got: {bar}"
);
assert!(bar.contains("—"), "None must use — instead of a percentage");
}
#[test]
fn grade_tier_boundaries_are_correct() {
assert_eq!(
Grade::of(10.0, 30.0).icon(),
"✓",
"exactly threshold/3 → Clean"
);
assert_eq!(
Grade::of(10.001, 30.0).icon(),
"▲",
"just above threshold/3 → Moderate"
);
assert_eq!(
Grade::of(30.0, 30.0).icon(),
"▲",
"exactly threshold → Moderate (not Crappy)"
);
assert_eq!(
Grade::of(30.001, 30.0).icon(),
"✗",
"just above threshold → Crappy"
);
}
#[test]
fn signed_delta_uses_one_decimal_for_ordinary_deltas() {
assert_eq!(signed_delta(1.0), "+1.0");
assert_eq!(signed_delta(-12.34), "-12.3");
}
#[test]
fn signed_delta_widens_until_the_value_is_visible() {
assert_eq!(signed_delta(-0.04), "-0.04");
assert_eq!(signed_delta(0.04), "+0.04");
assert_eq!(signed_delta(-0.004), "-0.004");
}
#[test]
fn signed_delta_caps_at_three_decimals() {
assert_eq!(signed_delta(-0.0004), "-0.000");
}
#[test]
fn uncovered_display_uses_en_dash_ranges_and_bare_singles() {
assert_eq!(
uncovered_display(&LineRange::list(&[(12, 14), (17, 17)])),
"12–14, 17"
);
}
#[test]
fn uncovered_display_is_empty_for_no_ranges() {
assert_eq!(uncovered_display(&[]), "");
}
#[test]
fn uncovered_display_caps_at_three_and_counts_the_rest() {
assert_eq!(
uncovered_display(&LineRange::list(&[
(1, 2),
(4, 5),
(7, 8),
(10, 11),
(13, 14)
])),
"1–2, 4–5, 7–8 +2 more"
);
}
#[test]
fn uncovered_display_shows_exactly_three_without_tail() {
assert_eq!(
uncovered_display(&LineRange::list(&[(1, 2), (4, 5), (7, 8)])),
"1–2, 4–5, 7–8"
);
}
#[test]
fn uncovered_suffixes_are_empty_when_hints_are_off() {
assert_eq!(uncovered_header_suffix(false), ("", ""));
assert_eq!(
uncovered_cell_suffix(false, &LineRange::list(&[(1, 2)])),
""
);
}
#[test]
fn uncovered_suffixes_extend_the_gfm_row_when_hints_are_on() {
assert_eq!(uncovered_header_suffix(true), (" Uncovered |", "---|"));
assert_eq!(
uncovered_cell_suffix(true, &LineRange::list(&[(1, 2)])),
" 1–2 |"
);
assert_eq!(uncovered_cell_suffix(true, &[]), " |");
}
}