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);
109
110impl Col {
111    pub fn header_align(self) -> Alignment {
112        match self {
113            Self::Label => Alignment::Left,
114            Self::MountPoint => Alignment::Left,
115            _ => Alignment::Center,
116        }
117    }
118    pub fn content_align(self) -> Alignment {
119        match self {
120            Self::Id => Alignment::Right,
121            Self::Dev => Alignment::Center,
122            Self::Filesystem => Alignment::Left,
123            Self::Label => Alignment::Left,
124            Self::Type => Alignment::Center,
125            Self::Remote => Alignment::Center,
126            Self::Disk => Alignment::Center,
127            Self::Used => Alignment::Right,
128            Self::Use => Alignment::Right,
129            Self::UsePercent => Alignment::Right,
130            Self::Free => Alignment::Right,
131            Self::FreePercent => Alignment::Right,
132            Self::Size => Alignment::Right,
133            Self::InodesUsed => Alignment::Right,
134            Self::InodesUse => Alignment::Right,
135            Self::InodesUsePercent => Alignment::Right,
136            Self::InodesFree => Alignment::Right,
137            Self::InodesCount => Alignment::Right,
138            Self::MountPoint => Alignment::Left,
139            Self::Uuid => Alignment::Left,
140            Self::PartUuid => Alignment::Left,
141        }
142    }
143    pub fn description(self) -> &'static str {
144        match self {
145            Self::Id => "mount point id",
146            Self::Dev => "device id",
147            Self::Filesystem => "filesystem",
148            Self::Label => "volume label",
149            Self::Type => "filesystem type",
150            Self::Remote => "whether it's a remote filesystem",
151            Self::Disk => "storage type",
152            Self::Used => "size used",
153            Self::Use => "usage graphical view",
154            Self::UsePercent => "percentage of blocks used",
155            Self::Free => "free bytes",
156            Self::FreePercent => "percentage of free blocks",
157            Self::Size => "total size",
158            Self::InodesUsed => "number of inodes used",
159            Self::InodesUse => "graphical view of inodes usage",
160            Self::InodesUsePercent => "percentage of inodes used",
161            Self::InodesFree => "number of free inodes",
162            Self::InodesCount => "total count of inodes",
163            Self::MountPoint => "mount point",
164            Self::Uuid => "filesystem UUID",
165            Self::PartUuid => "partition UUID",
166        }
167    }
168    pub fn comparator(self) -> impl for<'a, 'b> FnMut(&'a Mount, &'b Mount) -> Ordering {
169        match self {
170            Self::Id => |a: &Mount, b: &Mount| a.info.id.cmp(&b.info.id),
171            Self::Dev => |a: &Mount, b: &Mount| a.info.dev.cmp(&b.info.dev),
172            Self::Filesystem =>  |a: &Mount, b: &Mount| a.info.fs.cmp(&b.info.fs),
173            Self::Label =>  |a: &Mount, b: &Mount| match (&a.fs_label, &b.fs_label) {
174                (Some(a), Some(b)) => a.cmp(b),
175                (Some(_), None) => Ordering::Less,
176                (None, Some(_)) => Ordering::Greater,
177                (None, None) => Ordering::Equal,
178            },
179            Self::Type =>  |a: &Mount, b: &Mount| a.info.fs_type.cmp(&b.info.fs_type),
180            Self::Remote =>  |a: &Mount, b: &Mount| a.info.is_remote().cmp(&b.info.is_remote()),
181            Self::Disk =>  |a: &Mount, b: &Mount| match (&a.disk, &b.disk) {
182                (Some(a), Some(b)) => a.disk_type().to_lowercase().cmp(&b.disk_type().to_lowercase()),
183                (Some(_), None) => Ordering::Greater,
184                (None, Some(_)) => Ordering::Less,
185                (None, None) => Ordering::Equal,
186            },
187            Self::Used =>  |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) {
188                (Some(a), Some(b)) => a.used().cmp(&b.used()),
189                (Some(_), None) => Ordering::Greater,
190                (None, Some(_)) => Ordering::Less,
191                (None, None) => Ordering::Equal,
192            },
193            Self::Use | Self::UsePercent =>  |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) {
194                // the 'use' column shows the percentage of used blocks, so it makes sense
195                // to sort by use_share for it
196                // SAFETY: use_share() doesn't return NaN
197                (Some(a), Some(b)) => a.use_share().partial_cmp(&b.use_share()).unwrap(),
198                (Some(_), None) => Ordering::Greater,
199                (None, Some(_)) => Ordering::Less,
200                (None, None) => Ordering::Equal,
201            },
202            Self::Free =>  |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) {
203                (Some(a), Some(b)) => a.available().cmp(&b.available()),
204                (Some(_), None) => Ordering::Greater,
205                (None, Some(_)) => Ordering::Less,
206                (None, None) => Ordering::Equal,
207            },
208            Self::FreePercent =>  |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) {
209                (Some(a), Some(b)) => b.use_share().partial_cmp(&a.use_share()).unwrap(),
210                (Some(_), None) => Ordering::Greater,
211                (None, Some(_)) => Ordering::Less,
212                (None, None) => Ordering::Equal,
213            },
214            Self::Size =>  |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) {
215                (Some(a), Some(b)) => a.size().cmp(&b.size()),
216                (Some(_), None) => Ordering::Greater,
217                (None, Some(_)) => Ordering::Less,
218                (None, None) => Ordering::Equal,
219            },
220            Self::InodesUsed =>  |a: &Mount, b: &Mount| match (&a.inodes(), &b.inodes()) {
221                (Some(a), Some(b)) => a.used().cmp(&b.used()),
222                (Some(_), None) => Ordering::Greater,
223                (None, Some(_)) => Ordering::Less,
224                (None, None) => Ordering::Equal,
225            },
226            Self::InodesUsePercent | Self::InodesUse  =>  |a: &Mount, b: &Mount| match (&a.inodes(), &b.inodes()) {
227                // SAFETY: use_share() doesn't return NaN
228                (Some(a), Some(b)) => a.use_share().partial_cmp(&b.use_share()).unwrap(),
229                (Some(_), None) => Ordering::Greater,
230                (None, Some(_)) => Ordering::Less,
231                (None, None) => Ordering::Equal,
232            },
233            Self::InodesFree =>  |a: &Mount, b: &Mount| match (&a.inodes(), &b.inodes()) {
234                (Some(a), Some(b)) => a.favail.cmp(&b.favail),
235                (Some(_), None) => Ordering::Greater,
236                (None, Some(_)) => Ordering::Less,
237                (None, None) => Ordering::Equal,
238            },
239            Self::InodesCount =>  |a: &Mount, b: &Mount| match (&a.inodes(), &b.inodes()) {
240                (Some(a), Some(b)) => a.files.cmp(&b.files),
241                (Some(_), None) => Ordering::Greater,
242                (None, Some(_)) => Ordering::Less,
243                (None, None) => Ordering::Equal,
244            },
245            Self::MountPoint =>  |a: &Mount, b: &Mount| a.info.mount_point.cmp(&b.info.mount_point),
246            Self::Uuid => |a: &Mount, b: &Mount| match (&a.uuid, &b.uuid) {
247                (Some(a), Some(b)) => a.cmp(b),
248                (Some(_), None) => Ordering::Less,
249                (None, Some(_)) => Ordering::Greater,
250                (None, None) => Ordering::Equal,
251            },
252            Self::PartUuid => |a: &Mount, b: &Mount| match (&a.part_uuid, &b.part_uuid) {
253                (Some(a), Some(b)) => a.cmp(b),
254                (Some(_), None) => Ordering::Less,
255                (None, Some(_)) => Ordering::Greater,
256                (None, None) => Ordering::Equal,
257            },
258        }
259    }
260    pub fn default_sort_order(self) -> Order {
261        match self {
262            Self::Id => Order::Asc,
263            Self::Dev => Order::Asc,
264            Self::Filesystem => Order::Asc,
265            Self::Label => Order::Asc,
266            Self::Type => Order::Asc,
267            Self::Remote => Order::Desc,
268            Self::Disk => Order::Asc,
269            Self::Used => Order::Asc,
270            Self::Use => Order::Desc,
271            Self::UsePercent => Order::Asc,
272            Self::Free => Order::Asc,
273            Self::FreePercent => Order::Desc,
274            Self::Size => Order::Desc,
275            Self::InodesUsed => Order::Asc,
276            Self::InodesUse => Order::Asc,
277            Self::InodesUsePercent => Order::Asc,
278            Self::InodesFree => Order::Asc,
279            Self::InodesCount => Order::Asc,
280            Self::MountPoint => Order::Asc,
281            Self::Uuid => Order::Asc,
282            Self::PartUuid => Order::Asc,
283        }
284    }
285    pub fn default_sort_col() -> Self {
286        Self::Size
287    }
288}
289
290
291#[derive(Debug)]
292pub struct ParseColError {
293    /// the string which couldn't be parsed
294    pub raw: String,
295}
296impl ParseColError {
297    pub fn new<S: Into<String>>(s: S) -> Self {
298        Self { raw: s.into() }
299    }
300}
301impl fmt::Display for ParseColError {
302    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303        write!(
304            f,
305            "{:?} can't be parsed as a column; use 'dysk --list-cols' to see all column names",
306            self.raw,
307        )
308    }
309}
310impl std::error::Error for ParseColError {}
311