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 print(
63 mounts: &[&Mount],
64 args: &Args,
65) -> Result<(), std::io::Error> {
66 let units = args.units;
67 let mut csv = Csv::new(args.csv_separator, std::io::stdout());
68 for col in args.cols.cols() {
69 csv.cell(col.title())?;
70 }
71 csv.end_line()?;
72 for mount in mounts {
73 for col in args.cols.cols() {
74 match col {
75 Col::Id => csv.cell_opt(mount.info.id),
76 Col::Dev => csv.cell(&mount.info.dev),
77 Col::Filesystem => csv.cell(&mount.info.fs),
78 Col::Label => csv.cell_opt(mount.fs_label.as_ref()),
79 Col::Type => csv.cell(&mount.info.fs_type),
80 Col::Remote => csv.cell(if mount.is_remote() { "yes" } else { "no" }),
81 Col::Disk => csv.cell_opt(mount.disk.as_ref().map(|d| d.disk_type())),
82 Col::Used => csv.cell_opt(mount.stats().map(|s| units.fmt(s.used()))),
83 Col::Use => csv.cell_opt(mount.stats().map(|s| s.use_share())),
84 Col::UsePercent => csv.cell_opt(
85 mount
86 .stats()
87 .map(|s| format!("{:.0}%", 100.0 * s.use_share())),
88 ),
89 Col::Free => csv.cell_opt(mount.stats().map(|s| units.fmt(s.available()))),
90 Col::FreePercent => csv.cell_opt(
91 mount
92 .stats()
93 .map(|s| format!("{:.0}%", 100.0 * (1.0 - s.use_share()))),
94 ),
95 Col::Size => csv.cell_opt(mount.stats().map(|s| units.fmt(s.size()))),
96 Col::InodesUsed => csv.cell_opt(mount.inodes().map(|i| i.used())),
97 Col::InodesUse => csv.cell_opt(mount.inodes().map(|i| i.use_share())),
98 Col::InodesUsePercent => csv.cell_opt(
99 mount
100 .inodes()
101 .map(|i| format!("{:.0}%", 100.0 * i.use_share())),
102 ),
103 Col::InodesFree => csv.cell_opt(mount.inodes().map(|i| i.favail)),
104 Col::InodesCount => csv.cell_opt(mount.inodes().map(|i| i.files)),
105 Col::MountPoint => csv.cell(mount.info.mount_point.to_string_lossy()),
106 Col::Uuid => csv.cell(mount.uuid.as_ref().map_or("", |v| v)),
107 Col::PartUuid => csv.cell(mount.part_uuid.as_ref().map_or("", |v| v)),
108 Col::MountOptions => csv.cell(mount.info.options_string()),
109 Col::CompressLevel => csv.cell_opt(mount.info.option_value("compress")),
110 }?;
111 }
112 csv.end_line()?;
113 }
114 Ok(())
115}
116
117#[test]
118fn test_csv() {
119 use std::io::Cursor;
120 let mut w = Cursor::new(Vec::new());
121 let mut csv = Csv::new(';', &mut w);
122 csv.cell("1;2;3").unwrap();
123 csv.cell("\"").unwrap();
124 csv.cell("").unwrap();
125 csv.end_line().unwrap();
126 csv.cell(3).unwrap();
127 let s = String::from_utf8(w.into_inner()).unwrap();
128 assert_eq!(
129 s,
130 r#""1;2;3";"""";;
1313;"#,
132 );
133}