1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
use {
    crate::{
        Args, col::Col,
    },
    lfs_core::*,
    std::{
        fmt::Display,
        io::Write,
    },
};

/// Utility to write in CSV
struct Csv<W: Write> {
    separator: char,
    w: W,
}

impl<W: Write> Csv<W> {
    pub fn new(separator: char, w: W) -> Self {
        Self { separator, w }
    }
    pub fn cell<D: Display>(&mut self, content: D) -> Result<(), std::io::Error> {
        let s = content.to_string();
        let needs_quotes = s.contains(self.separator) || s.contains('"') || s.contains('\n');
        if needs_quotes {
            write!(self.w, "\"")?;
            for c in s.chars() {
                if c == '"' {
                    write!(self.w, "\"\"")?;
                } else {
                    write!(self.w, "{}", c)?;
                }
            }
            write!(self.w, "\"")?;
        } else {
            write!(self.w, "{}", s)?;
        }
        write!(self.w, "{}", self.separator)
    }
    pub fn cell_opt<D: Display>(&mut self, content: Option<D>) -> Result<(), std::io::Error> {
        if let Some(c) = content {
            self.cell(c)
        } else {
            write!(self.w, "{}", self.separator)
        }
    }
    pub fn end_line(&mut self) -> Result<(), std::io::Error> {
        writeln!(self.w)
    }
}

pub fn print(mounts: &[&Mount], args: &Args) -> Result<(), std::io::Error> {
    let units = args.units;
    let mut csv = Csv::new(args.csv_separator, std::io::stdout());
    for col in args.cols.cols() {
        csv.cell(col.title())?;
    }
    csv.end_line()?;
    for mount in mounts {
        for col in args.cols.cols() {
            match col {
                Col::Id => csv.cell(mount.info.id),
                Col::Dev => csv.cell(format!("{}:{}", mount.info.dev.major, mount.info.dev.minor)),
                Col::Filesystem => csv.cell(&mount.info.fs),
                Col::Label => csv.cell_opt(mount.fs_label.as_ref()),
                Col::Type => csv.cell(&mount.info.fs_type),
                Col::Remote => csv.cell(if mount.info.is_remote() { "yes" } else { "no" }),
                Col::Disk => csv.cell_opt(mount.disk.as_ref().map(|d| d.disk_type())),
                Col::Used => csv.cell_opt(mount.stats().map(|s| units.fmt(s.used()))),
                Col::Use => csv.cell_opt(mount.stats().map(|s| s.use_share())),
                Col::UsePercent => csv.cell_opt(mount.stats().map(|s| format!("{:.0}%", 100.0 * s.use_share()))),
                Col::Free => csv.cell_opt(mount.stats().map(|s| units.fmt(s.available()))),
                Col::FreePercent => csv.cell_opt(mount.stats().map(|s| format!("{:.0}%", 100.0 * (1.0 - s.use_share())))),
                Col::Size => csv.cell_opt(mount.stats().map(|s| units.fmt(s.size()))),
                Col::InodesUsed => csv.cell_opt(mount.inodes().map(|i| i.used())),
                Col::InodesUse => csv.cell_opt(mount.inodes().map(|i| i.use_share())),
                Col::InodesUsePercent => csv.cell_opt(mount.inodes().map(|i| format!("{:.0}%", 100.0 * i.use_share()))),
                Col::InodesFree => csv.cell_opt(mount.inodes().map(|i| i.favail)),
                Col::InodesCount => csv.cell_opt(mount.inodes().map(|i| i.files)),
                Col::MountPoint => csv.cell(&mount.info.mount_point.to_string_lossy()),
            }?;
        }
        csv.end_line()?;
    }
    Ok(())
}

#[test]
fn test_csv() {
    use std::io::Cursor;
    let mut w = Cursor::new(Vec::new());
    let mut csv = Csv::new(';', &mut w);
    csv.cell("1;2;3").unwrap();
    csv.cell("\"").unwrap();
    csv.cell("").unwrap();
    csv.end_line().unwrap();
    csv.cell(3).unwrap();
    let s = String::from_utf8(w.into_inner()).unwrap();
    assert_eq!(
        s,
r#""1;2;3";"""";;
3;"#,
    );
}