linux_df_parser/
lib.rs

1//! linux-df-parser
2//! ===============
3//!
4//! A simple parser for the Linux `df` command. To get numbers in bytes, call `df` with `-B1`
5//! argument: `/bin/df -B1`
6//!
7//! Usage
8//! -----
9//! ```
10//! # use linux_df_parser::Df;
11//! let s = r#"
12//!     df: /run/user/1000/doc: Operation not permitted
13//!     Filesystem                 1B-blocks         Used    Available Use% Mounted on
14//!     udev                     12294803456            0  12294803456   0% /dev
15//!     /dev/nvme0n1p2             493201408    121312256    346304512  26% /boot
16//! "#.trim();
17//! let df = Df::from(s);
18//! assert_eq!(df.get_by_filesystem("/dev/nvme0n1p2").unwrap().used, 121312256);
19//! ```
20
21#![warn(clippy::all, missing_docs, nonstandard_style, future_incompatible)]
22
23/// `df` command representation
24#[derive(Debug)]
25pub struct Df(pub Vec<DfLine>);
26
27/// A line of the `df` command
28#[derive(Debug)]
29pub struct DfLine {
30    /// Filesystem
31    pub filesystem: String,
32    /// Mount point
33    pub mounted: String,
34    /// Total size
35    pub total: u64,
36    /// Used size
37    pub used: u64,
38}
39
40impl From<&str> for Df {
41    fn from(value: &str) -> Self {
42        Self(value.lines().filter_map(DfLine::from_str).collect())
43    }
44}
45
46impl Df {
47    /// Returns a [`DfLine`] by filesystem
48    pub fn get_by_filesystem(&self, filesystem: &str) -> Option<&DfLine> {
49        self.0.iter().find(|x| x.filesystem == filesystem)
50    }
51
52    /// Returns a [`DfLine`] by mount point
53    pub fn get_by_mount(&self, mounted: &str) -> Option<&DfLine> {
54        self.0.iter().find(|x| x.mounted == mounted)
55    }
56}
57
58impl DfLine {
59    fn from_str(s: &str) -> Option<Self> {
60        let mut parts = s.split_whitespace();
61        let filesystem = parts.next()?.into();
62        let total = parts.next()?.parse().ok()?;
63        let used = parts.next()?.parse().ok()?;
64        parts.next()?;
65        parts.next()?;
66        let mounted = parts.next()?.into();
67        Some(Self {
68            filesystem,
69            mounted,
70            total,
71            used,
72        })
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_line_from_str() {
82        assert!(DfLine::from_str("df: /run/user/1000/doc: Operation not permitted").is_none());
83        assert!(DfLine::from_str(
84            "Filesystem                 1B-blocks         Used    Available Use% Mounted on"
85        )
86        .is_none());
87
88        let line = DfLine::from_str(
89            "udev                     12294803456            0  12294803456   0% /dev",
90        )
91        .unwrap();
92        assert_eq!(line.filesystem, "udev");
93        assert_eq!(line.mounted, "/dev");
94        assert_eq!(line.total, 12294803456);
95        assert_eq!(line.used, 0);
96    }
97
98    #[test]
99    fn test_df() {
100        let src = r#"
101            df: /run/user/1000/doc: Operation not permitted
102            Filesystem                 1B-blocks         Used    Available Use% Mounted on
103            udev                     12294803456            0  12294803456   0% /dev
104            /dev/nvme0n1p2             493201408    121312256    346304512  26% /boot
105            /dev/nvme0n1p1             535805952      3579904    532226048   1% /boot/efi
106        "#
107        .trim();
108
109        // From str
110        let df = Df::from(src);
111        assert_eq!(df.0.len(), 3);
112        assert_eq!(df.0.get(1).unwrap().mounted, "/boot");
113
114        // `get_by_filesystem`
115        assert!(df.get_by_filesystem("unknown").is_none());
116        assert_eq!(df.get_by_filesystem("udev").unwrap().mounted, "/dev");
117
118        // `get_by_mount`
119        assert!(df.get_by_mount("unknown").is_none());
120        assert_eq!(df.get_by_mount("/dev").unwrap().filesystem, "udev");
121    }
122}