Skip to content

Commit 388184d

Browse files
doctor: Respect NO_COLOR for status tags
Co-authored-by: SCE <sce@crocoder.dev>
1 parent dc495c9 commit 388184d

4 files changed

Lines changed: 187 additions & 19 deletions

File tree

cli/src/services/doctor.rs

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ use crate::services::setup::{
1212
};
1313
#[cfg(test)]
1414
use crate::services::setup::{iter_embedded_assets_for_setup_target, SetupTarget};
15-
use crate::services::style::{heading, label, success, value, OwoColorize};
15+
use crate::services::style::{
16+
heading, label, status_tag_fail, status_tag_miss, status_tag_pass, status_tag_warn, success,
17+
value,
18+
};
1619

1720
pub const NAME: &str = "doctor";
1821

@@ -1473,10 +1476,10 @@ fn format_tagged_lines(lines: Vec<TaggedLine>) -> String {
14731476
fn status_tag_prefix(tag: StatusTag) -> String {
14741477
let prefix = format!("[{}]", status_tag_label(tag));
14751478
match tag {
1476-
StatusTag::Pass => prefix.green().to_string(),
1477-
StatusTag::Fail => prefix.red().to_string(),
1478-
StatusTag::Warn => prefix.yellow().to_string(),
1479-
StatusTag::Miss => prefix.blue().to_string(),
1479+
StatusTag::Pass => status_tag_pass(&prefix),
1480+
StatusTag::Fail => status_tag_fail(&prefix),
1481+
StatusTag::Warn => status_tag_warn(&prefix),
1482+
StatusTag::Miss => status_tag_miss(&prefix),
14801483
}
14811484
}
14821485

@@ -1960,21 +1963,38 @@ mod tests {
19601963
}
19611964

19621965
fn assert_all_lines_tagged(output: &str) {
1963-
let prefixes = [
1964-
super::status_tag_prefix(super::StatusTag::Pass),
1965-
super::status_tag_prefix(super::StatusTag::Fail),
1966-
super::status_tag_prefix(super::StatusTag::Miss),
1967-
super::status_tag_prefix(super::StatusTag::Warn),
1968-
]
1969-
.map(|prefix| format!("{prefix} "));
19701966
for line in output.lines() {
1967+
let normalized = strip_ansi_codes(line);
19711968
assert!(
1972-
prefixes.iter().any(|prefix| line.starts_with(prefix)),
1969+
normalized.starts_with("[PASS] ")
1970+
|| normalized.starts_with("[FAIL] ")
1971+
|| normalized.starts_with("[MISS] ")
1972+
|| normalized.starts_with("[WARN] "),
19731973
"line missing status tag: '{line}'"
19741974
);
19751975
}
19761976
}
19771977

1978+
fn strip_ansi_codes(input: &str) -> String {
1979+
let mut output = String::with_capacity(input.len());
1980+
let mut chars = input.chars().peekable();
1981+
while let Some(ch) = chars.next() {
1982+
if ch == '\u{1b}' {
1983+
if matches!(chars.peek(), Some('[')) {
1984+
chars.next();
1985+
for next in chars.by_ref() {
1986+
if next == 'm' {
1987+
break;
1988+
}
1989+
}
1990+
}
1991+
continue;
1992+
}
1993+
output.push(ch);
1994+
}
1995+
output
1996+
}
1997+
19781998
fn base_report(mode: DoctorMode, readiness: Readiness) -> HookDoctorReport {
19791999
HookDoctorReport {
19802000
mode,
@@ -2093,7 +2113,8 @@ mod tests {
20932113
let output = super::format_execution(&execution);
20942114

20952115
assert_all_lines_tagged(&output);
2096-
assert!(output.contains(&super::status_tag_prefix(super::StatusTag::Fail)));
2116+
let normalized = strip_ansi_codes(&output);
2117+
assert!(normalized.contains("[FAIL]"));
20972118
}
20982119

20992120
#[test]
@@ -2116,7 +2137,8 @@ mod tests {
21162137
let output = super::format_execution(&execution);
21172138

21182139
assert_all_lines_tagged(&output);
2119-
assert!(output.contains(&super::status_tag_prefix(super::StatusTag::Fail)));
2140+
let normalized = strip_ansi_codes(&output);
2141+
assert!(normalized.contains("[FAIL]"));
21202142
}
21212143

21222144
#[test]
@@ -2142,11 +2164,32 @@ mod tests {
21422164
let output = super::format_execution(&execution);
21432165

21442166
assert_all_lines_tagged(&output);
2145-
assert!(output.contains(&super::status_tag_prefix(super::StatusTag::Warn)));
2146-
assert!(output.contains(&super::status_tag_prefix(super::StatusTag::Miss)));
2167+
let normalized = strip_ansi_codes(&output);
2168+
assert!(normalized.contains("[WARN]"));
2169+
assert!(normalized.contains("[MISS]"));
21472170
assert!(output.contains("warning from test"));
21482171
}
21492172

2173+
#[test]
2174+
fn doctor_text_output_disables_prefix_colors_when_no_color_set() {
2175+
let previous = std::env::var("NO_COLOR").ok();
2176+
std::env::set_var("NO_COLOR", "1");
2177+
let execution = DoctorExecution {
2178+
report: base_report(DoctorMode::Diagnose, Readiness::Ready),
2179+
fix_results: Vec::new(),
2180+
};
2181+
let output = super::format_execution(&execution);
2182+
2183+
match previous {
2184+
Some(value) => std::env::set_var("NO_COLOR", value),
2185+
None => std::env::remove_var("NO_COLOR"),
2186+
}
2187+
2188+
assert_all_lines_tagged(&output);
2189+
assert!(output.contains("[PASS] "));
2190+
assert!(!output.contains("\u{1b}["));
2191+
}
2192+
21502193
#[test]
21512194
fn doctor_reports_local_config_validation_failures() -> Result<()> {
21522195
let test_dir = TestDir::new("doctor-local-config")?;

cli/src/services/style.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,25 @@ pub fn prompt_value(text: &str) -> String {
108108
style_if_enabled(text, |s| s.yellow().to_string())
109109
}
110110

111+
#[must_use]
112+
pub fn status_tag_pass(text: &str) -> String {
113+
style_if_enabled(text, |s| s.green().to_string())
114+
}
115+
116+
#[must_use]
117+
pub fn status_tag_fail(text: &str) -> String {
118+
style_if_enabled(text, |s| s.red().to_string())
119+
}
120+
121+
#[must_use]
122+
pub fn status_tag_warn(text: &str) -> String {
123+
style_if_enabled(text, |s| s.yellow().to_string())
124+
}
125+
126+
#[must_use]
127+
pub fn status_tag_miss(text: &str) -> String {
128+
style_if_enabled(text, |s| s.blue().to_string())
129+
}
130+
111131
#[cfg(test)]
112132
mod tests;

context/plans/doctor-status-color-no-color.md

Lines changed: 105 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

context/sce/agent-trace-hook-doctor.md

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)