1use {
2 crate::col::*,
3 std::str::FromStr,
4};
5
6#[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 pub fn add(
42 &mut self,
43 added: Col,
44 ) {
45 self.remove(added);
46 self.0.push(added);
47 }
48 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 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 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", 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", 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", DEFAULT_COLS,
331 );
332 check("default", DEFAULT_COLS);
333 check(
334 "-default", vec![],
336 );
337 check(
338 "default-dev", 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", 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}