Skip to main content

dysk_cli/
cols.rs

1use {
2    crate::col::*,
3    std::str::FromStr,
4};
5
6/// Sequence of columns, ordered
7#[derive(Debug, Clone, PartialEq)]
8pub struct Cols(pub Vec<Col>);
9
10impl Default for Cols {
11    fn default() -> Self {
12        Self(DEFAULT_COLS.to_vec())
13    }
14}
15
16impl Cols {
17    #[cfg(test)]
18    pub fn new<V: Into<Vec<Col>>>(v: V) -> Self {
19        Self(v.into())
20    }
21    pub fn empty() -> Self {
22        Self(Vec::new())
23    }
24    pub fn is_empty(&self) -> bool {
25        self.0.is_empty()
26    }
27    pub fn contains(
28        &self,
29        tbl: Col,
30    ) -> bool {
31        self.0.contains(&tbl)
32    }
33    pub fn remove(
34        &mut self,
35        removed: Col,
36    ) {
37        self.0.retain(|&f| f != removed);
38    }
39    /// Add a col, preventing duplicates
40    /// (may be used when the col is present to reorder)
41    pub fn add(
42        &mut self,
43        added: Col,
44    ) {
45        self.remove(added);
46        self.0.push(added);
47    }
48    /// Add the columns of the set, except when they're
49    /// already present
50    ///
51    /// This makes it possible to add a set while keeping
52    /// the order of the previous columns, for example
53    /// `dysk -c disk+`
54    pub fn add_set(
55        &mut self,
56        col_set: &[Col],
57    ) {
58        if self.0 == ALL_COLS {
59            for &col in col_set {
60                self.add(col);
61            }
62        } else {
63            for &col in col_set {
64                if !self.contains(col) {
65                    self.add(col);
66                }
67            }
68        }
69    }
70    pub fn remove_set(
71        &mut self,
72        col_set: &[Col],
73    ) {
74        for &col in col_set {
75            self.remove(col);
76        }
77    }
78    pub fn cols(&self) -> &[Col] {
79        &self.0
80    }
81}
82
83impl FromStr for Cols {
84    type Err = ParseColError;
85    fn from_str(value: &str) -> Result<Self, ParseColError> {
86        let value = value.trim();
87        let mut tokens: Vec<String> = Vec::new();
88        let mut must_create = true;
89        for c in value.chars() {
90            if c.is_alphabetic() || c == '_' {
91                if must_create {
92                    tokens.push(c.into());
93                    must_create = false;
94                } else {
95                    let len = tokens.len();
96                    tokens[len - 1].push(c);
97                }
98            } else {
99                tokens.push(c.into());
100                must_create = true;
101            }
102        }
103        let mut cols = if let Some(first_token) = tokens.first() {
104            if first_token == "+" || first_token == "-" {
105                // if it starts with an addition or removal, the
106                // default set is implied
107                Cols::default()
108            } else {
109                Cols::empty()
110            }
111        } else {
112            return Ok(Self::default());
113        };
114        let mut negative = false;
115        for token in &tokens {
116            match token.as_ref() {
117                "-" => {
118                    negative = true;
119                }
120                "+" | "," | " " => {}
121                "all" => {
122                    if negative {
123                        cols = Cols::empty();
124                        negative = false;
125                    } else {
126                        // if we add all to something, it means the already
127                        // present one are meant to be first
128                        for &col in ALL_COLS {
129                            if !cols.contains(col) {
130                                cols.add(col);
131                            }
132                        }
133                    }
134                }
135                "default" => {
136                    if negative {
137                        cols.remove_set(DEFAULT_COLS);
138                        negative = false;
139                    } else {
140                        cols.add_set(DEFAULT_COLS);
141                    }
142                }
143                _ => {
144                    let col: Col = token.parse()?;
145                    if negative {
146                        cols.remove(col);
147                        negative = false;
148                    } else {
149                        cols.add(col);
150                    }
151                }
152            }
153        }
154        match tokens.last().map(|s| s.as_ref()) {
155            Some("-") => {
156                cols.remove_set(DEFAULT_COLS);
157            }
158            Some("+") => {
159                cols.add_set(DEFAULT_COLS);
160            }
161            _ => {}
162        }
163        Ok(cols)
164    }
165}
166
167#[cfg(test)]
168mod cols_parsing {
169    use super::{
170        Col::*,
171        *,
172    };
173
174    fn check<V: Into<Vec<Col>>>(
175        s: &str,
176        v: V,
177    ) {
178        println!("cols definition: {s:?}");
179        let from_str: Cols = s.parse().unwrap();
180        let from_vec: Cols = Cols::new(v);
181        assert_eq!(from_str, from_vec);
182    }
183
184    #[test]
185    fn bad_cols() {
186        assert_eq!(
187            "nothing".parse::<Cols>().unwrap_err().to_string(),
188            r#""nothing" can't be parsed as a column; use 'dysk --list-cols' to see all column names"#,
189        );
190    }
191
192    #[test]
193    fn explicit_cols() {
194        check("dev", vec![Dev]);
195        check("dev,free,used", vec![Dev, Free, Used]);
196        check("dev+free + used", vec![Dev, Free, Used]);
197        check("  dev   free used ", vec![Dev, Free, Used]);
198        check("all", ALL_COLS);
199    }
200
201    #[test]
202    fn algebraic_cols() {
203        check(
204            "all - dev -inodes + label",
205            vec![
206                Id,
207                Filesystem,
208                Type,
209                Remote,
210                Disk,
211                Used,
212                Use,
213                UsePercent,
214                Free,
215                FreePercent,
216                Size,
217                InodesUsed,
218                InodesUsePercent,
219                InodesFree,
220                InodesCount,
221                MountPoint,
222                Uuid,
223                PartUuid,
224                MountOptions,
225                CompressLevel,
226                Label,
227            ],
228        );
229        check("dev + dev +disk - use + size", vec![Dev, Disk, Size]);
230        check(
231            "all-default+use",
232            vec![
233                Id,
234                Dev,
235                Label,
236                Remote,
237                UsePercent,
238                FreePercent,
239                InodesUsed,
240                InodesUse,
241                InodesUsePercent,
242                InodesFree,
243                InodesCount,
244                Uuid,
245                PartUuid,
246                MountOptions,
247                CompressLevel,
248                Use,
249            ],
250        );
251        check(
252            "all+default", // special: all but default at the end
253            vec![
254                Id,
255                Dev,
256                Label,
257                Remote,
258                UsePercent,
259                FreePercent,
260                InodesUsed,
261                InodesUse,
262                InodesUsePercent,
263                InodesFree,
264                InodesCount,
265                Uuid,
266                PartUuid,
267                MountOptions,
268                CompressLevel,
269                Filesystem,
270                Type,
271                Disk,
272                Used,
273                Use,
274                Free,
275                Size,
276                MountPoint,
277            ],
278        );
279        check(
280            "fs dev all", // we want all column but fs and dev at the start
281            vec![
282                Filesystem,
283                Dev,
284                Id,
285                Label,
286                Type,
287                Remote,
288                Disk,
289                Used,
290                Use,
291                UsePercent,
292                Free,
293                FreePercent,
294                Size,
295                InodesUsed,
296                InodesUse,
297                InodesUsePercent,
298                InodesFree,
299                InodesCount,
300                MountPoint,
301                Uuid,
302                PartUuid,
303                MountOptions,
304                CompressLevel,
305            ],
306        );
307        check(
308            "fs dev all -id-disk",
309            vec![
310                Filesystem,
311                Dev,
312                Label,
313                Type,
314                Remote,
315                Used,
316                Use,
317                UsePercent,
318                Free,
319                FreePercent,
320                Size,
321                InodesUsed,
322                InodesUse,
323                InodesUsePercent,
324                InodesFree,
325                InodesCount,
326                MountPoint,
327                Uuid,
328                PartUuid,
329                MountOptions,
330                CompressLevel,
331            ],
332        );
333    }
334
335    #[test]
336    fn cols_from_default() {
337        check("", DEFAULT_COLS);
338        check(
339            "-dev", // no impact as dev isn't in defaults
340            DEFAULT_COLS,
341        );
342        check("default", DEFAULT_COLS);
343        check(
344            "-default", // not really useful
345            vec![],
346        );
347        check(
348            "default-dev", // no impact as dev isn't in defaults
349            DEFAULT_COLS,
350        );
351        check(
352            "+dev",
353            vec![
354                Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint, Dev,
355            ],
356        );
357        check(
358            "dev+",
359            vec![
360                Dev, Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint,
361            ],
362        );
363        check(
364            "all-",
365            vec![
366                Id,
367                Dev,
368                Label,
369                Remote,
370                UsePercent,
371                FreePercent,
372                InodesUsed,
373                InodesUse,
374                InodesUsePercent,
375                InodesFree,
376                InodesCount,
377                Uuid,
378                PartUuid,
379                MountOptions,
380                CompressLevel,
381            ],
382        );
383        check(
384            "-size+inodes_free+",
385            vec![
386                Filesystem, Type, Disk, Used, Use, Free, MountPoint, InodesFree, Size,
387            ],
388        );
389        check(
390            "+dev-size+inodes_use",
391            vec![
392                Filesystem, Type, Disk, Used, Use, Free, MountPoint, Dev, InodesUse,
393            ],
394        );
395        check(
396            "-use-type",
397            vec![Filesystem, Disk, Used, Free, Size, MountPoint],
398        );
399        check(
400            "default+dev",
401            vec![
402                Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint, Dev,
403            ],
404        );
405        check(
406            "default,size+use", // just reordering
407            vec![Filesystem, Type, Disk, Used, Free, MountPoint, Size, Use],
408        );
409        check(
410            "dev default",
411            vec![
412                Dev, Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint,
413            ],
414        );
415        check(
416            "size dev default -disk",
417            vec![Size, Dev, Filesystem, Type, Used, Use, Free, MountPoint],
418        );
419        check(
420            "default-fs+inodes",
421            vec![Type, Disk, Used, Use, Free, Size, MountPoint, InodesUse],
422        );
423        check(
424            "+inodes_used+inodes_free",
425            vec![
426                Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint, InodesUsed, InodesFree,
427            ],
428        );
429    }
430}