Skip to main content

lean_ctx/core/patterns/
maven.rs

1use regex::Regex;
2use std::sync::OnceLock;
3
4static MAVEN_DOWNLOAD_RE: OnceLock<Regex> = OnceLock::new();
5static MAVEN_PROGRESS_RE: OnceLock<Regex> = OnceLock::new();
6static GRADLE_DOWNLOAD_RE: OnceLock<Regex> = OnceLock::new();
7static GRADLE_PROGRESS_RE: OnceLock<Regex> = OnceLock::new();
8static TESTS_RUN_RE: OnceLock<Regex> = OnceLock::new();
9
10fn maven_download_re() -> &'static Regex {
11    MAVEN_DOWNLOAD_RE
12        .get_or_init(|| Regex::new(r"(?i)\[INFO\]\s+(Downloading|Downloaded)\s+").unwrap())
13}
14
15fn maven_progress_re() -> &'static Regex {
16    MAVEN_PROGRESS_RE.get_or_init(|| Regex::new(r"\[INFO\].*kB\s+\|").unwrap())
17}
18
19fn gradle_download_re() -> &'static Regex {
20    GRADLE_DOWNLOAD_RE
21        .get_or_init(|| Regex::new(r"(?i)^(Downloading|Download)\s+https?://").unwrap())
22}
23
24fn gradle_progress_re() -> &'static Regex {
25    GRADLE_PROGRESS_RE.get_or_init(|| Regex::new(r"^[<>=\s]+$|^[0-9]+%\s+EXECUTING").unwrap())
26}
27
28fn tests_run_re() -> &'static Regex {
29    TESTS_RUN_RE.get_or_init(|| Regex::new(r"Tests run:\s*\d+").unwrap())
30}
31
32fn is_maven_noise(line: &str) -> bool {
33    let t = line.trim_start();
34    if maven_download_re().is_match(t) {
35        return true;
36    }
37    if maven_progress_re().is_match(t) {
38        return true;
39    }
40    if t.contains("Progress (") && t.contains("):") && t.contains('%') {
41        return true;
42    }
43    false
44}
45
46fn is_gradle_noise(line: &str) -> bool {
47    let t = line.trim();
48    if gradle_download_re().is_match(t) {
49        return true;
50    }
51    if gradle_progress_re().is_match(t) {
52        return true;
53    }
54    let tl = t.to_ascii_lowercase();
55    if tl.starts_with("consider enabling configuration cache")
56        || tl.contains("deprecated gradle features were used")
57        || tl.starts_with("you can use '--warning-mode")
58    {
59        return true;
60    }
61    false
62}
63
64fn is_maven_or_gradle_command(command: &str) -> bool {
65    let c = command.trim();
66    let cl = c.to_ascii_lowercase();
67    cl.starts_with("mvn ")
68        || cl.starts_with("./mvnw ")
69        || cl.starts_with("mvnw ")
70        || cl.starts_with("gradle ")
71        || cl.starts_with("./gradlew ")
72        || cl.starts_with("gradlew ")
73}
74
75fn is_gradle_command(command: &str) -> bool {
76    let cl = command.trim().to_ascii_lowercase();
77    cl.starts_with("gradle ") || cl.starts_with("./gradlew ") || cl.starts_with("gradlew ")
78}
79
80pub fn compress(command: &str, output: &str) -> Option<String> {
81    if !is_maven_or_gradle_command(command) {
82        return None;
83    }
84    if is_gradle_command(command) {
85        Some(compress_gradle(output))
86    } else {
87        Some(compress_maven(output))
88    }
89}
90
91fn compress_maven(output: &str) -> String {
92    let mut kept = Vec::new();
93
94    for line in output.lines() {
95        let t = line.trim_end();
96        if t.trim().is_empty() {
97            continue;
98        }
99        if is_maven_noise(t) {
100            continue;
101        }
102
103        let tl = t.to_ascii_lowercase();
104        if tl.contains("[error]")
105            || tl.contains("[fatal]")
106            || tl.contains("build failure")
107            || tl.contains("build success")
108            || tl.contains("failure!")
109            || tl.contains("tests run:")
110            || tl.contains("failures:")
111            || tl.contains("errors:")
112            || tl.contains("skipped:")
113            || tests_run_re().is_match(t)
114        {
115            kept.push(t.trim().to_string());
116            continue;
117        }
118
119        if tl.contains("[warning]") {
120            kept.push(t.trim().to_string());
121        }
122    }
123
124    if kept.is_empty() {
125        "mvn (no build/test lines kept)".to_string()
126    } else {
127        kept.join("\n")
128    }
129}
130
131fn compress_gradle(output: &str) -> String {
132    let mut kept = Vec::new();
133    let mut task_lines = Vec::new();
134
135    for line in output.lines() {
136        let t = line.trim_end();
137        if t.trim().is_empty() {
138            continue;
139        }
140        if is_gradle_noise(t) {
141            continue;
142        }
143
144        let tl = t.to_ascii_lowercase();
145        if tl.starts_with("> task ") {
146            if tl.contains("failed")
147                || tl.contains("failure")
148                || tl.contains("skipped")
149                || tl.contains("no-source")
150            {
151                task_lines.push(t.trim().to_string());
152            }
153            continue;
154        }
155
156        if tl.contains("actionable tasks:") {
157            kept.push(t.trim().to_string());
158            continue;
159        }
160
161        if tl.contains("build successful")
162            || tl.contains("build failed")
163            || tl.starts_with("failure:")
164            || tl.contains("what went wrong:")
165            || tl.contains("execution failed for task")
166            || tl.contains("error:")
167            || tl.contains("exception")
168            || tl.contains("tests completed:")
169            || (tl.contains("test ") && (tl.contains("failed") || tl.contains("passed")))
170            || tl.contains("there were failing tests")
171        {
172            kept.push(t.trim().to_string());
173        }
174    }
175
176    if !task_lines.is_empty() {
177        kept.push("tasks:\n".to_string() + &task_lines.join("\n"));
178    }
179
180    if kept.is_empty() {
181        "gradle (no summary kept)".to_string()
182    } else {
183        kept.join("\n")
184    }
185}