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                Label,
225            ],
226        );
227        check("dev + dev +disk - use + size", vec![Dev, Disk, Size]);
228        check(
229            "all-default+use",
230            vec![
231                Id,
232                Dev,
233                Label,
234                Remote,
235                UsePercent,
236                FreePercent,
237                InodesUsed,
238                InodesUse,
239                InodesUsePercent,
240                InodesFree,
241                InodesCount,
242                Uuid,
243                PartUuid,
244                Use,
245            ],
246        );
247        check(
248            "all+default", // special: all but default at the end
249            vec![
250                Id,
251                Dev,
252                Label,
253                Remote,
254                UsePercent,
255                FreePercent,
256                InodesUsed,
257                InodesUse,
258                InodesUsePercent,
259                InodesFree,
260                InodesCount,
261                Uuid,
262                PartUuid,
263                Filesystem,
264                Type,
265                Disk,
266                Used,
267                Use,
268                Free,
269                Size,
270                MountPoint,
271            ],
272        );
273        check(
274            "fs dev all", // we want all column but fs and dev at the start
275            vec![
276                Filesystem,
277                Dev,
278                Id,
279                Label,
280                Type,
281                Remote,
282                Disk,
283                Used,
284                Use,
285                UsePercent,
286                Free,
287                FreePercent,
288                Size,
289                InodesUsed,
290                InodesUse,
291                InodesUsePercent,
292                InodesFree,
293                InodesCount,
294                MountPoint,
295                Uuid,
296                PartUuid,
297            ],
298        );
299        check(
300            "fs dev all -id-disk",
301            vec![
302                Filesystem,
303                Dev,
304                Label,
305                Type,
306                Remote,
307                Used,
308                Use,
309                UsePercent,
310                Free,
311                FreePercent,
312                Size,
313                InodesUsed,
314                InodesUse,
315                InodesUsePercent,
316                InodesFree,
317                InodesCount,
318                MountPoint,
319                Uuid,
320                PartUuid,
321            ],
322        );
323    }
324
325    #[test]
326    fn cols_from_default() {
327        check("", DEFAULT_COLS);
328        check(
329            "-dev", // no impact as dev isn't in defaults
330            DEFAULT_COLS,
331        );
332        check("default", DEFAULT_COLS);
333        check(
334            "-default", // not really useful
335            vec![],
336        );
337        check(
338            "default-dev", // no impact as dev isn't in defaults
339            DEFAULT_COLS,
340        );
341        check(
342            "+dev",
343            vec![
344                Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint, Dev,
345            ],
346        );
347        check(
348            "dev+",
349            vec![
350                Dev, Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint,
351            ],
352        );
353        check(
354            "all-",
355            vec![
356                Id,
357                Dev,
358                Label,
359                Remote,
360                UsePercent,
361                FreePercent,
362                InodesUsed,
363                InodesUse,
364                InodesUsePercent,
365                InodesFree,
366                InodesCount,
367                Uuid,
368                PartUuid,
369            ],
370        );
371        check(
372            "-size+inodes_free+",
373            vec![
374                Filesystem, Type, Disk, Used, Use, Free, MountPoint, InodesFree, Size,
375            ],
376        );
377        check(
378            "+dev-size+inodes_use",
379            vec![
380                Filesystem, Type, Disk, Used, Use, Free, MountPoint, Dev, InodesUse,
381            ],
382        );
383        check(
384            "-use-type",
385            vec![Filesystem, Disk, Used, Free, Size, MountPoint],
386        );
387        check(
388            "default+dev",
389            vec![
390                Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint, Dev,
391            ],
392        );
393        check(
394            "default,size+use", // just reordering
395            vec![Filesystem, Type, Disk, Used, Free, MountPoint, Size, Use],
396        );
397        check(
398            "dev default",
399            vec![
400                Dev, Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint,
401            ],
402        );
403        check(
404            "size dev default -disk",
405            vec![Size, Dev, Filesystem, Type, Used, Use, Free, MountPoint],
406        );
407        check(
408            "default-fs+inodes",
409            vec![Type, Disk, Used, Use, Free, Size, MountPoint, InodesUse],
410        );
411        check(
412            "+inodes_used+inodes_free",
413            vec![
414                Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint, InodesUsed, InodesFree,
415            ],
416        );
417    }
418}