Skip to main content

lean_ctx_patterns/
cmake.rs

1// SPDX-License-Identifier: Apache-2.0
2
3pub fn compress(cmd: &str, output: &str) -> Option<String> {
4    let trimmed = output.trim();
5    if trimmed.is_empty() {
6        return Some("ok".to_string());
7    }
8
9    if cmd.contains("--build") || cmd.contains("make") {
10        return Some(compress_build(trimmed));
11    }
12    if cmd.contains("ctest") || cmd.contains("test") {
13        return Some(compress_test(trimmed));
14    }
15
16    Some(compress_configure(trimmed))
17}
18
19fn compress_configure(output: &str) -> String {
20    let mut found_generators = Vec::new();
21    let mut found_compilers = Vec::new();
22    let mut warnings = 0u32;
23    let mut errors = Vec::new();
24
25    for line in output.lines() {
26        let trimmed = line.trim();
27        if trimmed.starts_with("-- The") && trimmed.contains("compiler") {
28            found_compilers.push(trimmed.to_string());
29        }
30        if trimmed.starts_with("-- Generating")
31            || (trimmed.starts_with("--") && trimmed.contains("generator"))
32        {
33            found_generators.push(trimmed.to_string());
34        }
35        if trimmed.contains("CMake Warning") || trimmed.starts_with("WARNING:") {
36            warnings += 1;
37        }
38        if trimmed.contains("CMake Error") || trimmed.starts_with("ERROR:") {
39            errors.push(trimmed.to_string());
40        }
41    }
42
43    if !errors.is_empty() {
44        let mut result = format!("{} errors:", errors.len());
45        for e in errors.iter().take(10) {
46            result.push_str(&format!("\n  {e}"));
47        }
48        return result;
49    }
50
51    let success =
52        output.contains("Configuring done") || output.contains("Build files have been written");
53    let mut result = if success {
54        "CMake configured ok".to_string()
55    } else {
56        "CMake configure:".to_string()
57    };
58    if warnings > 0 {
59        result.push_str(&format!(" ({warnings} warnings)"));
60    }
61    if !found_compilers.is_empty() {
62        result.push_str(&format!("\n  compilers: {}", found_compilers.len()));
63    }
64    result
65}
66
67fn compress_build(output: &str) -> String {
68    let lines: Vec<&str> = output.lines().collect();
69    let errors: Vec<&&str> = lines
70        .iter()
71        .filter(|l| l.contains("error:") || l.contains("Error:"))
72        .collect();
73    let warnings: Vec<&&str> = lines
74        .iter()
75        .filter(|l| l.contains("warning:") || l.contains("Warning:"))
76        .collect();
77
78    let progress_lines: Vec<&&str> = lines
79        .iter()
80        .filter(|l| l.trim().starts_with('[') && l.contains(']'))
81        .collect();
82
83    if !errors.is_empty() {
84        let mut result = format!("{} errors", errors.len());
85        if !warnings.is_empty() {
86            result.push_str(&format!(", {} warnings", warnings.len()));
87        }
88        for e in errors.iter().take(10) {
89            result.push_str(&format!("\n  {}", e.trim()));
90        }
91        return result;
92    }
93
94    let mut result = format!("Build ok ({} steps", progress_lines.len().max(lines.len()));
95    if !warnings.is_empty() {
96        result.push_str(&format!(", {} warnings", warnings.len()));
97    }
98    result.push(')');
99    result
100}
101
102fn compress_test(output: &str) -> String {
103    let mut passed = 0u32;
104    let mut failed = 0u32;
105    let mut failures = Vec::new();
106
107    for line in output.lines() {
108        let trimmed = line.trim();
109        if trimmed.contains("Passed") {
110            for word in trimmed.split_whitespace() {
111                if let Ok(n) = word.parse::<u32>() {
112                    passed = n;
113                    break;
114                }
115            }
116        }
117        if trimmed.contains("Failed") && !trimmed.starts_with("The following") {
118            for word in trimmed.split_whitespace() {
119                if let Ok(n) = word.parse::<u32>() {
120                    failed = n;
121                    break;
122                }
123            }
124        }
125        if trimmed.starts_with("Failed")
126            || (trimmed.contains("***Failed") || trimmed.contains("***Exception"))
127        {
128            failures.push(trimmed.to_string());
129        }
130    }
131
132    let summary_line = output
133        .lines()
134        .rev()
135        .find(|l| l.contains("tests passed") || l.contains("% tests passed"));
136    if let Some(summary) = summary_line {
137        let mut result = format!("ctest: {}", summary.trim());
138        for f in failures.iter().take(5) {
139            result.push_str(&format!("\n  {f}"));
140        }
141        return result;
142    }
143
144    if passed == 0 && failed == 0 {
145        return compact_lines(output, 10);
146    }
147
148    let mut result = format!("ctest: {passed} passed");
149    if failed > 0 {
150        result.push_str(&format!(", {failed} failed"));
151    }
152    for f in failures.iter().take(5) {
153        result.push_str(&format!("\n  {f}"));
154    }
155    result
156}
157
158fn compact_lines(text: &str, max: usize) -> String {
159    let lines: Vec<&str> = text.lines().filter(|l| !l.trim().is_empty()).collect();
160    if lines.len() <= max {
161        return lines.join("\n");
162    }
163    format!(
164        "{}\n{}",
165        lines[..max].join("\n"),
166        super::elision_marker(lines.len() - max)
167    )
168}