1use crate::ansi::strip_ansi;
16
17pub fn compress(cmd: &str, output: &str) -> Option<String> {
18 let trimmed = output.trim();
19 if trimmed.is_empty() {
20 return Some("gem: ok".to_string());
21 }
22 if cmd.contains(" list") || cmd.contains(" search") {
23 return Some(compress_list(trimmed));
24 }
25 Some(compress_install(trimmed))
26}
27
28fn compress_list(output: &str) -> String {
29 let rows: Vec<&str> = output
30 .lines()
31 .map(str::trim)
32 .filter(|l| !l.is_empty() && !l.starts_with("***") && l.contains('('))
33 .collect();
34 if rows.is_empty() {
35 return fallback(output);
36 }
37 let n = rows.len();
38 let cap = 30.min(n);
39 let mut s = format!("gem: {n} gem(s)\n{}", rows[..cap].join("\n"));
40 if n > cap {
41 s.push_str(&format!("\n... +{} more", n - cap));
42 }
43 s
44}
45
46fn compress_install(output: &str) -> String {
47 let mut kept: Vec<String> = Vec::new();
48 for raw in output.lines() {
49 let line = strip_ansi(raw);
50 let t = line.trim();
51 if t.is_empty() {
52 continue;
53 }
54 let l = t.to_ascii_lowercase();
55 if l.starts_with("fetching")
56 || l.starts_with("parsing documentation")
57 || l.starts_with("installing ri")
58 || l.starts_with("installing rdoc")
59 || l.starts_with("done installing documentation")
60 || l.starts_with("building native extensions")
61 {
62 continue;
63 }
64 if l.starts_with("successfully installed")
65 || l.starts_with("successfully uninstalled")
66 || l.contains("gems installed")
67 || l.contains("gem installed")
68 || l.contains("error")
69 || l.contains("could not")
70 || l.contains("conflict")
71 {
72 kept.push(t.to_string());
73 }
74 }
75 if kept.is_empty() {
76 return "gem: ok".to_string();
77 }
78 kept.join("\n")
79}
80
81fn fallback(text: &str) -> String {
82 let lines: Vec<&str> = text.lines().filter(|l| !l.trim().is_empty()).collect();
83 let n = lines.len().min(10);
84 let mut s = lines[..n].join("\n");
85 if lines.len() > n {
86 s.push_str(&format!("\n... (+{} lines)", lines.len() - n));
87 }
88 s
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn install_keeps_success_drops_docs() {
97 let out = "Fetching rails-7.1.0.gem\nFetching activesupport-7.1.0.gem\nSuccessfully installed activesupport-7.1.0\nSuccessfully installed rails-7.1.0\nParsing documentation for rails-7.1.0\nInstalling ri documentation for rails-7.1.0\nDone installing documentation for rails after 3 seconds\n2 gems installed";
98 let r = compress("gem install rails", out).unwrap();
99 assert!(r.contains("Successfully installed rails-7.1.0"), "{r}");
100 assert!(r.contains("2 gems installed"), "{r}");
101 assert!(!r.contains("Fetching"), "drops fetch noise: {r}");
102 assert!(!r.contains("Parsing documentation"), "drops doc noise: {r}");
103 }
104
105 #[test]
106 fn search_counts_and_caps() {
107 let out = "*** REMOTE GEMS ***\n\nbundler (2.5.0)\nrails (7.1.0)\nrake (13.1.0)";
110 let r = compress("gem search rails", out).unwrap();
111 assert!(r.contains("gem: 3 gem(s)"), "{r}");
112 assert!(r.contains("rails (7.1.0)"), "{r}");
113 }
114
115 #[test]
116 fn empty_is_ok() {
117 assert_eq!(compress("gem install x", "").unwrap(), "gem: ok");
118 }
119}