lean_ctx/core/patterns/
buf.rs1use crate::core::compressor::strip_ansi;
8
9macro_rules! static_regex {
10 ($pattern:expr_2021) => {{
11 static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
12 RE.get_or_init(|| {
13 regex::Regex::new($pattern).expect(concat!("BUG: invalid static regex: ", $pattern))
14 })
15 }};
16}
17
18fn violation_re() -> &'static regex::Regex {
19 static_regex!(r"^(.+?):\d+:\d+:(.+)$")
20}
21
22pub fn compress(_cmd: &str, output: &str) -> Option<String> {
23 let trimmed = output.trim();
24 if trimmed.is_empty() {
25 return Some("buf: ok".to_string());
26 }
27
28 let mut violations: Vec<String> = Vec::new();
29 let mut other: Vec<String> = Vec::new();
30 for raw in trimmed.lines() {
31 let line = strip_ansi(raw);
32 let t = line.trim();
33 if t.is_empty() {
34 continue;
35 }
36 if violation_re().is_match(t) {
37 violations.push(t.to_string());
38 } else {
39 other.push(t.to_string());
40 }
41 }
42
43 if violations.is_empty() {
44 if other.is_empty() {
46 return Some("buf: ok".to_string());
47 }
48 return Some(other.join("\n"));
49 }
50
51 let mut parts = vec![format!("buf: {} violation(s)", violations.len())];
52 for v in violations.iter().take(20) {
53 parts.push(format!(" {v}"));
54 }
55 if violations.len() > 20 {
56 parts.push(format!(" ... +{} more", violations.len() - 20));
57 }
58 Some(parts.join("\n"))
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn counts_and_keeps_violations() {
67 let out = "proto/foo.proto:10:1:Field name should be lower_snake_case.\nproto/bar.proto:5:3:Enum value should be UPPER_SNAKE_CASE.";
68 let r = compress("buf lint", out).unwrap();
69 assert!(r.contains("buf: 2 violation(s)"), "{r}");
70 assert!(r.contains("foo.proto:10:1"), "{r}");
71 }
72
73 #[test]
74 fn empty_is_ok() {
75 assert_eq!(compress("buf lint", "").unwrap(), "buf: ok");
76 }
77}