1use {
2 crate::col::*,
3 std::{
4 str::FromStr,
5 },
6};
7
8#[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 pub fn add(&mut self, added: Col) {
38 self.remove(added);
39 self.0.push(added);
40 }
41 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 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 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", 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", 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", DEFAULT_COLS,
237 );
238 check(
239 "default",
240 DEFAULT_COLS,
241 );
242 check(
243 "-default", vec![],
245 );
246 check(
247 "default-dev", 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", 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}