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