dysk_cli/
table.rs

1use {
2    crate::{
3        Args,
4        col::Col,
5    },
6    lfs_core::*,
7    std::io::Write,
8    termimad::{
9        CompoundStyle,
10        MadSkin,
11        ProgressBar,
12        crossterm::style::Color::*,
13        minimad::{
14            self,
15            OwningTemplateExpander,
16            TableBuilder,
17        },
18    },
19};
20
21// those colors are chosen to be "redish" for used, "greenish" for available
22// and, most importantly, to work on both white and black backgrounds. If you
23// find a better combination, please show me.
24static USED_COLOR: u8 = 209;
25static AVAI_COLOR: u8 = 65;
26static SIZE_COLOR: u8 = 172;
27
28static BAR_WIDTH: usize = 5;
29static INODES_BAR_WIDTH: usize = 5;
30
31pub fn write<W: Write>(
32    w: &mut W,
33    mounts: &[&Mount],
34    color: bool,
35    args: &Args,
36) -> std::io::Result<()> {
37    if args.cols.is_empty() {
38        return Ok(());
39    }
40    let units = args.units;
41    let mut expander = OwningTemplateExpander::new();
42    expander.set_default("");
43    for mount in mounts {
44        let sub = expander
45            .sub("rows")
46            .set(
47                "id",
48                mount
49                    .info
50                    .id
51                    .as_ref()
52                    .map_or("".to_string(), |i| i.to_string()),
53            )
54            .set("dev", mount.info.dev)
55            .set("filesystem", &mount.info.fs)
56            .set("disk", mount.disk.as_ref().map_or("", |d| d.disk_type()))
57            .set("type", &mount.info.fs_type)
58            .set("mount-point", mount.info.mount_point.to_string_lossy())
59            .set("mount-options", mount.info.options_string())
60            .set_option("uuid", mount.uuid.as_ref())
61            .set_option("part_uuid", mount.part_uuid.as_ref())
62            .set_option(
63                "compress-level",
64                mount.info.option_value("compress"),
65            );
66        if let Some(label) = &mount.fs_label {
67            sub.set("label", label);
68        }
69        if mount.is_remote() {
70            sub.set("remote", "x");
71        }
72        if let Some(stats) = mount.stats() {
73            let use_share = stats.use_share();
74            let free_share = 1.0 - use_share;
75            sub.set("size", units.fmt(stats.size()))
76                .set("used", units.fmt(stats.used()))
77                .set("use-percents", format!("{:.0}%", 100.0 * use_share))
78                .set_md("bar", progress_bar_md(use_share, BAR_WIDTH, args.ascii))
79                .set("free", units.fmt(stats.available()))
80                .set("free-percents", format!("{:.0}%", 100.0 * free_share));
81            if let Some(inodes) = &stats.inodes {
82                let iuse_share = inodes.use_share();
83                sub.set("inodes", inodes.files)
84                    .set("iused", inodes.used())
85                    .set("iuse-percents", format!("{:.0}%", 100.0 * iuse_share))
86                    .set_md(
87                        "ibar",
88                        progress_bar_md(iuse_share, INODES_BAR_WIDTH, args.ascii),
89                    )
90                    .set("ifree", inodes.favail);
91            }
92        } else if mount.is_timeout() {
93            sub.set("use-error", "timeout");
94        } else if mount.is_unreachable() {
95            sub.set("use-error", "unreachable");
96        }
97    }
98    let mut skin = if color {
99        make_colored_skin()
100    } else {
101        MadSkin::no_style()
102    };
103    if args.ascii {
104        skin.limit_to_ascii();
105    }
106
107    let mut tbl = TableBuilder::default();
108    for col in args.cols.cols() {
109        tbl.col(
110            minimad::Col::new(
111                col.title(),
112                match col {
113                    Col::Id => "${id}",
114                    Col::Dev => "${dev}",
115                    Col::Filesystem => "${filesystem}",
116                    Col::Label => "${label}",
117                    Col::Disk => "${disk}",
118                    Col::Type => "${type}",
119                    Col::Remote => "${remote}",
120                    Col::Used => "~~${used}~~",
121                    Col::Use => "~~${use-percents}~~ ${bar}~~${use-error}~~",
122                    Col::UsePercent => "~~${use-percents}~~",
123                    Col::Free => "*${free}*",
124                    Col::FreePercent => "*${free-percents}*",
125                    Col::Size => "**${size}**",
126                    Col::InodesFree => "*${ifree}*",
127                    Col::InodesUsed => "~~${iused}~~",
128                    Col::InodesUse => "~~${iuse-percents}~~ ${ibar}",
129                    Col::InodesUsePercent => "~~${iuse-percents}~~",
130                    Col::InodesCount => "**${inodes}**",
131                    Col::MountPoint => "${mount-point}",
132                    Col::Uuid => "${uuid}",
133                    Col::PartUuid => "${part_uuid}",
134                    Col::MountOptions => "${mount-options}",
135                    Col::CompressLevel => "${compress-level}",
136                },
137            )
138            .align_content(col.content_align())
139            .align_header(col.header_align()),
140        );
141    }
142
143    skin.write_owning_expander_md(w, &expander, &tbl)
144}
145
146fn make_colored_skin() -> MadSkin {
147    MadSkin {
148        bold: CompoundStyle::with_fg(AnsiValue(SIZE_COLOR)), // size
149        inline_code: CompoundStyle::with_fgbg(AnsiValue(USED_COLOR), AnsiValue(AVAI_COLOR)), // use bar
150        strikeout: CompoundStyle::with_fg(AnsiValue(USED_COLOR)),                            // use%
151        italic: CompoundStyle::with_fg(AnsiValue(AVAI_COLOR)), // available
152        ..Default::default()
153    }
154}
155
156fn progress_bar_md(
157    share: f64,
158    bar_width: usize,
159    ascii: bool,
160) -> String {
161    if ascii {
162        let count = (share * bar_width as f64).round() as usize;
163        let bar: String = "".repeat(count);
164        let no_bar: String = "-".repeat(bar_width - count);
165        format!("~~{}~~*{}*", bar, no_bar)
166    } else {
167        let pb = ProgressBar::new(share as f32, bar_width);
168        format!("`{:<width$}`", pb, width = bar_width)
169    }
170}