Skip to main content

double_o/pattern/
builtins.rs

1use regex::Regex;
2
3use super::{FailurePattern, FailureStrategy, Pattern, SuccessPattern, SuccessStrategy};
4
5/// Built-in pattern definitions for common commands.
6pub fn builtin_patterns() -> Vec<Pattern> {
7    vec![
8        // pytest
9        Pattern {
10            command_match: Regex::new(r"(?:^|\b)pytest\b")
11                .expect("valid regex: pytest command_match"),
12            success: Some(SuccessPattern {
13                strategy: SuccessStrategy::Regex {
14                    pattern: Regex::new(r"(?P<passed>\d+) passed.*in (?P<time>[\d.]+)s")
15                        .expect("valid regex: pytest success pattern"),
16                    summary: "{passed} passed, {time}s".into(),
17                },
18            }),
19            failure: Some(FailurePattern {
20                strategy: FailureStrategy::Tail { lines: 30 },
21            }),
22        },
23        // cargo test
24        Pattern {
25            command_match: Regex::new(r"\bcargo\s+test\b")
26                .expect("valid regex: cargo test command_match"),
27            success: Some(SuccessPattern {
28                strategy: SuccessStrategy::Regex {
29                    pattern: Regex::new(
30                        r"test result: ok\. (?P<passed>\d+) passed; (?P<failed>\d+) failed.*finished in (?P<time>[\d.]+)s",
31                    )
32                    .expect("valid regex: cargo test success pattern"),
33                    summary: "{passed} passed, {time}s".into(),
34                },
35            }),
36            failure: Some(FailurePattern {
37                strategy: FailureStrategy::Tail { lines: 40 },
38            }),
39        },
40        // go test
41        Pattern {
42            command_match: Regex::new(r"\bgo\s+test\b")
43                .expect("valid regex: go test command_match"),
44            success: Some(SuccessPattern {
45                strategy: SuccessStrategy::Regex {
46                    pattern: Regex::new(r"ok\s+\S+\s+(?P<time>[\d.]+)s")
47                        .expect("valid regex: go test success pattern"),
48                    summary: "ok ({time}s)".into(),
49                },
50            }),
51            failure: Some(FailurePattern {
52                strategy: FailureStrategy::Tail { lines: 30 },
53            }),
54        },
55        // jest / vitest
56        Pattern {
57            command_match: Regex::new(r"\b(?:jest|vitest|npx\s+(?:jest|vitest))\b")
58                .expect("valid regex: jest/vitest command_match"),
59            success: Some(SuccessPattern {
60                strategy: SuccessStrategy::Regex {
61                    pattern: Regex::new(
62                        r"Tests:\s+(?P<passed>\d+) passed.*Time:\s+(?P<time>[\d.]+)\s*s",
63                    )
64                    .expect("valid regex: jest/vitest success pattern"),
65                    summary: "{passed} passed, {time}s".into(),
66                },
67            }),
68            failure: Some(FailurePattern {
69                strategy: FailureStrategy::Tail { lines: 30 },
70            }),
71        },
72        // ruff
73        Pattern {
74            command_match: Regex::new(r"\bruff\s+check\b")
75                .expect("valid regex: ruff check command_match"),
76            success: Some(SuccessPattern {
77                strategy: SuccessStrategy::Regex {
78                    pattern: Regex::new(r"All checks passed")
79                        .expect("valid regex: ruff check success pattern"),
80                    summary: String::new(), // empty = quiet success
81                },
82            }),
83            failure: None, // show all violations
84        },
85        // eslint
86        Pattern {
87            command_match: Regex::new(r"\beslint\b")
88                .expect("valid regex: eslint command_match"),
89            success: Some(SuccessPattern {
90                strategy: SuccessStrategy::Regex {
91                    pattern: Regex::new(r"(?s).*")
92                        .expect("valid regex: eslint success pattern (always matches)"),
93                    summary: String::new(),
94                },
95            }),
96            failure: None,
97        },
98        // cargo build
99        Pattern {
100            command_match: Regex::new(r"\bcargo\s+build\b")
101                .expect("valid regex: cargo build command_match"),
102            success: Some(SuccessPattern {
103                strategy: SuccessStrategy::Regex {
104                    pattern: Regex::new(r"(?s).*")
105                        .expect("valid regex: cargo build success pattern (always matches)"),
106                    summary: String::new(),
107                },
108            }),
109            failure: Some(FailurePattern {
110                strategy: FailureStrategy::Head { lines: 20 },
111            }),
112        },
113        // go build
114        Pattern {
115            command_match: Regex::new(r"\bgo\s+build\b")
116                .expect("valid regex: go build command_match"),
117            success: Some(SuccessPattern {
118                strategy: SuccessStrategy::Regex {
119                    pattern: Regex::new(r"(?s).*")
120                        .expect("valid regex: go build success pattern (always matches)"),
121                    summary: String::new(),
122                },
123            }),
124            failure: Some(FailurePattern {
125                strategy: FailureStrategy::Head { lines: 20 },
126            }),
127        },
128        // tsc
129        Pattern {
130            command_match: Regex::new(r"\btsc\b")
131                .expect("valid regex: tsc command_match"),
132            success: Some(SuccessPattern {
133                strategy: SuccessStrategy::Regex {
134                    pattern: Regex::new(r"(?s).*")
135                        .expect("valid regex: tsc success pattern (always matches)"),
136                    summary: String::new(),
137                },
138            }),
139            failure: Some(FailurePattern {
140                strategy: FailureStrategy::Head { lines: 20 },
141            }),
142        },
143        // cargo clippy
144        Pattern {
145            command_match: Regex::new(r"\bcargo\s+clippy\b")
146                .expect("valid regex: cargo clippy command_match"),
147            success: Some(SuccessPattern {
148                strategy: SuccessStrategy::Regex {
149                    pattern: Regex::new(r"(?s).*")
150                        .expect("valid regex: cargo clippy success pattern (always matches)"),
151                    summary: String::new(),
152                },
153            }),
154            failure: None,
155        },
156        // npm/yarn/pnpm/bun test (shared pattern)
157        nodejs_test_pattern(r"npm\s+test"),
158        nodejs_test_pattern(r"yarn\s+test"),
159        nodejs_test_pattern(r"pnpm\s+test"),
160        nodejs_test_pattern(r"bun\s+test"),
161        // cargo tarpaulin (coverage)
162        Pattern {
163            command_match: Regex::new(r"\bcargo\s+tarpaulin\b")
164                .expect("valid regex: cargo tarpaulin command_match"),
165            success: Some(SuccessPattern {
166                strategy: SuccessStrategy::Regex {
167                    pattern: Regex::new(r"Overall coverage: (?P<cov>[\d.]+)%")
168                        .expect("valid regex: cargo tarpaulin success pattern"),
169                    summary: "{cov}% coverage".into(),
170                },
171            }),
172            failure: Some(FailurePattern {
173                strategy: FailureStrategy::Tail { lines: 20 },
174            }),
175        },
176        // cargo fmt
177        Pattern {
178            command_match: Regex::new(r"\bcargo\s+fmt\b")
179                .expect("valid regex: cargo fmt command_match"),
180            success: Some(SuccessPattern {
181                strategy: SuccessStrategy::Regex {
182                    pattern: Regex::new(r"(?s).*")
183                        .expect("valid regex: cargo fmt success pattern (always matches)"),
184                    summary: String::new(),
185                },
186            }),
187            failure: Some(FailurePattern {
188                strategy: FailureStrategy::Grep {
189                    pattern: Regex::new(r"Diff in").expect("valid regex: cargo fmt failure pattern"),
190                },
191            }),
192        },
193        // mypy
194        Pattern {
195            command_match: Regex::new(r"\bmypy\b")
196                .expect("valid regex: mypy command_match"),
197            success: Some(SuccessPattern {
198                strategy: SuccessStrategy::Regex {
199                    pattern: Regex::new(r"Success: no issues found")
200                        .expect("valid regex: mypy success pattern"),
201                    summary: "Success: no issues found".into(),
202                },
203            }),
204            failure: Some(FailurePattern {
205                strategy: FailureStrategy::Head { lines: 20 },
206            }),
207        },
208        // rubocop
209        Pattern {
210            command_match: Regex::new(r"\brubocop\b")
211                .expect("valid regex: rubocop command_match"),
212            success: Some(SuccessPattern {
213                strategy: SuccessStrategy::Regex {
214                    pattern: Regex::new(r"(?P<offenses>\d+) offenses detected")
215                        .expect("valid regex: rubocop success pattern"),
216                    summary: "{offenses} offenses".into(),
217                },
218            }),
219            failure: Some(FailurePattern {
220                strategy: FailureStrategy::Head { lines: 30 },
221            }),
222        },
223        // ruff format
224        Pattern {
225            command_match: Regex::new(r"\bruff\s+format\b")
226                .expect("valid regex: ruff format command_match"),
227            success: Some(SuccessPattern {
228                strategy: SuccessStrategy::Regex {
229                    pattern: Regex::new(r"(?P<count>\d+) files reformatted")
230                        .expect("valid regex: ruff format success pattern"),
231                    summary: "{count} files reformatted".into(),
232                },
233            }),
234            failure: Some(FailurePattern {
235                strategy: FailureStrategy::Head { lines: 20 },
236            }),
237        },
238        // prettier
239        Pattern {
240            command_match: Regex::new(r"\bprettier\b")
241                .expect("valid regex: prettier command_match"),
242            success: Some(SuccessPattern {
243                strategy: SuccessStrategy::Regex {
244                    pattern: Regex::new(r"(?s).*")
245                        .expect("valid regex: prettier success pattern (always matches)"),
246                    summary: String::new(),
247                },
248            }),
249            failure: Some(FailurePattern {
250                strategy: FailureStrategy::Head { lines: 20 },
251            }),
252        },
253        // npm run build
254        Pattern {
255            command_match: Regex::new(r"\bnpm\s+run\s+build\b")
256                .expect("valid regex: npm run build command_match"),
257            success: Some(SuccessPattern {
258                strategy: SuccessStrategy::Regex {
259                    pattern: Regex::new(r"(?s).*")
260                        .expect("valid regex: npm run build success pattern (always matches)"),
261                    summary: String::new(),
262                },
263            }),
264            failure: Some(FailurePattern {
265                strategy: FailureStrategy::Head { lines: 20 },
266            }),
267        },
268        // yarn build
269        Pattern {
270            command_match: Regex::new(r"\byarn\s+build\b")
271                .expect("valid regex: yarn build command_match"),
272            success: Some(SuccessPattern {
273                strategy: SuccessStrategy::Regex {
274                    pattern: Regex::new(r"Done in (?P<time>[\d.]+)s")
275                        .expect("valid regex: yarn build success pattern"),
276                    summary: "Done in {time}s".into(),
277                },
278            }),
279            failure: Some(FailurePattern {
280                strategy: FailureStrategy::Head { lines: 20 },
281            }),
282        },
283        // pnpm build (quiet)
284        nodejs_quiet_build_pattern(r"pnpm\s+build"),
285        // bun build (quiet)
286        nodejs_quiet_build_pattern(r"bun\s+build"),
287    ]
288}
289
290/// Create a test pattern for Node.js package managers (npm/yarn/pnpm/bun).
291///
292/// All these tools report test output in the same format:
293/// - Success: "Tests: N passed... Time: X.XX s"
294/// - Failure: show last 30 lines
295fn nodejs_test_pattern(command_match: &str) -> Pattern {
296    Pattern {
297        command_match: Regex::new(command_match)
298            .unwrap_or_else(|e| panic!("valid regex: {command_match} command_match: {e}")),
299        success: Some(SuccessPattern {
300            strategy: SuccessStrategy::Regex {
301                pattern: Regex::new(
302                    r"(?s)Tests:\s+(?P<passed>\d+) passed.*Time:\s+(?P<time>[\d.]+)\s*s",
303                )
304                .expect("valid regex: nodejs test success pattern"),
305                summary: "{passed} passed, {time}s".into(),
306            },
307        }),
308        failure: Some(FailurePattern {
309            strategy: FailureStrategy::Tail { lines: 30 },
310        }),
311    }
312}
313
314/// Create a quiet build pattern for Node.js package managers (pnpm/bun).
315///
316/// These tools succeed with verbose output that should be hidden.
317/// On failure, show the first 20 lines where errors typically appear.
318fn nodejs_quiet_build_pattern(command_match: &str) -> Pattern {
319    Pattern {
320        command_match: Regex::new(command_match)
321            .unwrap_or_else(|e| panic!("valid regex: {command_match} command_match: {e}")),
322        success: Some(SuccessPattern {
323            strategy: SuccessStrategy::Regex {
324                pattern: Regex::new(r"(?s).*")
325                    .expect("valid regex: nodejs quiet build success pattern (always matches)"),
326                summary: String::new(),
327            },
328        }),
329        failure: Some(FailurePattern {
330            strategy: FailureStrategy::Head { lines: 20 },
331        }),
332    }
333}