1use {
2 crate::{
3 Args,
4 col::Col,
5 },
6 lfs_core::*,
7 std::{
8 fmt::Display,
9 io::Write,
10 },
11};
12
13struct 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}