use super::*;
pub(crate) fn format_memory_bytes(bytes: u64) -> String {
const MB: u64 = 1024 * 1024;
const GB: u64 = 1024 * MB;
if bytes >= GB {
let g = bytes as f64 / GB as f64;
if g.fract().abs() < 0.05 {
format!("{:.0} GB", g)
} else {
format!("{:.1} GB", g)
}
} else if bytes >= MB {
let m = bytes as f64 / MB as f64;
format!("{:.0} MB", m)
} else {
format!("{} bytes", bytes)
}
}
pub(crate) fn format_cpu_nanos(nanos: u64) -> String {
let cores = nanos as f64 / 1_000_000_000.0;
if cores.fract().abs() < 0.05 {
format!("{:.0} cores", cores)
} else {
format!("{:.1} cores", cores)
}
}
pub(crate) fn format_duration_ns(ns: u64) -> String {
let secs = ns / 1_000_000_000;
if secs >= 3600 {
format!("{}h", secs / 3600)
} else if secs >= 60 {
format!("{}m", secs / 60)
} else if secs > 0 {
format!("{}s", secs)
} else {
format!("{}ms", ns / 1_000_000)
}
}
pub(crate) fn format_health_test(test: &[String]) -> String {
if test.is_empty() {
return String::new();
}
match test[0].as_str() {
"CMD" | "CMD-SHELL" => test[1..].join(" "),
"NONE" => "disabled".to_string(),
_ => test.join(" "),
}
}
pub(crate) fn format_iso_timestamp(s: &str) -> Option<String> {
if s.is_empty() || s.starts_with("0001-") {
return None;
}
let main = s.split('.').next().unwrap_or(s);
let main = main.trim_end_matches('Z');
Some(main.replace('T', " "))
}
pub(crate) fn truncate_panel_value(s: &str, max: usize) -> String {
if s.chars().count() <= max {
return s.to_string();
}
let cut = max.saturating_sub(2);
let take_from_end = cut;
let chars: Vec<char> = s.chars().collect();
let start = chars.len().saturating_sub(take_from_end);
let suffix: String = chars[start..].iter().collect();
format!("…{}", suffix)
}
pub(crate) fn snap_top_to_card_boundary(lines: &[Line<'static>], cap: u16) -> u16 {
let cap_us = cap as usize;
let mut best: Option<usize> = None;
for (i, line) in lines.iter().enumerate().take(cap_us) {
let starts_with_close = line
.spans
.first()
.map(|s| s.content.starts_with('\u{2570}'))
.unwrap_or(false);
if starts_with_close {
best = Some(i + 1);
}
}
best.map(|n| n as u16).unwrap_or(cap)
}
pub(crate) fn wrap_to_lines(s: &str, width: usize, max_lines: usize) -> Vec<String> {
if width == 0 || max_lines == 0 {
return Vec::new();
}
let chars: Vec<char> = s.chars().collect();
let mut out = Vec::new();
let mut start = 0;
while start < chars.len() && out.len() < max_lines {
let remaining = chars.len() - start;
let take = remaining.min(width);
let is_last_slot = out.len() + 1 == max_lines;
let overflows = is_last_slot && take < remaining;
let end = start + take;
let chunk: String = if overflows {
let cut = end.saturating_sub(1);
let mut s: String = chars[start..cut].iter().collect();
s.push('\u{2026}');
s
} else {
chars[start..end].iter().collect()
};
out.push(chunk);
start = end;
}
out
}
pub(crate) fn pad_or_truncate_path(s: &str, w: usize) -> String {
let cur = s.width();
if cur == w {
return s.to_string();
}
if cur < w {
return format!("{}{}", s, " ".repeat(w - cur));
}
let truncated = truncate_panel_value(s, w);
let tw = truncated.width();
if tw < w {
format!("{}{}", truncated, " ".repeat(w - tw))
} else {
truncated
}
}
pub(crate) fn short_digest(d: &str) -> String {
if let Some(hex) = d.strip_prefix("sha256:") {
if hex.len() > 12 {
return format!("sha256:{}…{}", &hex[..6], &hex[hex.len() - 4..]);
}
}
d.to_string()
}
pub(crate) fn short_id(id: &str) -> String {
if id.len() <= 12 {
id.to_string()
} else {
id[..12].to_string()
}
}