lean_ctx/core/patterns/
pnpm.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 pnpm_added_re() -> &'static regex::Regex {
11 static_regex!(r"(\d+) packages? (?:are )?(?:installed|added|updated)")
12}
13
14pub fn compress(command: &str, output: &str) -> Option<String> {
15 if command.contains("install") || command.contains("add") || command.contains("i ") {
16 return Some(compress_install(output));
17 }
18 if command.contains("list") || command.contains("ls") {
19 return Some(compress_list(output));
20 }
21 if command.contains("outdated") {
22 return Some(compress_outdated(output));
23 }
24 if command.contains("run") || command.contains("exec") {
25 return Some(compress_run(output));
26 }
27 if command.contains("test") {
28 return Some(compress_test(output));
29 }
30 if command.contains("why") {
31 return Some(compact_output(output, 10));
32 }
33 if command.contains("store") {
34 return Some(compress_store(output));
35 }
36 None
37}
38
39fn compress_install(output: &str) -> String {
40 let trimmed = output.trim();
41 if trimmed.is_empty() {
42 return "ok".to_string();
43 }
44
45 let mut pkg_count = 0u32;
46 if let Some(caps) = pnpm_added_re().captures(trimmed) {
47 pkg_count = caps[1].parse().unwrap_or(0);
48 }
49
50 let progress_free: Vec<&str> = trimmed
51 .lines()
52 .filter(|l| {
53 let t = l.trim();
54 !t.is_empty()
55 && !t.starts_with("Progress:")
56 && !t.starts_with("Already up to date")
57 && !t.contains("Downloading")
58 && !t.contains("fetched from")
59 })
60 .collect();
61
62 if pkg_count > 0 {
63 return format!("ok ({pkg_count} packages installed)");
64 }
65 if progress_free.len() <= 3 {
66 return progress_free.join("\n");
67 }
68 format!(
69 "ok\n{}",
70 progress_free[progress_free.len() - 3..].join("\n")
71 )
72}
73
74fn compress_list(output: &str) -> String {
75 let lines: Vec<&str> = output.lines().collect();
76 if lines.len() <= 5 {
77 return output.to_string();
78 }
79
80 let _deps: Vec<&str> = lines
81 .iter()
82 .filter(|l| l.contains("dependencies:") || l.starts_with(' '))
83 .copied()
84 .collect();
85
86 let top: Vec<String> = lines
87 .iter()
88 .filter(|l| {
89 let trimmed = l.trim();
90 !trimmed.is_empty()
91 && (trimmed.starts_with('+')
92 || trimmed.starts_with("└")
93 || trimmed.starts_with("├"))
94 })
95 .map(|l| {
96 l.replace("├──", "")
97 .replace("└──", "")
98 .replace("├─", "")
99 .replace("└─", "")
100 .trim()
101 .to_string()
102 })
103 .collect();
104
105 if !top.is_empty() {
106 return format!("{} packages:\n{}", top.len(), top.join("\n"));
107 }
108 compact_output(output, 15)
109}
110
111fn compress_outdated(output: &str) -> String {
112 let lines: Vec<&str> = output.lines().collect();
113 if lines.len() <= 1 {
114 return "all up-to-date".to_string();
115 }
116
117 let mut packages = Vec::new();
118 for line in &lines[1..] {
119 let parts: Vec<&str> = line.split_whitespace().collect();
120 if parts.len() >= 3 {
121 packages.push(format!("{}: {} → {}", parts[0], parts[1], parts[2]));
122 }
123 }
124
125 if packages.is_empty() {
126 return "all up-to-date".to_string();
127 }
128 format!("{} outdated:\n{}", packages.len(), packages.join("\n"))
129}
130
131fn compress_run(output: &str) -> String {
132 let lines: Vec<&str> = output
133 .lines()
134 .filter(|l| {
135 let t = l.trim();
136 !t.is_empty() && !t.starts_with('>')
137 })
138 .collect();
139
140 if lines.len() <= 5 {
141 return lines.join("\n");
142 }
143 let tail = &lines[lines.len() - 3..];
144 format!("...({} lines)\n{}", lines.len(), tail.join("\n"))
145}
146
147fn compress_test(output: &str) -> String {
148 compress_run(output)
149}
150
151fn compress_store(output: &str) -> String {
152 let trimmed = output.trim();
153 if trimmed.is_empty() {
154 return "ok".to_string();
155 }
156 compact_output(trimmed, 5)
157}
158
159fn compact_output(text: &str, max: usize) -> String {
160 let lines: Vec<&str> = text.lines().filter(|l| !l.trim().is_empty()).collect();
161 if lines.len() <= max {
162 return lines.join("\n");
163 }
164 format!(
165 "{}\n... ({} more lines)",
166 lines[..max].join("\n"),
167 lines.len() - max
168 )
169}