use ratatui::style::Style;
use crate::themes::DEFAULT_COLORS;
use crate::utils::Epsilon;
use crate::utils::format_bytes;
const ALL_CAPS: &[&str] = &[
"svo", "pdf", "tiff", "jpeg", "png", "rgb", "yuv", "aac", "mp3", "h264", "cbr", "lf", "und",
"eng",
];
const FLOAT_PRECISION: usize = 4;
#[inline]
#[must_use]
pub fn is_byte_key(key: &str) -> bool {
key.to_lowercase().contains("size")
|| key.to_lowercase().contains("compressed")
|| key.to_lowercase().contains("uncompressed")
|| key.to_lowercase().contains("byte")
}
#[must_use]
pub fn is_bool(value: &serde_json::Value) -> bool {
matches!(value, serde_json::Value::Bool(_))
}
#[must_use]
pub fn value_cell_style(formatted_value: &str) -> Option<Style> {
match formatted_value {
"TRUE" => Some(Style::default().fg(DEFAULT_COLORS.green)),
"FALSE" => Some(Style::default().fg(DEFAULT_COLORS.red)),
_ => None,
}
}
pub fn join_dot(parts: impl IntoIterator<Item = impl AsRef<str>>) -> String {
parts
.into_iter()
.map(|p| p.as_ref().to_string())
.collect::<Vec<_>>()
.join(" · ")
}
#[must_use]
pub fn prefixed_label(prefix: &str, label: &str) -> String {
format!("{prefix}{label}")
}
#[must_use]
pub fn prefixed_label_with_value(prefix: &str, label: &str, value_str: &str) -> String {
format!("{prefix}{label}: {value_str}")
}
#[must_use]
pub fn format_key(key: &str) -> String {
let words: Vec<String> = key
.split('_')
.map(|w| {
let lower = w.to_lowercase();
if ALL_CAPS.iter().any(|&c| c == lower) {
w.to_uppercase()
} else {
let mut c = w.chars();
match c.next() {
None => String::new(),
Some(first) => first.to_uppercase().chain(c).collect(),
}
}
})
.collect();
words.join(" ")
}
pub fn value_to_string(v: &serde_json::Value) -> String {
match v {
serde_json::Value::Null => "—".to_string(),
serde_json::Value::Bool(b) => b.to_string().to_uppercase(),
serde_json::Value::Number(n) => n
.as_f64()
.filter(|f| f.fract().abs() >= Epsilon::FORMAT)
.map_or_else(|| n.to_string(), |f| format!("{f:.FLOAT_PRECISION$}")),
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Array(arr) => {
if arr.is_empty() {
"[]".to_string()
} else if arr.len() <= 3 && arr.iter().all(|x| x.is_string() || x.is_number()) {
arr.iter()
.map(value_to_string)
.collect::<Vec<_>>()
.join(", ")
} else {
format!("[{} items]", arr.len())
}
}
serde_json::Value::Object(_) => "{…}".to_string(),
}
}
fn json_f64_to_u64_for_bytes(f: f64) -> u64 {
if f <= 0.0 || !f.is_finite() {
return 0;
}
if f >= u64::MAX as f64 {
return u64::MAX;
}
f as u64
}
#[must_use]
pub fn format_value(v: &serde_json::Value, key: &str) -> String {
let key_lower = key.to_lowercase();
if is_byte_key(&key_lower) {
if let Some(n) = v.as_u64() {
return format_bytes(n);
}
if let Some(n) = v.as_i64().filter(|&x| x >= 0) {
return format_bytes(n.cast_unsigned());
}
if let Some(f) = v.as_f64().filter(|&x| x >= 0.0 && x.is_finite()) {
return format_bytes(json_f64_to_u64_for_bytes(f));
}
}
let s = value_to_string(v);
if key_lower.contains("percent") {
format!("{s}%")
} else {
s
}
}