dysk_cli/
csv.rs

1use {
2    crate::{
3        Args,
4        col::Col,
5    },
6    lfs_core::*,
7    std::{
8        fmt::Display,
9        io::Write,
10    },
11};
12
13/// Utility to write in CSV
14struct Csv<W: Write> {
15    separator: char,
16    w: W,
17}
18
19impl<W: Write> Csv<W> {
20    pub fn new(
21        separator: char,
22        w: W,
23    ) -> Self {
24        Self { separator, w }
25    }
26    pub fn cell<D: Display>(
27        &mut self,
28        content: D,
29    ) -> Result<(), std::io::Error> {
30        let s = content.to_string();
31        let needs_quotes = s.contains(self.separator) || s.contains('"') || s.contains('\n');
32        if needs_quotes {
33            write!(self.w, "\"")?;
34            for c in s.chars() {
35                if c == '"' {
36                    write!(self.w, "\"\"")?;
37                } else {
38                    write!(self.w, "{}", c)?;
39                }
40            }
41            write!(self.w, "\"")?;
42        } else {
43            write!(self.w, "{}", s)?;
44        }
45        write!(self.w, "{}", self.separator)
46    }
47    pub fn cell_opt<D: Display>(
48        &mut self,
49        content: Option<D>,
50    ) -> Result<(), std::io::Error> {
51        if let Some(c) = content {
52            self.cell(c)
53        } else {
54            write!(self.w, "{}", self.separator)
55        }
56    }
57    pub fn end_line(&mut self) -> Result<(), std::io::Error> {
58        writeln!(self.w)
59    }
60}
61
62pub fn write<W: Write>(
63    w: &mut W,
64    mounts: &[&Mount],
65    args: &Args,
66) -> std::io::Result<()> {
67    let units = args.units;
68    let mut csv = Csv::new(args.csv_separator, w);
69    for col in args.cols.cols() {
70        csv.cell(col.title())?;
71    }
72    csv.end_line()?;
73    for mount in mounts {
74        for col in args.cols.cols() {
75            match col {
76                Col::Id => csv.cell_opt(mount.info.id),
77                Col::Dev => csv.cell(&mount.info.dev),
78                Col::Filesystem => csv.cell(&mount.info.fs),
79                Col::Label => csv.cell_opt(mount.fs_label.as_ref()),
80                Col::Type => csv.cell(&mount.info.fs_type),
81                Col::Remote => csv.cell(if mount.is_remote() { "yes" } else { "no" }),
82                Col::Disk => csv.cell_opt(mount.disk.as_ref().map(|d| d.disk_type())),
83                Col::Used => csv.cell_opt(mount.stats().map(|s| units.fmt(s.used()))),
84                Col::Use => csv.cell_opt(mount.stats().map(|s| s.use_share())),
85                Col::UsePercent => csv.cell_opt(
86                    mount
87                        .stats()
88                        .map(|s| format!("{:.0}%", 100.0 * s.use_share())),
89                ),
90                Col::Free => csv.cell_opt(mount.stats().map(|s| units.fmt(s.available()))),
91                Col::FreePercent => csv.cell_opt(
92                    mount
93                        .stats()
94                        .map(|s| format!("{:.0}%", 100.0 * (1.0 - s.use_share()))),
95                ),
96                Col::Size => csv.cell_opt(mount.stats().map(|s| units.fmt(s.size()))),
97                Col::InodesUsed => csv.cell_opt(mount.inodes().map(|i| i.used())),
98                Col::InodesUse => csv.cell_opt(mount.inodes().map(|i| i.use_share())),
99                Col::InodesUsePercent => csv.cell_opt(
100                    mount
101                        .inodes()
102                        .map(|i| format!("{:.0}%", 100.0 * i.use_share())),
103                ),
104                Col::InodesFree => csv.cell_opt(mount.inodes().map(|i| i.favail)),
105                Col::InodesCount => csv.cell_opt(mount.inodes().map(|i| i.files)),
106                Col::MountPoint => csv.cell(mount.info.mount_point.to_string_lossy()),
107                Col::Uuid => csv.cell(mount.uuid.as_ref().map_or("", |v| v)),
108                Col::PartUuid => csv.cell(mount.part_uuid.as_ref().map_or("", |v| v)),
109                Col::MountOptions => csv.cell(mount.info.options_string()),
110                Col::CompressLevel => csv.cell_opt(mount.info.option_value("compress")),
111            }?;
112        }
113        csv.end_line()?;
114    }
115    Ok(())
116}
117
118#[test]
119fn test_csv() {
120    use std::io::Cursor;
121    let mut w = Cursor::new(Vec::new());
122    let mut csv = Csv::new(';', &mut w);
123    csv.cell("1;2;3").unwrap();
124    csv.cell("\"").unwrap();
125    csv.cell("").unwrap();
126    csv.end_line().unwrap();
127    csv.cell(3).unwrap();
128    let s = String::from_utf8(w.into_inner()).unwrap();
129    assert_eq!(
130        s,
131        r#""1;2;3";"""";;
1323;"#,
133    );
134}