Skip to main content

lean_ctx/core/patterns/
pnpm.rs

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