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