Skip to main content

aft/compress/
bun.rs

1use crate::compress::generic::GenericCompressor;
2use crate::compress::Compressor;
3
4pub struct BunCompressor;
5
6impl Compressor for BunCompressor {
7    fn matches(&self, command: &str) -> bool {
8        command
9            .split_whitespace()
10            .next()
11            .is_some_and(|head| head == "bun")
12    }
13
14    fn compress(&self, command: &str, output: &str) -> String {
15        match bun_subcommand(command).as_deref() {
16            Some("install" | "add" | "remove") => compress_package(output),
17            Some("run" | "test") => GenericCompressor::compress_output(output),
18            Some("build") => compress_build(output),
19            _ => GenericCompressor::compress_output(output),
20        }
21    }
22}
23
24fn bun_subcommand(command: &str) -> Option<String> {
25    command
26        .split_whitespace()
27        .skip_while(|token| *token != "bun")
28        .skip(1)
29        .find(|token| !token.starts_with('-'))
30        .map(ToString::to_string)
31}
32
33fn compress_package(output: &str) -> String {
34    let mut result = Vec::new();
35    for line in output.lines() {
36        if is_bun_progress(line) {
37            continue;
38        }
39        let trimmed = line.trim_start();
40        if trimmed.contains("packages installed")
41            || trimmed.contains("package installed")
42            || trimmed.starts_with("error:")
43            || trimmed.starts_with("bun install error:")
44            || trimmed.starts_with("Saved lockfile")
45        {
46            result.push(line.to_string());
47        }
48    }
49    trim_trailing_lines(&result.join("\n"))
50}
51
52fn compress_build(output: &str) -> String {
53    let mut result = Vec::new();
54    let mut timing_seen = 0usize;
55    let mut timing_omitted = 0usize;
56    for line in output.lines() {
57        if is_timing_line(line) {
58            timing_seen += 1;
59            if timing_seen > 10 {
60                timing_omitted += 1;
61                continue;
62            }
63        }
64        result.push(line.to_string());
65    }
66    if timing_omitted > 0 {
67        result.push(format!("... and {timing_omitted} more timing lines"));
68    }
69    trim_trailing_lines(&result.join("\n"))
70}
71
72fn is_bun_progress(line: &str) -> bool {
73    let trimmed = line.trim();
74    trimmed == "."
75        || trimmed.chars().all(|char| char == '.')
76        || trimmed.starts_with("Resolving")
77        || trimmed.starts_with("Resolved")
78        || trimmed.starts_with("Downloaded")
79        || trimmed.starts_with("Extracted")
80}
81
82fn is_timing_line(line: &str) -> bool {
83    let trimmed = line.trim_start();
84    trimmed.starts_with('[') && trimmed.contains(" ms]")
85}
86
87fn trim_trailing_lines(input: &str) -> String {
88    input
89        .lines()
90        .map(str::trim_end)
91        .collect::<Vec<_>>()
92        .join("\n")
93}