Skip to main content

lean_ctx/core/patterns/
pnpm.rs

1macro_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}