lean_ctx/core/patterns/
pip.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 pip_installed_re() -> &'static regex::Regex {
11 static_regex!(r"Successfully installed\s+(.+)")
12}
13fn pip_outdated_re() -> &'static regex::Regex {
14 static_regex!(r"^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)")
15}
16
17pub fn compress(command: &str, output: &str) -> Option<String> {
18 if command.contains("uninstall") {
19 return Some(compress_uninstall(output));
20 }
21 if command.contains("install") {
22 return Some(compress_install(output));
23 }
24 if command.contains("list") || command.contains("freeze") {
25 if command.contains("outdated") || command.contains("--outdated") {
26 return Some(compress_outdated(output));
27 }
28 return Some(compress_list(output));
29 }
30 if command.contains("show") {
31 return Some(compress_show(output));
32 }
33 if command.contains("check") {
34 return Some(compress_check(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 if let Some(caps) = pip_installed_re().captures(trimmed) {
46 let packages: Vec<&str> = caps[1].split_whitespace().collect();
47 return format!("ok (+{} packages): {}", packages.len(), packages.join(", "));
48 }
49
50 if trimmed.contains("already satisfied") {
51 return "ok (already satisfied)".to_string();
52 }
53
54 compact_output(trimmed, 5)
55}
56
57fn compress_list(output: &str) -> String {
58 let lines: Vec<&str> = output.lines().collect();
59 if lines.len() <= 2 {
60 return output.to_string();
61 }
62
63 let skip = if lines[0].starts_with("Package") || lines[0].starts_with("---") {
64 2
65 } else {
66 0
67 };
68 let packages: Vec<String> = lines[skip..]
69 .iter()
70 .filter_map(|l| {
71 let parts: Vec<&str> = l.split_whitespace().collect();
72 if parts.len() >= 2 {
73 Some(format!("{}=={}", parts[0], parts[1]))
74 } else {
75 None
76 }
77 })
78 .collect();
79
80 if packages.is_empty() {
81 return output.to_string();
82 }
83 format!("{} packages:\n{}", packages.len(), packages.join("\n"))
84}
85
86fn compress_outdated(output: &str) -> String {
87 let lines: Vec<&str> = output.lines().collect();
88 let skip = if lines.first().is_some_and(|l| l.starts_with("Package")) {
89 2
90 } else {
91 0
92 };
93
94 let mut outdated = Vec::new();
95 for line in lines.iter().skip(skip) {
96 if let Some(caps) = pip_outdated_re().captures(line) {
97 let name = &caps[1];
98 let current = &caps[2];
99 let latest = &caps[3];
100 outdated.push(format!("{name}: {current} → {latest}"));
101 }
102 }
103
104 if outdated.is_empty() {
105 return "all up-to-date".to_string();
106 }
107 format!("{} outdated:\n{}", outdated.len(), outdated.join("\n"))
108}
109
110fn compress_uninstall(output: &str) -> String {
111 let trimmed = output.trim();
112 if trimmed.is_empty() {
113 return "ok".to_string();
114 }
115
116 let removed: Vec<&str> = trimmed
117 .lines()
118 .filter(|l| l.contains("Successfully uninstalled"))
119 .collect();
120
121 if removed.is_empty() {
122 return compact_output(trimmed, 3);
123 }
124
125 let names: Vec<String> = removed
126 .iter()
127 .take(30)
128 .map(|l| {
129 l.trim()
130 .strip_prefix("Successfully uninstalled ")
131 .unwrap_or(l.trim())
132 .to_string()
133 })
134 .collect();
135
136 let total = removed.len();
137 if names.is_empty() {
138 return format!("ok (removed {total} packages)");
139 }
140 format!("ok (removed {total} packages): {}", names.join(", "))
141}
142
143fn compress_show(output: &str) -> String {
144 compact_output(output, 10)
145}
146
147fn compress_check(output: &str) -> String {
148 let trimmed = output.trim();
149 if trimmed.is_empty() || trimmed.contains("No broken requirements") {
150 return "ok (no broken dependencies)".to_string();
151 }
152
153 let broken: Vec<&str> = trimmed
154 .lines()
155 .filter(|l| l.contains("requires") || l.contains("has requirement"))
156 .collect();
157
158 if broken.is_empty() {
159 return compact_output(trimmed, 5);
160 }
161 format!(
162 "{} broken dependencies:\n{}",
163 broken.len(),
164 broken.join("\n")
165 )
166}
167
168fn compact_output(text: &str, max: usize) -> String {
169 let lines: Vec<&str> = text.lines().filter(|l| !l.trim().is_empty()).collect();
170 if lines.len() <= max {
171 return lines.join("\n");
172 }
173 format!(
174 "{}\n... ({} more lines)",
175 lines[..max].join("\n"),
176 lines.len() - max
177 )
178}