Skip to main content

st_mem/
display.rs

1use crate::elf::FirmwareUsage;
2
3/// Generate a text-based progress bar.
4///
5/// # Arguments
6///
7/// * `pct` - Percentage (0.0 - 100.0).
8/// * `width` - Number of characters in the bar.
9///
10/// # Returns
11///
12/// A string like `[████████░░░░░░░░]`.
13pub fn progress_bar(pct: f64, width: usize) -> String {
14    let fill = if pct <= 0.0 {
15        0
16    } else {
17        let f = (pct / 100.0 * width as f64) as usize;
18        if f < 1 { 1 } else { f }.min(width)
19    };
20    let empty = width - fill;
21    format!("[{}{}]", "\u{2588}".repeat(fill), "\u{2591}".repeat(empty))
22}
23
24/// Format byte count into a human-readable string (B, KB, MB).
25pub fn format_bytes(bytes: u64) -> String {
26    if bytes >= 1024 * 1024 {
27        format!("{} MB", bytes / (1024 * 1024))
28    } else if bytes >= 1024 {
29        format!("{} KB", bytes / 1024)
30    } else {
31        format!("{} B", bytes)
32    }
33}
34
35/// Generate a full firmware memory report as a formatted string.
36///
37/// Produces output like:
38/// ```text
39///   [FIRMWARE SIZE]
40///   +----------------------------------------------------------------+
41///   | FLASH [████░░░░░░░░░░░░░░░░░░░░░░░░░░]  15.1%    9.7K / 64K    |
42///   | RAM   [█░░░░░░░░░░░░░░░░░░░░░░░░░░░░░]   0.0%      4B / 20K    |
43///   +----------------------------------------------------------------+
44/// ```
45///
46/// # Arguments
47///
48/// * `usage` - The firmware usage data.
49/// * `bar_width` - Number of characters for the progress bar (default 30).
50pub fn format_report(usage: &FirmwareUsage, bar_width: usize) -> String {
51    format_report_with_labels(usage, bar_width, "FLASH", "RAM")
52}
53
54/// Generate a firmware memory report with custom region labels.
55pub fn format_report_with_labels(
56    usage: &FirmwareUsage,
57    bar_width: usize,
58    flash_label: &str,
59    ram_label: &str,
60) -> String {
61    let flash_bar = progress_bar(usage.flash_percent(), bar_width);
62    let ram_bar = progress_bar(usage.ram_percent(), bar_width);
63
64    let flash_used_str = format_bytes(usage.flash_used);
65    let flash_total_str = format_bytes(usage.flash_total);
66    let ram_used_str = format_bytes(usage.ram_used);
67    let ram_total_str = format_bytes(usage.ram_total);
68
69    // Calculate the maximum label width
70    let label_width = flash_label.len().max(ram_label.len());
71
72    // Format each line
73    let line_flash = format!(
74        " {:<label$} {} {:>5.1}%  {:>6} / {:<6} ",
75        flash_label,
76        flash_bar,
77        usage.flash_percent(),
78        flash_used_str,
79        flash_total_str,
80        label = label_width
81    );
82    let line_ram = format!(
83        " {:<label$} {} {:>5.1}%  {:>6} / {:<6} ",
84        ram_label,
85        ram_bar,
86        usage.ram_percent(),
87        ram_used_str,
88        ram_total_str,
89        label = label_width
90    );
91
92    // Determine border width from the longest line (character count)
93    let total_width = line_flash.chars().count().max(line_ram.chars().count());
94    let border = format!("+{}+", "-".repeat(total_width));
95
96    // Pad lines to fixed width
97    let pad = |s: &str, w: usize| {
98        let chars: Vec<char> = s.chars().collect();
99        if chars.len() >= w {
100            chars[..w].iter().collect()
101        } else {
102            format!("{}{}", s, " ".repeat(w - chars.len()))
103        }
104    };
105
106    let mut out = String::new();
107    out.push_str("  [FIRMWARE SIZE]\n");
108    out.push_str(&format!("  {}\n", border));
109    out.push_str(&format!("  |{}|\n", pad(&line_flash, total_width)));
110    out.push_str(&format!("  |{}|\n", pad(&line_ram, total_width)));
111    out.push_str(&format!("  {}", border));
112    out
113}
114
115/// Convenience: analyze and print a firmware memory report to stdout.
116///
117/// # Arguments
118///
119/// * `elf_path` - Path to the ELF binary.
120/// * `memory_x_path` - Path to the `memory.x` linker script.
121pub fn print_report<P1: AsRef<std::path::Path>, P2: AsRef<std::path::Path>>(
122    elf_path: P1,
123    memory_x_path: P2,
124) -> Result<(), String> {
125    let config = crate::memory::MemoryConfig::from_file(memory_x_path)?;
126    let usage = crate::elf::analyze_elf(elf_path, &config)?;
127    println!("{}", format_report(&usage, 30));
128    Ok(())
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn test_progress_bar() {
137        assert_eq!(progress_bar(0.0, 10), "[░░░░░░░░░░]");
138        assert_eq!(progress_bar(100.0, 10), "[██████████]");
139        assert_eq!(progress_bar(50.0, 10), "[█████░░░░░]");
140    }
141
142    #[test]
143    fn test_format_bytes() {
144        assert_eq!(format_bytes(500), "500 B");
145        assert_eq!(format_bytes(1024), "1 KB");
146        assert_eq!(format_bytes(65536), "64 KB");
147        assert_eq!(format_bytes(1048576), "1 MB");
148    }
149
150    #[test]
151    fn test_format_report() {
152        let usage = FirmwareUsage {
153            flash_used: 9933,
154            ram_used: 4,
155            flash_total: 65536,
156            ram_total: 20480,
157        };
158        let report = format_report(&usage, 30);
159        println!("{}", report);
160        assert!(report.contains("FIRMWARE SIZE"));
161        assert!(report.contains("FLASH"));
162        assert!(report.contains("RAM"));
163    }
164}