Skip to main content

lean_ctx/core/patterns/
mod.rs

1pub 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 ") || c.starts_with("bunx ") {
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        || c.starts_with("vp ")
175        || c.starts_with("vite-plus ")
176    {
177        return next_build::compress(c, output);
178    }
179    if c.starts_with("tsc") || c.contains("typescript") {
180        return typescript::compress(output);
181    }
182    if c.starts_with("rubocop")
183        || c.starts_with("bundle ")
184        || c.starts_with("rake ")
185        || c.starts_with("rails test")
186        || c.starts_with("rspec")
187    {
188        return ruby::compress(c, output);
189    }
190    if c.starts_with("grep ") || c.starts_with("rg ") {
191        return grep::compress(output);
192    }
193    if c.starts_with("find ") {
194        return find::compress(output);
195    }
196    if c.starts_with("ls ") || c == "ls" {
197        return ls::compress(output);
198    }
199    if c.starts_with("curl ") {
200        return curl::compress_with_cmd(c, output);
201    }
202    if c.starts_with("wget ") {
203        return wget::compress(output);
204    }
205    if c == "env" || c.starts_with("env ") || c.starts_with("printenv") {
206        return env_filter::compress(output);
207    }
208    if c.starts_with("dotnet ") {
209        return dotnet::compress(c, output);
210    }
211    if c.starts_with("flutter ")
212        || (c.starts_with("dart ") && (c.contains(" analyze") || c.ends_with(" analyze")))
213    {
214        return flutter::compress(c, output);
215    }
216    if c.starts_with("poetry ")
217        || c.starts_with("uv sync")
218        || (c.starts_with("uv ") && c.contains("pip install"))
219        || c.starts_with("conda ")
220        || c.starts_with("mamba ")
221        || c.starts_with("pipx ")
222    {
223        return poetry::compress(c, output);
224    }
225    if c.starts_with("aws ") {
226        return aws::compress(c, output);
227    }
228    if c.starts_with("psql ") || c.starts_with("pg_") {
229        return psql::compress(c, output);
230    }
231    if c.starts_with("mysql ") || c.starts_with("mariadb ") {
232        return mysql::compress(c, output);
233    }
234    if c.starts_with("prisma ") || c.starts_with("npx prisma") {
235        return prisma::compress(c, output);
236    }
237    if c.starts_with("swift ") {
238        return swift::compress(c, output);
239    }
240    if c.starts_with("zig ") {
241        return zig::compress(c, output);
242    }
243    if c.starts_with("cmake ") || c.starts_with("ctest") {
244        return cmake::compress(c, output);
245    }
246    if c.starts_with("ansible") || c.starts_with("ansible-playbook") {
247        return ansible::compress(c, output);
248    }
249    if c.starts_with("composer ") {
250        return composer::compress(c, output);
251    }
252    if c.starts_with("php artisan") || c.starts_with("artisan ") {
253        return artisan::compress(c, output);
254    }
255    if c.starts_with("./vendor/bin/pest") || c.starts_with("pest ") {
256        return artisan::compress("php artisan test", output);
257    }
258    if c.starts_with("mix ") || c.starts_with("iex ") {
259        return mix::compress(c, output);
260    }
261    if c.starts_with("bazel ") || c.starts_with("blaze ") {
262        return bazel::compress(c, output);
263    }
264    if c.starts_with("systemctl ") || c.starts_with("journalctl") {
265        return systemd::compress(c, output);
266    }
267
268    None
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274
275    #[test]
276    fn routes_git_commands() {
277        let output = "On branch main\nnothing to commit";
278        assert!(compress_output("git status", output).is_some());
279    }
280
281    #[test]
282    fn routes_cargo_commands() {
283        let output = "   Compiling lean-ctx v2.1.1\n    Finished `release` profile [optimized] target(s) in 30.5s";
284        assert!(compress_output("cargo build --release", output).is_some());
285    }
286
287    #[test]
288    fn routes_npm_commands() {
289        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";
290        assert!(compress_output("npm install", output).is_some());
291    }
292
293    #[test]
294    fn routes_docker_commands() {
295        let output = "CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES";
296        assert!(compress_output("docker ps", output).is_some());
297    }
298
299    #[test]
300    fn routes_mypy_commands() {
301        let output = "src/main.py:10: error: Missing return  [return]\nFound 1 error in 1 file (checked 3 source files)";
302        assert!(compress_output("mypy .", output).is_some());
303        assert!(compress_output("python -m mypy src/", output).is_some());
304    }
305
306    #[test]
307    fn routes_pytest_commands() {
308        let output = "===== test session starts =====\ncollected 5 items\ntest_main.py ..... [100%]\n===== 5 passed in 0.5s =====";
309        assert!(compress_output("pytest", output).is_some());
310        assert!(compress_output("python -m pytest tests/", output).is_some());
311    }
312
313    #[test]
314    fn unknown_command_returns_none() {
315        assert!(compress_output("some-unknown-tool --version", "v1.0").is_none());
316    }
317
318    #[test]
319    fn case_insensitive_routing() {
320        let output = "On branch main\nnothing to commit";
321        assert!(compress_output("Git Status", output).is_some());
322        assert!(compress_output("GIT STATUS", output).is_some());
323    }
324
325    #[test]
326    fn routes_vp_and_vite_plus() {
327        let output = "  VITE v5.0.0  ready in 200 ms\n\n  -> Local:   http://localhost:5173/\n  -> Network: http://192.168.1.2:5173/";
328        assert!(compress_output("vp build", output).is_some());
329        assert!(compress_output("vite-plus build", output).is_some());
330    }
331
332    #[test]
333    fn routes_bunx_commands() {
334        let output = "1 pass tests\nDone 12ms";
335        let compressed = compress_output("bunx test", output).unwrap();
336        assert!(compressed.contains("bun test: 1 passed"));
337    }
338
339    #[test]
340    fn routes_deno_task() {
341        let output = "Task dev deno run --allow-net server.ts\nListening on http://localhost:8000";
342        assert!(try_specific_pattern("deno task dev", output).is_some());
343    }
344}