Skip to main content

lean_ctx_patterns/
mise.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! mise (dev tool version manager) output compression.
4//!
5//! `mise ls`/`list` prints `tool  version  source-path` rows — we keep
6//! tool + version and drop the config source path. `mise install` keeps the
7//! installed/failed lines and drops download progress.
8
9use crate::ansi::strip_ansi;
10
11pub fn compress(cmd: &str, output: &str) -> Option<String> {
12    let trimmed = output.trim();
13    if trimmed.is_empty() {
14        return Some("mise: ok".to_string());
15    }
16    if cmd.contains(" ls") || cmd.contains(" list") {
17        return Some(compress_ls(trimmed));
18    }
19    if cmd.contains("install") || cmd.contains("use") || cmd.contains("upgrade") {
20        return Some(compress_install(trimmed));
21    }
22    Some(fallback(trimmed))
23}
24
25fn compress_ls(output: &str) -> String {
26    let mut rows: Vec<String> = Vec::new();
27    for raw in output.lines() {
28        let line = strip_ansi(raw);
29        let cols: Vec<&str> = line.split_whitespace().collect();
30        if cols.len() >= 2 {
31            rows.push(format!("{} {}", cols[0], cols[1]));
32        }
33    }
34    if rows.is_empty() {
35        return fallback(output);
36    }
37    format!("mise: {} tool(s)\n{}", rows.len(), rows.join("\n"))
38}
39
40fn compress_install(output: &str) -> String {
41    let mut kept: Vec<String> = Vec::new();
42    for raw in output.lines() {
43        let line = strip_ansi(raw);
44        let t = line.trim();
45        if t.is_empty() {
46            continue;
47        }
48        let l = t.to_ascii_lowercase();
49        if l.contains("installed")
50            || l.contains("installing")
51            || l.contains("failed")
52            || l.contains("error")
53            || t.contains('✓')
54        {
55            kept.push(t.to_string());
56        }
57    }
58    if kept.is_empty() {
59        return "mise: ok".to_string();
60    }
61    kept.join("\n")
62}
63
64fn fallback(text: &str) -> String {
65    let lines: Vec<&str> = text.lines().filter(|l| !l.trim().is_empty()).collect();
66    let n = lines.len().min(10);
67    let mut s = lines[..n].join("\n");
68    if lines.len() > n {
69        s.push_str(&format!("\n... (+{} lines)", lines.len() - n));
70    }
71    s
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn ls_drops_source_path() {
80        let out = "node    20.10.0  ~/.config/mise/config.toml\npython  3.12.0   ~/.tool-versions\nrust    1.75.0   ~/.config/mise/config.toml";
81        let r = compress("mise ls", out).unwrap();
82        assert!(r.contains("3 tool(s)"), "{r}");
83        assert!(r.contains("node 20.10.0"), "{r}");
84        assert!(!r.contains("config.toml"), "drops source path: {r}");
85    }
86
87    #[test]
88    fn install_keeps_status() {
89        let out = "mise downloading node@20.10.0\nmise extracting node@20.10.0\nmise node@20.10.0 ✓ installed";
90        let r = compress("mise install node", out).unwrap();
91        assert!(r.contains("installed"), "{r}");
92        assert!(!r.contains("extracting"), "drops progress: {r}");
93    }
94
95    #[test]
96    fn empty_is_ok() {
97        assert_eq!(compress("mise ls", "").unwrap(), "mise: ok");
98    }
99}