lean_ctx/core/patterns/
ruff.rs1macro_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 ruff_line_re() -> &'static regex::Regex {
11 static_regex!(r"^(.+?):(\d+):(\d+):\s+([A-Z]\d+)\s+(.+)$")
12}
13fn ruff_fixed_re() -> &'static regex::Regex {
14 static_regex!(r"Found (\d+) errors?.*?(\d+) fixable")
15}
16
17pub fn compress(command: &str, output: &str) -> Option<String> {
18 if command.contains("format") || command.contains("fmt") {
19 return Some(compress_format(output));
20 }
21 Some(compress_check(output))
22}
23
24fn compress_check(output: &str) -> String {
25 let trimmed = output.trim();
26 if trimmed.is_empty() || trimmed.contains("All checks passed") {
27 return "clean".to_string();
28 }
29
30 let mut by_rule: std::collections::HashMap<String, u32> = std::collections::HashMap::new();
31 let mut files: std::collections::HashSet<String> = std::collections::HashSet::new();
32 let mut issue_lines: Vec<&str> = Vec::new();
33
34 for line in trimmed.lines() {
35 if let Some(caps) = ruff_line_re().captures(line) {
36 let file = caps[1].to_string();
37 let rule = caps[4].to_string();
38 files.insert(file);
39 *by_rule.entry(rule).or_insert(0) += 1;
40 issue_lines.push(line);
41 }
42 }
43
44 if by_rule.is_empty() {
45 if let Some(caps) = ruff_fixed_re().captures(trimmed) {
46 return format!("{} errors ({} fixable)", &caps[1], &caps[2]);
47 }
48 return compact_output(trimmed, 10);
49 }
50
51 let total: u32 = by_rule.values().sum();
52
53 if total <= 30 {
54 return trimmed.to_string();
55 }
56
57 let mut rules: Vec<(String, u32)> = by_rule.into_iter().collect();
58 rules.sort_by_key(|x| std::cmp::Reverse(x.1));
59
60 let mut parts = Vec::new();
61 parts.push(format!("{total} issues in {} files", files.len()));
62 for line in issue_lines.iter().take(20) {
63 parts.push(format!(" {line}"));
64 }
65 if issue_lines.len() > 20 {
66 parts.push(format!(" ... +{} more issues", issue_lines.len() - 20));
67 }
68 parts.push(String::new());
69 parts.push("by rule:".to_string());
70 for (rule, count) in rules.iter().take(8) {
71 parts.push(format!(" {rule}: {count}"));
72 }
73 if rules.len() > 8 {
74 parts.push(format!(" ... +{} more rules", rules.len() - 8));
75 }
76
77 parts.join("\n")
78}
79
80fn compress_format(output: &str) -> String {
81 let trimmed = output.trim();
82 if trimmed.is_empty() {
83 return "ok (formatted)".to_string();
84 }
85
86 let reformatted: Vec<&str> = trimmed
87 .lines()
88 .filter(|l| l.contains("reformatted") || l.contains("would reformat"))
89 .collect();
90
91 let unchanged: Vec<&str> = trimmed
92 .lines()
93 .filter(|l| l.contains("left unchanged") || l.contains("already formatted"))
94 .collect();
95
96 if !reformatted.is_empty() {
97 return format!("{} files reformatted", reformatted.len());
98 }
99 if !unchanged.is_empty() {
100 return format!("ok ({} files already formatted)", unchanged.len());
101 }
102
103 compact_output(trimmed, 5)
104}
105
106fn compact_output(text: &str, max: usize) -> String {
107 let lines: Vec<&str> = text.lines().filter(|l| !l.trim().is_empty()).collect();
108 if lines.len() <= max {
109 return lines.join("\n");
110 }
111 format!(
112 "{}\n... ({} more lines)",
113 lines[..max].join("\n"),
114 lines.len() - max
115 )
116}