dysk_cli/
col.rs

1use {
2    crate::order::Order,
3    lfs_core::Mount,
4    std::{
5        cmp::Ordering,
6        fmt,
7        str::FromStr,
8    },
9    termimad::minimad::Alignment,
10};
11
12macro_rules! col_enum {
13    (@just_variant $variant:ident $discarded:ident) => {
14        Col::$variant
15    };
16    ($($variant:ident $name:literal $($alias:literal)* : $title:literal $($def:ident)*,)*) => {
17        /// A column of the lfs table.
18        #[derive(Debug, Clone, Copy, PartialEq)]
19        pub enum Col {
20            $($variant,)*
21        }
22        pub static ALL_COLS: &[Col] = &[
23            $(Col::$variant,)*
24        ];
25        pub static DEFAULT_COLS: &[Col] = &[
26            $(
27                $(col_enum!(@just_variant $variant $def),)*
28            )*
29        ];
30        impl FromStr for Col {
31            type Err = ParseColError;
32            fn from_str(s: &str) -> Result<Self, ParseColError> {
33                match s {
34                    $(
35                        $name => Ok(Self::$variant),
36                        $(
37                            $alias => Ok(Self::$variant),
38                        )*
39                    )*
40                    _ => Err(ParseColError::new(s)),
41                }
42            }
43        }
44        impl fmt::Display for Col {
45            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46                match self {
47                    $(
48                        Self::$variant => write!(f, "{}", self.title()),
49                    )*
50                }
51            }
52        }
53        impl Col {
54            pub fn name(self) -> &'static str {
55                match self {
56                    $(
57                        Self::$variant => $name,
58                    )*
59                }
60            }
61            pub fn title(self) -> &'static str {
62                match self {
63                    $(
64                        Self::$variant => $title,
65                    )*
66                }
67            }
68            pub fn aliases(self) -> &'static [&'static str] {
69                match self {
70                    $(
71                        Self::$variant => &[$($alias,)*],
72                    )*
73                }
74            }
75            pub fn is_default(self) -> bool {
76                DEFAULT_COLS.contains(&self)
77            }
78        }
79    };
80}
81
82// definition of all columns and their names
83// in the --cols definition
84col_enum!(
85    // syntax:
86    // Variant name [aliases]: title [default]
87    Id "id": "id",
88    Dev "dev" "device" "device_id": "dev",
89    Filesystem "fs" "filesystem": "filesystem" default,
90    Label "label": "label",
91    Type "type": "type" default,
92    Remote "remote" "rem": "remote",
93    Disk "disk" "dsk": "disk" default,
94    Used "used": "used" default,
95    Use "use": "use" default,
96    UsePercent "use_percent": "use%",
97    Free "free": "free" default,
98    FreePercent "free_percent": "free%",
99    Size "size": "size" default,
100    InodesUsed "inodes_used" "iused": "used inodes",
101    InodesUse "inodes" "ino" "inodes_use" "iuse": "inodes",
102    InodesUsePercent "inodes_use_percent" "iuse_percent": "inodes%",
103    InodesFree "inodes_free" "ifree": "free inodes",
104    InodesCount "inodes_total" "inodes_count" "itotal": "inodes total",
105    MountPoint "mount" "mount_point" "mp": "mount point" default,
106    Uuid "uuid": "UUID",
107    PartUuid "partuuid" "part_uuid": "PARTUUID",
108    MountOptions "options" "mount_options": "mount options",
109    CompressLevel "compress" "compress_level": "compress",
110);
111
112impl Col {
113    pub fn header_align(self) -> Alignment {
114        match self {
115            Self::Label => Alignment::Left,
116            Self::MountPoint => Alignment::Left,
117            Self::MountOptions => Alignment::Left,
118            _ => Alignment::Center,
119        }
120    }
121    pub fn content_align(self) -> Alignment {
122        match self {
123            Self::Id => Alignment::Right,
124            Self::Dev => Alignment::Center,
125            Self::Filesystem => Alignment::Left,
126            Self::Label => Alignment::Left,
127            Self::Type => Alignment::Center,
128            Self::Remote => Alignment::Center,
129            Self::Disk => Alignment::Center,
130            Self::Used => Alignment::Right,
131            Self::Use => Alignment::Right,
132            Self::UsePercent => Alignment::Right,
133            Self::Free => Alignment::Right,
134            Self::FreePercent => Alignment::Right,
135            Self::Size => Alignment::Right,
136            Self::InodesUsed => Alignment::Right,
137            Self::InodesUse => Alignment::Right,
138            Self::InodesUsePercent => Alignment::Right,
139            Self::InodesFree => Alignment::Right,
140            Self::InodesCount => Alignment::Right,
141            Self::MountPoint => Alignment::Left,
142            Self::Uuid => Alignment::Left,
143            Self::PartUuid => Alignment::Left,
144            Self::MountOptions => Alignment::Left,
145            Self::CompressLevel => Alignment::Center,
146        }
147    }
148    pub fn description(self) -> &'static str {
149        match self {
150            Self::Id => "mount point id",
151            Self::Dev => "device id",
152            Self::Filesystem => "filesystem",
153            Self::Label => "volume label",
154            Self::Type => "filesystem type",
155            Self::Remote => "whether it's a remote filesystem",
156            Self::Disk => "storage type",
157            Self::Used => "size used",
158            Self::Use => "usage graphical view",
159            Self::UsePercent => "percentage of blocks used",
160            Self::Free => "free bytes",
161            Self::FreePercent => "percentage of free blocks",
162            Self::Size => "total size",
163            Self::InodesUsed => "number of inodes used",
164            Self::InodesUse => "graphical view of inodes usage",
165            Self::InodesUsePercent => "percentage of inodes used",
166            Self::InodesFree => "number of free inodes",
167            Self::InodesCount => "total count of inodes",
168            Self::MountPoint => "mount point",
169            Self::Uuid => "filesystem UUID",
170            Self::PartUuid => "partition UUID",
171            Self::MountOptions => "mount options (linux only)",
172            Self::CompressLevel => "compress algo/level",
173        }
174    }
175    pub fn comparator(self) -> impl for<'a, 'b> FnMut(&'a Mount, &'b Mount) -> Ordering {
176        match self {
177            Self::Id => |a: &Mount, b: &Mount| a.info.id.cmp(&b.info.id),
178            Self::Dev => |a: &Mount, b: &Mount| a.info.dev.cmp(&b.info.dev),
179            Self::Filesystem => |a: &Mount, b: &Mount| a.info.fs.cmp(&b.info.fs),
180            Self::Label => |a: &Mount, b: &Mount| match (&a.fs_label, &b.fs_label) {
181                (Some(a), Some(b)) => a.cmp(b),
182                (Some(_), None) => Ordering::Less,
183                (None, Some(_)) => Ordering::Greater,
184                (None, None) => Ordering::Equal,
185            },
186            Self::Type => |a: &Mount, b: &Mount| a.info.fs_type.cmp(&b.info.fs_type),
187            Self::Remote => |a: &Mount, b: &Mount| a.is_remote().cmp(&b.is_remote()),
188            Self::Disk => |a: &Mount, b: &Mount| match (&a.disk, &b.disk) {
189                (Some(a), Some(b)) => a
190                    .disk_type()
191                    .to_lowercase()
192                    .cmp(&b.disk_type().to_lowercase()),
193                (Some(_), None) => Ordering::Greater,
194                (None, Some(_)) => Ordering::Less,
195                (None, None) => Ordering::Equal,
196            },
197            Self::Used => |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) {
198                (Some(a), Some(b)) => a.used().cmp(&b.used()),
199                (Some(_), None) => Ordering::Greater,
200                (None, Some(_)) => Ordering::Less,
201                (None, None) => Ordering::Equal,
202            },
203            Self::Use | Self::UsePercent => |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) {
204                // the 'use' column shows the percentage of used blocks, so it makes sense
205                // to sort by use_share for it
206                // SAFETY: use_share() doesn't return NaN
207                (Some(a), Some(b)) => a.use_share().partial_cmp(&b.use_share()).unwrap(),
208                (Some(_), None) => Ordering::Greater,
209                (None, Some(_)) => Ordering::Less,
210                (None, None) => Ordering::Equal,
211            },
212            Self::Free => |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) {
213                (Some(a), Some(b)) => a.available().cmp(&b.available()),
214                (Some(_), None) => Ordering::Greater,
215                (None, Some(_)) => Ordering::Less,
216                (None, None) => Ordering::Equal,
217            },
218            Self::FreePercent => |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) {
219                (Some(a), Some(b)) => b.use_share().partial_cmp(&a.use_share()).unwrap(),
220                (Some(_), None) => Ordering::Greater,
221                (None, Some(_)) => Ordering::Less,
222                (None, None) => Ordering::Equal,
223            },
224            Self::Size => |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) {
225                (Some(a), Some(b)) => a.size().cmp(&b.size()),
226                (Some(_), None) => Ordering::Greater,
227                (None, Some(_)) => Ordering::Less,
228                (None, None) => Ordering::Equal,
229            },
230            Self::InodesUsed => |a: &Mount, b: &Mount| match (&a.inodes(), &b.inodes()) {
231                (Some(a), Some(b)) => a.used().cmp(&b.used()),
232                (Some(_), None) => Ordering::Greater,
233                (None, Some(_)) => Ordering::Less,
234                (None, None) => Ordering::Equal,
235            },
236            Self::InodesUsePercent | Self::InodesUse => {
237                |a: &Mount, b: &Mount| match (&a.inodes(), &b.inodes()) {
238                    // SAFETY: use_share() doesn't return NaN
239                    (Some(a), Some(b)) => a.use_share().partial_cmp(&b.use_share()).unwrap(),
240                    (Some(_), None) => Ordering::Greater,
241                    (None, Some(_)) => Ordering::Less,
242                    (None, None) => Ordering::Equal,
243                }
244            }
245            Self::InodesFree => |a: &Mount, b: &Mount| match (&a.inodes(), &b.inodes()) {
246                (Some(a), Some(b)) => a.favail.cmp(&b.favail),
247                (Some(_), None) => Ordering::Greater,
248                (None, Some(_)) => Ordering::Less,
249                (None, None) => Ordering::Equal,
250            },
251            Self::InodesCount => |a: &Mount, b: &Mount| match (&a.inodes(), &b.inodes()) {
252                (Some(a), Some(b)) => a.files.cmp(&b.files),
253                (Some(_), None) => Ordering::Greater,
254                (None, Some(_)) => Ordering::Less,
255                (None, None) => Ordering::Equal,
256            },
257            Self::MountPoint => |a: &Mount, b: &Mount| a.info.mount_point.cmp(&b.info.mount_point),
258            Self::Uuid => |a: &Mount, b: &Mount| match (&a.uuid, &b.uuid) {
259                (Some(a), Some(b)) => a.cmp(b),
260                (Some(_), None) => Ordering::Less,
261                (None, Some(_)) => Ordering::Greater,
262                (None, None) => Ordering::Equal,
263            },
264            Self::PartUuid => |a: &Mount, b: &Mount| match (&a.part_uuid, &b.part_uuid) {
265                (Some(a), Some(b)) => a.cmp(b),
266                (Some(_), None) => Ordering::Less,
267                (None, Some(_)) => Ordering::Greater,
268                (None, None) => Ordering::Equal,
269            },
270            Self::MountOptions => |a: &Mount, b: &Mount| a.info.options_string()
271                .cmp(&b.info.options_string()),
272            Self::CompressLevel => |a: &Mount, b: &Mount| match (a.info.option_value("compress"), b.info.option_value("compress")) {
273                (Some(a), Some(b)) => a.cmp(b),
274                (Some(_), None) => Ordering::Less,
275                (None, Some(_)) => Ordering::Greater,
276                (None, None) => Ordering::Equal,
277            },
278        }
279    }
280    pub fn default_sort_order(self) -> Order {
281        match self {
282            Self::Id => Order::Asc,
283            Self::Dev => Order::Asc,
284            Self::Filesystem => Order::Asc,
285            Self::Label => Order::Asc,
286            Self::Type => Order::Asc,
287            Self::Remote => Order::Desc,
288            Self::Disk => Order::Asc,
289            Self::Used => Order::Asc,
290            Self::Use => Order::Desc,
291            Self::UsePercent => Order::Asc,
292            Self::Free => Order::Asc,
293            Self::FreePercent => Order::Desc,
294            Self::Size => Order::Desc,
295            Self::InodesUsed => Order::Asc,
296            Self::InodesUse => Order::Asc,
297            Self::InodesUsePercent => Order::Asc,
298            Self::InodesFree => Order::Asc,
299            Self::InodesCount => Order::Asc,
300            Self::MountPoint => Order::Asc,
301            Self::Uuid => Order::Asc,
302            Self::PartUuid => Order::Asc,
303            Self::MountOptions => Order::Asc,
304            Self::CompressLevel => Order::Asc,
305        }
306    }
307    pub fn default_sort_col() -> Self {
308        Self::Size
309    }
310}
311
312#[derive(Debug)]
313pub struct ParseColError {
314    /// the string which couldn't be parsed
315    pub raw: String,
316}
317impl ParseColError {
318    pub fn new<S: Into<String>>(s: S) -> Self {
319        Self { raw: s.into() }
320    }
321}
322impl fmt::Display for ParseColError {
323    fn fmt(
324        &self,
325        f: &mut fmt::Formatter<'_>,
326    ) -> fmt::Result {
327        write!(
328            f,
329            "{:?} can't be parsed as a column; use 'dysk --list-cols' to see all column names",
330            self.raw,
331        )
332    }
333}
334impl std::error::Error for ParseColError {}