#![allow(dead_code)]
use std::process::Command;
pub fn read_trim(path: &str) -> Option<String> {
std::fs::read_to_string(path)
.ok()
.map(|s| s.trim_end().to_string())
.filter(|s| !s.is_empty())
}
pub fn first_line(path: &str) -> Option<String> {
let s = std::fs::read_to_string(path).ok()?;
s.lines()
.map(str::trim)
.find(|l| !l.is_empty())
.map(str::to_string)
}
pub fn cmd(prog: &str, args: &[&str]) -> Option<String> {
let out = Command::new(prog).args(args).output().ok()?;
if !out.status.success() {
return None;
}
let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
if s.is_empty() {
None
} else {
Some(s)
}
}
pub fn sh(command: &str) -> Option<String> {
let out = Command::new("sh").arg("-c").arg(command).output().ok()?;
if !out.status.success() {
return None;
}
String::from_utf8_lossy(&out.stdout)
.lines()
.map(str::trim)
.find(|l| !l.is_empty())
.map(str::to_string)
}
pub fn sh_raw(command: &str) -> Option<String> {
let out = Command::new("sh").arg("-c").arg(command).output().ok()?;
if !out.status.success() {
return None;
}
Some(String::from_utf8_lossy(&out.stdout).into_owned())
}
pub fn human_iec(bytes: u64) -> String {
const UNITS: [&str; 6] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
let mut v = bytes as f64;
let mut i = 0;
while v >= 1024.0 && i < UNITS.len() - 1 {
v /= 1024.0;
i += 1;
}
if i == 0 {
format!("{bytes} B")
} else {
format!("{v:.2} {}", UNITS[i])
}
}
pub fn percent(part: u64, total: u64) -> u64 {
if total == 0 {
0
} else {
((part as f64 / total as f64) * 100.0).round() as u64
}
}
#[cfg(any(target_os = "macos", test))]
pub fn ioreg_find(dump: &str, key: &str) -> Option<String> {
let needle = format!("\"{key}\" = ");
dump.lines().find_map(|l| {
l.find(&needle)
.map(|pos| l[pos + needle.len()..].to_string())
})
}
#[cfg(any(target_os = "macos", test))]
pub fn ioreg_decode(raw: &str) -> Option<String> {
let raw = raw.trim();
if let Some(inner) = raw
.strip_prefix("<\"")
.and_then(|r| r.strip_suffix("\">"))
.or_else(|| raw.strip_prefix('"').and_then(|r| r.strip_suffix('"')))
{
return non_empty(inner);
}
let hex = raw.strip_prefix('<').and_then(|r| r.strip_suffix('>'))?;
let hex: String = hex.chars().filter(|c| !c.is_whitespace()).collect();
if hex.is_empty() || hex.len() % 2 != 0 || !hex.chars().all(|c| c.is_ascii_hexdigit()) {
return None;
}
let bytes: Vec<u8> = (0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap_or(0))
.collect();
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
non_empty(&String::from_utf8_lossy(&bytes[..end]))
}
#[cfg(any(target_os = "macos", test))]
pub fn ioreg_string(dump: &str, key: &str) -> Option<String> {
ioreg_decode(&ioreg_find(dump, key)?)
}
#[cfg(any(target_os = "macos", test))]
pub fn ioreg_u64(dump: &str, key: &str) -> Option<u64> {
ioreg_find(dump, key)?.trim().parse().ok()
}
#[cfg(any(target_os = "macos", test))]
fn non_empty(s: &str) -> Option<String> {
let s = s.trim();
if s.is_empty() {
None
} else {
Some(s.to_string())
}
}
#[cfg(test)]
mod tests {
use super::{human_iec, ioreg_string, ioreg_u64, percent};
#[test]
fn iec_units_and_rounding() {
assert_eq!(human_iec(0), "0 B");
assert_eq!(human_iec(512), "512 B");
assert_eq!(human_iec(1024), "1.00 KiB");
assert_eq!(human_iec(1536), "1.50 KiB");
assert_eq!(human_iec(1024 * 1024), "1.00 MiB");
assert_eq!(human_iec(3u64 * 1024 * 1024 * 1024), "3.00 GiB");
}
#[test]
fn percent_rounds_and_guards_zero() {
assert_eq!(percent(0, 0), 0);
assert_eq!(percent(1, 0), 0);
assert_eq!(percent(1, 2), 50);
assert_eq!(percent(1, 3), 33);
assert_eq!(percent(2, 3), 67);
assert_eq!(percent(3, 3), 100);
}
#[test]
fn ioreg_values_decode_all_three_shapes() {
let dump = "\
+-o AGXAcceleratorG13G <class AGXAcceleratorG13G>
| \"model\" = <\"Apple M1\">
| \"gpu-core-count\" = 8
| \"board-id\" = <4d61636d696e69392c3100>
| \"IOClass\" = \"AGXAcceleratorG13G\"
";
assert_eq!(ioreg_string(dump, "model").as_deref(), Some("Apple M1"));
assert_eq!(
ioreg_string(dump, "board-id").as_deref(),
Some("Macmini9,1")
);
assert_eq!(
ioreg_string(dump, "IOClass").as_deref(),
Some("AGXAcceleratorG13G")
);
assert_eq!(ioreg_u64(dump, "gpu-core-count"), Some(8));
assert_eq!(ioreg_u64(dump, "model"), None);
assert_eq!(ioreg_string(dump, "absent"), None);
}
}