1pub 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("test") {
10 return Some(compress_test(trimmed));
11 }
12 if cmd.contains("deps.get") || cmd.contains("deps.compile") {
13 return Some(compress_deps(trimmed));
14 }
15 if cmd.contains("compile") || cmd.contains("build") {
16 return Some(compress_compile(trimmed));
17 }
18 if cmd.contains("format") || cmd.contains("fmt") {
19 return Some(compress_format(trimmed));
20 }
21 if cmd.contains("credo") || cmd.contains("dialyzer") {
22 return Some(compress_lint(trimmed));
23 }
24
25 Some(compact_lines(trimmed, 15))
26}
27
28fn compress_test(output: &str) -> String {
29 let summary = output
30 .lines()
31 .rev()
32 .find(|l| l.contains("test") && (l.contains("passed") || l.contains("failure")));
33
34 if let Some(s) = summary {
35 let mut result = format!("mix test: {}", s.trim());
36 let failures: Vec<&str> = output
37 .lines()
38 .filter(|l| {
39 l.trim().starts_with("1)")
40 || l.trim().starts_with("2)")
41 || l.trim().starts_with("3)")
42 })
43 .collect();
44 for f in failures.iter().take(5) {
45 result.push_str(&format!("\n {}", f.trim()));
46 }
47 return result;
48 }
49 compact_lines(output, 10)
50}
51
52fn compress_deps(output: &str) -> String {
53 let mut resolved = 0u32;
54 let mut compiled = 0u32;
55
56 for line in output.lines() {
57 if line.contains("Resolving") || line.contains("resolving") {
58 resolved += 1;
59 }
60 if line.contains("Compiling") || line.contains("compiling") {
61 compiled += 1;
62 }
63 }
64
65 if resolved == 0 && compiled == 0 {
66 return compact_lines(output, 5);
67 }
68 format!("deps: {resolved} resolved, {compiled} compiled")
69}
70
71fn compress_compile(output: &str) -> String {
72 let mut compiled = 0u32;
73 let mut warnings = 0u32;
74 let mut errors = Vec::new();
75
76 for line in output.lines() {
77 let trimmed = line.trim();
78 if trimmed.starts_with("Compiling") || trimmed.starts_with("Compiled") {
79 compiled += 1;
80 }
81 if trimmed.contains("warning:") {
82 warnings += 1;
83 }
84 if trimmed.contains("error") && trimmed.contains("**") {
85 errors.push(trimmed.to_string());
86 }
87 }
88
89 if !errors.is_empty() {
90 let mut result = format!("{} errors", errors.len());
91 for e in errors.iter().take(10) {
92 result.push_str(&format!("\n {e}"));
93 }
94 return result;
95 }
96
97 let mut result = format!("{compiled} compiled");
98 if warnings > 0 {
99 result.push_str(&format!(", {warnings} warnings"));
100 }
101 result
102}
103
104fn compress_format(output: &str) -> String {
105 let files: Vec<&str> = output.lines().filter(|l| !l.trim().is_empty()).collect();
106 if files.is_empty() {
107 return "ok (formatted)".to_string();
108 }
109 format!("{} files", files.len())
110}
111
112fn compress_lint(output: &str) -> String {
113 let issues: Vec<&str> = output
114 .lines()
115 .filter(|l| {
116 let t = l.trim();
117 t.contains("┃") || t.starts_with("warning:") || t.starts_with("error:")
118 })
119 .collect();
120
121 if issues.is_empty() {
122 if output.contains("no issues") || output.contains("Analysis finished") {
123 return "clean".to_string();
124 }
125 return compact_lines(output, 10);
126 }
127 format!(
128 "{} issues:\n{}",
129 issues.len(),
130 issues
131 .iter()
132 .take(10)
133 .map(|i| format!(" {}", i.trim()))
134 .collect::<Vec<_>>()
135 .join("\n")
136 )
137}
138
139fn compact_lines(text: &str, max: usize) -> String {
140 let lines: Vec<&str> = text.lines().filter(|l| !l.trim().is_empty()).collect();
141 if lines.len() <= max {
142 return lines.join("\n");
143 }
144 format!(
145 "{}\n{}",
146 lines[..max].join("\n"),
147 super::elision_marker(lines.len() - max)
148 )
149}