lean_ctx_patterns/
next_build.rs1macro_rules! static_regex {
4 ($pattern:expr_2021) => {{
5 static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
6 RE.get_or_init(|| {
7 regex::Regex::new($pattern).expect(concat!("BUG: invalid static regex: ", $pattern))
8 })
9 }};
10}
11
12fn route_re() -> &'static regex::Regex {
13 static_regex!(r"^[○●λƒ◐]\s+(/\S*)")
14}
15fn size_re() -> &'static regex::Regex {
16 static_regex!(r"(\d+\.?\d*)\s*(kB|MB|B)\b")
17}
18fn build_time_re() -> &'static regex::Regex {
19 static_regex!(r"(?:(?:compiled|built|done)|ready)\s+(?:in\s+)?(\d+\.?\d*\s*[ms]+)")
20}
21fn vite_chunk_re() -> &'static regex::Regex {
22 static_regex!(r"dist/(\S+)\s+(\d+\.?\d*\s*[kKMm]?B)")
23}
24
25pub fn compress(command: &str, output: &str) -> Option<String> {
26 if command.contains("vite") {
27 return Some(compress_vite(output));
28 }
29 Some(compress_next(output))
30}
31
32fn compress_next(output: &str) -> String {
33 let trimmed = output.trim();
34 if trimmed.is_empty() {
35 return "ok".to_string();
36 }
37
38 let mut routes = Vec::new();
39 let mut total_size = 0f64;
40 let mut build_time = String::new();
41 let mut errors = Vec::new();
42
43 for line in trimmed.lines() {
44 if let Some(caps) = route_re().captures(line) {
45 let route = &caps[1];
46 let size = extract_size(line);
47 routes.push(format!("{route} ({size})"));
48 }
49 if let Some(caps) = build_time_re().captures(line) {
50 build_time = caps[1].to_string();
51 }
52 if line.to_lowercase().contains("error") && !line.contains("0 error") {
53 errors.push(line.trim().to_string());
54 }
55 if let Some(caps) = size_re().captures(line) {
56 let val: f64 = caps[1].parse().unwrap_or(0.0);
57 let unit = &caps[2];
58 total_size += match unit {
59 "MB" => val * 1024.0,
60 "kB" => val,
61 _ => val / 1024.0,
62 };
63 }
64 }
65
66 if !errors.is_empty() {
67 return format!("BUILD ERROR:\n{}", errors.join("\n"));
68 }
69
70 let mut parts = Vec::new();
71 if build_time.is_empty() {
72 parts.push("built".to_string());
73 } else {
74 parts.push(format!("built ({build_time})"));
75 }
76
77 if !routes.is_empty() {
78 parts.push(format!("{} routes:", routes.len()));
79 for r in routes.iter().take(15) {
80 parts.push(format!(" {r}"));
81 }
82 if routes.len() > 15 {
83 parts.push(format!(" ... +{} more", routes.len() - 15));
84 }
85 }
86
87 if total_size > 0.0 {
88 if total_size > 1024.0 {
89 parts.push(format!("total: {:.1} MB", total_size / 1024.0));
90 } else {
91 parts.push(format!("total: {total_size:.0} kB"));
92 }
93 }
94
95 if parts.len() == 1 && parts[0] == "built" {
96 return compact_output(trimmed, 10);
97 }
98
99 parts.join("\n")
100}
101
102fn compress_vite(output: &str) -> String {
103 let trimmed = output.trim();
104 if trimmed.is_empty() {
105 return "ok".to_string();
106 }
107
108 let mut chunks = Vec::new();
109 let mut build_time = String::new();
110
111 for line in trimmed.lines() {
112 if let Some(caps) = vite_chunk_re().captures(line) {
113 chunks.push(format!("{}: {}", &caps[1], &caps[2]));
114 }
115 if let Some(caps) = build_time_re().captures(line) {
116 build_time = caps[1].to_string();
117 }
118 }
119
120 let mut parts = Vec::new();
121 if build_time.is_empty() {
122 parts.push("built".to_string());
123 } else {
124 parts.push(format!("built ({build_time})"));
125 }
126
127 if !chunks.is_empty() {
128 parts.push(format!("{} chunks:", chunks.len()));
129 for c in chunks.iter().take(10) {
130 parts.push(format!(" {c}"));
131 }
132 if chunks.len() > 10 {
133 parts.push(format!(" ... +{} more", chunks.len() - 10));
134 }
135 }
136
137 if parts.len() == 1 && parts[0] == "built" {
138 return compact_output(trimmed, 10);
139 }
140 parts.join("\n")
141}
142
143fn extract_size(line: &str) -> String {
144 if let Some(caps) = size_re().captures(line) {
145 format!("{} {}", &caps[1], &caps[2])
146 } else {
147 "?".to_string()
148 }
149}
150
151fn compact_output(text: &str, max: usize) -> String {
152 let lines: Vec<&str> = text.lines().filter(|l| !l.trim().is_empty()).collect();
153 if lines.len() <= max {
154 return lines.join("\n");
155 }
156 format!(
157 "{}\n{}",
158 lines[..max].join("\n"),
159 super::elision_marker(lines.len() - max)
160 )
161}