lean_ctx/core/patterns/
mod.rs1pub mod ansible;
2pub mod artisan;
3pub mod aws;
4pub mod bazel;
5pub mod bun;
6pub mod cargo;
7pub mod cmake;
8pub mod composer;
9pub mod curl;
10pub mod deno;
11pub mod deps_cmd;
12pub mod docker;
13pub mod dotnet;
14pub mod env_filter;
15pub mod eslint;
16pub mod find;
17pub mod flutter;
18pub mod gh;
19pub mod git;
20pub mod golang;
21pub mod grep;
22pub mod helm;
23pub mod json_schema;
24pub mod kubectl;
25pub mod log_dedup;
26pub mod ls;
27pub mod make;
28pub mod maven;
29pub mod mix;
30pub mod mypy;
31pub mod mysql;
32pub mod next_build;
33pub mod npm;
34pub mod php;
35pub mod pip;
36pub mod playwright;
37pub mod pnpm;
38pub mod poetry;
39pub mod prettier;
40pub mod prisma;
41pub mod psql;
42pub mod ruby;
43pub mod ruff;
44pub mod swift;
45pub mod systemd;
46pub mod terraform;
47pub mod test;
48pub mod typescript;
49pub mod wget;
50pub mod zig;
51
52pub fn compress_output(command: &str, output: &str) -> Option<String> {
53 let cleaned = crate::core::compressor::strip_ansi(output);
54 let output = if cleaned.len() < output.len() {
55 &cleaned
56 } else {
57 output
58 };
59
60 if let Some(engine) = crate::core::filters::FilterEngine::load() {
61 if let Some(filtered) = engine.apply(command, output) {
62 return Some(filtered);
63 }
64 }
65
66 let specific = try_specific_pattern(command, output);
67 if specific.is_some() {
68 return specific;
69 }
70
71 if let Some(r) = json_schema::compress(output) {
72 return Some(r);
73 }
74
75 if let Some(r) = log_dedup::compress(output) {
76 return Some(r);
77 }
78
79 if let Some(r) = test::compress(output) {
80 return Some(r);
81 }
82
83 None
84}
85
86fn try_specific_pattern(cmd: &str, output: &str) -> Option<String> {
87 let cl = cmd.to_ascii_lowercase();
88 let c = cl.as_str();
89
90 if c.starts_with("git ") {
91 return git::compress(c, output);
92 }
93 if c.starts_with("gh ") {
94 return gh::compress(c, output);
95 }
96 if c == "terraform" || c.starts_with("terraform ") {
97 return terraform::compress(c, output);
98 }
99 if c == "make" || c.starts_with("make ") {
100 return make::compress(c, output);
101 }
102 if c.starts_with("mvn ")
103 || c.starts_with("./mvnw ")
104 || c.starts_with("mvnw ")
105 || c.starts_with("gradle ")
106 || c.starts_with("./gradlew ")
107 || c.starts_with("gradlew ")
108 {
109 return maven::compress(c, output);
110 }
111 if c.starts_with("kubectl ") || c.starts_with("k ") {
112 return kubectl::compress(c, output);
113 }
114 if c.starts_with("helm ") {
115 return helm::compress(c, output);
116 }
117 if c.starts_with("pnpm ") {
118 return pnpm::compress(c, output);
119 }
120 if c.starts_with("bun ") {
121 return bun::compress(c, output);
122 }
123 if c.starts_with("deno ") {
124 return deno::compress(c, output);
125 }
126 if c.starts_with("npm ") || c.starts_with("yarn ") {
127 return npm::compress(c, output);
128 }
129 if c.starts_with("cargo ") {
130 return cargo::compress(c, output);
131 }
132 if c.starts_with("docker ") || c.starts_with("docker-compose ") {
133 return docker::compress(c, output);
134 }
135 if c.starts_with("pip ") || c.starts_with("pip3 ") || c.starts_with("python -m pip") {
136 return pip::compress(c, output);
137 }
138 if c.starts_with("mypy") || c.starts_with("python -m mypy") || c.starts_with("dmypy ") {
139 return mypy::compress(c, output);
140 }
141 if c.starts_with("pytest") || c.starts_with("python -m pytest") {
142 return test::compress(output);
143 }
144 if c.starts_with("ruff ") {
145 return ruff::compress(c, output);
146 }
147 if c.starts_with("eslint")
148 || c.starts_with("npx eslint")
149 || c.starts_with("biome ")
150 || c.starts_with("stylelint")
151 {
152 return eslint::compress(c, output);
153 }
154 if c.starts_with("prettier") || c.starts_with("npx prettier") {
155 return prettier::compress(output);
156 }
157 if c.starts_with("go ") || c.starts_with("golangci-lint") || c.starts_with("golint") {
158 return golang::compress(c, output);
159 }
160 if c.starts_with("playwright")
161 || c.starts_with("npx playwright")
162 || c.starts_with("cypress")
163 || c.starts_with("npx cypress")
164 {
165 return playwright::compress(c, output);
166 }
167 if c.starts_with("vitest") || c.starts_with("npx vitest") || c.starts_with("pnpm vitest") {
168 return test::compress(output);
169 }
170 if c.starts_with("next ")
171 || c.starts_with("npx next")
172 || c.starts_with("vite ")
173 || c.starts_with("npx vite")
174 {
175 return next_build::compress(c, output);
176 }
177 if c.starts_with("tsc") || c.contains("typescript") {
178 return typescript::compress(output);
179 }
180 if c.starts_with("rubocop")
181 || c.starts_with("bundle ")
182 || c.starts_with("rake ")
183 || c.starts_with("rails test")
184 || c.starts_with("rspec")
185 {
186 return ruby::compress(c, output);
187 }
188 if c.starts_with("grep ") || c.starts_with("rg ") {
189 return grep::compress(output);
190 }
191 if c.starts_with("find ") {
192 return find::compress(output);
193 }
194 if c.starts_with("ls ") || c == "ls" {
195 return ls::compress(output);
196 }
197 if c.starts_with("curl ") {
198 return curl::compress_with_cmd(c, output);
199 }
200 if c.starts_with("wget ") {
201 return wget::compress(output);
202 }
203 if c == "env" || c.starts_with("env ") || c.starts_with("printenv") {
204 return env_filter::compress(output);
205 }
206 if c.starts_with("dotnet ") {
207 return dotnet::compress(c, output);
208 }
209 if c.starts_with("flutter ")
210 || (c.starts_with("dart ") && (c.contains(" analyze") || c.ends_with(" analyze")))
211 {
212 return flutter::compress(c, output);
213 }
214 if c.starts_with("poetry ")
215 || c.starts_with("uv sync")
216 || (c.starts_with("uv ") && c.contains("pip install"))
217 || c.starts_with("conda ")
218 || c.starts_with("mamba ")
219 || c.starts_with("pipx ")
220 {
221 return poetry::compress(c, output);
222 }
223 if c.starts_with("aws ") {
224 return aws::compress(c, output);
225 }
226 if c.starts_with("psql ") || c.starts_with("pg_") {
227 return psql::compress(c, output);
228 }
229 if c.starts_with("mysql ") || c.starts_with("mariadb ") {
230 return mysql::compress(c, output);
231 }
232 if c.starts_with("prisma ") || c.starts_with("npx prisma") {
233 return prisma::compress(c, output);
234 }
235 if c.starts_with("swift ") {
236 return swift::compress(c, output);
237 }
238 if c.starts_with("zig ") {
239 return zig::compress(c, output);
240 }
241 if c.starts_with("cmake ") || c.starts_with("ctest") {
242 return cmake::compress(c, output);
243 }
244 if c.starts_with("ansible") || c.starts_with("ansible-playbook") {
245 return ansible::compress(c, output);
246 }
247 if c.starts_with("composer ") {
248 return composer::compress(c, output);
249 }
250 if c.starts_with("php artisan") || c.starts_with("artisan ") {
251 return artisan::compress(c, output);
252 }
253 if c.starts_with("./vendor/bin/pest") || c.starts_with("pest ") {
254 return artisan::compress("php artisan test", output);
255 }
256 if c.starts_with("mix ") || c.starts_with("iex ") {
257 return mix::compress(c, output);
258 }
259 if c.starts_with("bazel ") || c.starts_with("blaze ") {
260 return bazel::compress(c, output);
261 }
262 if c.starts_with("systemctl ") || c.starts_with("journalctl") {
263 return systemd::compress(c, output);
264 }
265
266 None
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn routes_git_commands() {
275 let output = "On branch main\nnothing to commit";
276 assert!(compress_output("git status", output).is_some());
277 }
278
279 #[test]
280 fn routes_cargo_commands() {
281 let output = " Compiling lean-ctx v2.1.1\n Finished `release` profile [optimized] target(s) in 30.5s";
282 assert!(compress_output("cargo build --release", output).is_some());
283 }
284
285 #[test]
286 fn routes_npm_commands() {
287 let output = "added 150 packages, and audited 151 packages in 5s\n\n25 packages are looking for funding\n run `npm fund` for details\n\nfound 0 vulnerabilities";
288 assert!(compress_output("npm install", output).is_some());
289 }
290
291 #[test]
292 fn routes_docker_commands() {
293 let output = "CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES";
294 assert!(compress_output("docker ps", output).is_some());
295 }
296
297 #[test]
298 fn routes_mypy_commands() {
299 let output = "src/main.py:10: error: Missing return [return]\nFound 1 error in 1 file (checked 3 source files)";
300 assert!(compress_output("mypy .", output).is_some());
301 assert!(compress_output("python -m mypy src/", output).is_some());
302 }
303
304 #[test]
305 fn routes_pytest_commands() {
306 let output = "===== test session starts =====\ncollected 5 items\ntest_main.py ..... [100%]\n===== 5 passed in 0.5s =====";
307 assert!(compress_output("pytest", output).is_some());
308 assert!(compress_output("python -m pytest tests/", output).is_some());
309 }
310
311 #[test]
312 fn unknown_command_returns_none() {
313 assert!(compress_output("some-unknown-tool --version", "v1.0").is_none());
314 }
315
316 #[test]
317 fn case_insensitive_routing() {
318 let output = "On branch main\nnothing to commit";
319 assert!(compress_output("Git Status", output).is_some());
320 assert!(compress_output("GIT STATUS", output).is_some());
321 }
322}