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 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", 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", 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", DEFAULT_COLS,
341 );
342 check("default", DEFAULT_COLS);
343 check(
344 "-default", vec![],
346 );
347 check(
348 "default-dev", 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", 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}