Skip to main content

lean_ctx/core/patterns/
maven.rs

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