1pub mod battery;
11pub mod btrfs;
12pub mod diskstats;
13pub mod drm;
14pub mod hwmon;
15pub mod mdstat;
16pub mod meminfo;
17pub mod mounts;
18pub mod net;
19pub mod procstat;
20pub mod rapl;
21pub mod systemd;
22pub mod zfs;
23
24use std::fs;
25use std::path::{Path, PathBuf};
26
27#[derive(Debug, Clone)]
29pub struct SysRoots {
30 pub sys: PathBuf,
32 pub proc: PathBuf,
34}
35
36impl Default for SysRoots {
37 fn default() -> Self {
38 Self {
39 sys: PathBuf::from("/sys"),
40 proc: PathBuf::from("/proc"),
41 }
42 }
43}
44
45pub(crate) fn read_trimmed(path: &Path) -> Option<String> {
51 fs::read_to_string(path).ok().map(|s| s.trim().to_owned())
52}
53
54pub(crate) fn read_u64(path: &Path) -> Option<u64> {
56 read_trimmed(path)?.parse().ok()
57}
58
59pub(crate) fn read_i64(path: &Path) -> Option<i64> {
61 read_trimmed(path)?.parse().ok()
62}
63
64pub(crate) fn sorted_entries(dir: &Path) -> Vec<String> {
66 let mut names: Vec<String> = match fs::read_dir(dir) {
67 Ok(entries) => entries
68 .filter_map(|e| e.ok())
69 .filter_map(|e| e.file_name().into_string().ok())
70 .collect(),
71 Err(_) => Vec::new(),
72 };
73 names.sort_by(|a, b| natural_cmp(a, b));
74 names
75}
76
77pub(crate) fn natural_cmp(a: &str, b: &str) -> std::cmp::Ordering {
80 fn split(s: &str) -> (&str, Option<u64>) {
83 let digits_start = s
84 .char_indices()
85 .rev()
86 .take_while(|(_, c)| c.is_ascii_digit())
87 .last()
88 .map(|(i, _)| i)
89 .unwrap_or(s.len());
90 let (prefix, digits) = s.split_at(digits_start);
91 (prefix, digits.parse().ok())
92 }
93 let (pa, na) = split(a);
94 let (pb, nb) = split(b);
95 pa.cmp(pb).then(na.cmp(&nb)).then_with(|| a.cmp(b))
96}
97
98#[cfg(test)]
99pub(crate) mod testutil {
100 use std::fs;
103 use std::path::Path;
104
105 pub fn write(root: &Path, rel: &str, content: &str) {
107 let path = root.join(rel);
108 fs::create_dir_all(path.parent().unwrap()).unwrap();
109 fs::write(path, content).unwrap();
110 }
111
112 pub fn symlink(root: &Path, rel: &str, target: &Path) {
114 let path = root.join(rel);
115 fs::create_dir_all(path.parent().unwrap()).unwrap();
116 std::os::unix::fs::symlink(target, path).unwrap();
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::natural_cmp;
123
124 #[test]
125 fn natural_order_sorts_numbers_numerically() {
126 let mut names = vec!["hwmon10", "hwmon2", "hwmon1", "fan"];
127 names.sort_by(|a, b| natural_cmp(a, b));
128 assert_eq!(names, vec!["fan", "hwmon1", "hwmon2", "hwmon10"]);
129 }
130}