dysk_cli/
cols.rs

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