1#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
26pub enum Mode {
27 Short,
29 Standard,
31 Long,
33 Full,
35}
36
37struct FieldDef {
45 key: &'static str,
47 min_mode: Mode,
49}
50
51const FIELDS: &[FieldDef] = &[
59 FieldDef {
61 key: "os",
62 min_mode: Mode::Short,
63 },
64 FieldDef {
65 key: "kernel",
66 min_mode: Mode::Short,
67 },
68 FieldDef {
69 key: "host",
70 min_mode: Mode::Short,
71 },
72 FieldDef {
73 key: "domain",
74 min_mode: Mode::Long,
75 },
76 FieldDef {
77 key: "domain-search",
78 min_mode: Mode::Full,
79 },
80 FieldDef {
81 key: "chassis",
82 min_mode: Mode::Long,
83 },
84 FieldDef {
85 key: "init",
86 min_mode: Mode::Long,
87 },
88 FieldDef {
89 key: "locale",
90 min_mode: Mode::Long,
91 },
92 FieldDef {
93 key: "arch",
94 min_mode: Mode::Long,
95 },
96 FieldDef {
98 key: "cpu",
99 min_mode: Mode::Short,
100 },
101 FieldDef {
102 key: "cpu-freq",
103 min_mode: Mode::Long,
104 },
105 FieldDef {
106 key: "cpu-cache",
107 min_mode: Mode::Standard,
108 },
109 FieldDef {
110 key: "cpu-usage",
111 min_mode: Mode::Standard,
112 },
113 FieldDef {
115 key: "gpu",
116 min_mode: Mode::Short,
117 },
118 FieldDef {
123 key: "vulkan",
124 min_mode: Mode::Full,
125 },
126 FieldDef {
127 key: "opengl",
128 min_mode: Mode::Full,
129 },
130 FieldDef {
131 key: "opencl",
132 min_mode: Mode::Full,
133 },
134 FieldDef {
135 key: "motherboard",
136 min_mode: Mode::Standard,
137 },
138 FieldDef {
139 key: "bios",
140 min_mode: Mode::Long,
141 },
142 FieldDef {
143 key: "bootmgr",
144 min_mode: Mode::Long,
145 },
146 FieldDef {
147 key: "tpm",
148 min_mode: Mode::Long,
149 },
150 FieldDef {
151 key: "display",
152 min_mode: Mode::Standard,
153 },
154 FieldDef {
155 key: "brightness",
156 min_mode: Mode::Long,
157 },
158 FieldDef {
159 key: "audio",
160 min_mode: Mode::Standard,
161 },
162 FieldDef {
163 key: "camera",
164 min_mode: Mode::Standard,
165 },
166 FieldDef {
167 key: "gamepad",
168 min_mode: Mode::Full,
169 },
170 FieldDef {
171 key: "keyboard",
172 min_mode: Mode::Long,
173 },
174 FieldDef {
175 key: "mouse",
176 min_mode: Mode::Long,
177 },
178 FieldDef {
180 key: "memory",
181 min_mode: Mode::Short,
182 },
183 FieldDef {
184 key: "phys-mem",
185 min_mode: Mode::Standard,
186 },
187 FieldDef {
188 key: "swap",
189 min_mode: Mode::Standard,
190 },
191 FieldDef {
192 key: "uptime",
193 min_mode: Mode::Standard,
194 },
195 FieldDef {
196 key: "procs",
197 min_mode: Mode::Long,
198 },
199 FieldDef {
200 key: "load",
201 min_mode: Mode::Standard,
202 },
203 FieldDef {
204 key: "disk",
205 min_mode: Mode::Short,
206 },
207 FieldDef {
208 key: "phys-disk",
209 min_mode: Mode::Standard,
210 },
211 FieldDef {
212 key: "disk-io",
213 min_mode: Mode::Long,
214 },
215 FieldDef {
216 key: "btrfs",
217 min_mode: Mode::Long,
218 },
219 FieldDef {
220 key: "zpool",
221 min_mode: Mode::Long,
222 },
223 FieldDef {
224 key: "temp",
225 min_mode: Mode::Long,
226 },
227 FieldDef {
229 key: "net",
230 min_mode: Mode::Short,
231 },
232 FieldDef {
233 key: "net-io",
234 min_mode: Mode::Long,
235 },
236 FieldDef {
237 key: "public-ip",
238 min_mode: Mode::Long,
239 },
240 FieldDef {
241 key: "wifi",
242 min_mode: Mode::Long,
243 },
244 FieldDef {
245 key: "dns",
246 min_mode: Mode::Long,
247 },
248 FieldDef {
249 key: "bluetooth",
250 min_mode: Mode::Long,
251 },
252 FieldDef {
253 key: "battery",
254 min_mode: Mode::Long,
255 },
256 FieldDef {
257 key: "power-adapter",
258 min_mode: Mode::Long,
259 },
260 FieldDef {
262 key: "shell",
263 min_mode: Mode::Long,
264 },
265 FieldDef {
266 key: "editor",
267 min_mode: Mode::Long,
268 },
269 FieldDef {
270 key: "terminal",
271 min_mode: Mode::Long,
272 },
273 FieldDef {
274 key: "terminal-font",
275 min_mode: Mode::Long,
276 },
277 FieldDef {
278 key: "terminal-size",
279 min_mode: Mode::Long,
280 },
281 FieldDef {
282 key: "desktop",
283 min_mode: Mode::Long,
284 },
285 FieldDef {
286 key: "wm",
287 min_mode: Mode::Long,
288 },
289 FieldDef {
290 key: "login-manager",
291 min_mode: Mode::Long,
292 },
293 FieldDef {
295 key: "player",
296 min_mode: Mode::Long,
297 },
298 FieldDef {
299 key: "media",
300 min_mode: Mode::Long,
301 },
302 FieldDef {
304 key: "wm-theme",
305 min_mode: Mode::Full,
306 },
307 FieldDef {
308 key: "wallpaper",
309 min_mode: Mode::Full,
310 },
311 FieldDef {
312 key: "terminal-theme",
313 min_mode: Mode::Full,
314 },
315 FieldDef {
316 key: "theme",
317 min_mode: Mode::Full,
318 },
319 FieldDef {
320 key: "icons",
321 min_mode: Mode::Full,
322 },
323 FieldDef {
324 key: "cursor",
325 min_mode: Mode::Full,
326 },
327 FieldDef {
328 key: "font",
329 min_mode: Mode::Long,
330 },
331 FieldDef {
332 key: "users",
333 min_mode: Mode::Long,
334 },
335 FieldDef {
336 key: "packages",
337 min_mode: Mode::Long,
338 },
339 FieldDef {
340 key: "weather",
341 min_mode: Mode::Full,
342 },
343];
344
345pub fn fields_for(mode: Mode) -> Vec<String> {
351 FIELDS
352 .iter()
353 .filter(|f| f.min_mode <= mode)
354 .map(|f| f.key.to_string())
355 .collect()
356}
357
358pub fn all_keys() -> Vec<&'static str> {
360 FIELDS.iter().map(|f| f.key).collect()
361}
362
363pub fn config_fields_block() -> String {
370 const PER_LINE: usize = 6;
371 let mut out = String::new();
372 out.push_str("# List of fields to display (leave empty or omit to show all)\n");
373 out.push_str(
374 "# Note: \"phys-mem\" requires running as root (sudo) on Linux to read DMI memory tables.\n",
375 );
376 out.push_str(
377 "# Note: \"weather\" requires network access and is shown in full mode only by default.\n",
378 );
379 out.push_str(
380 "# Note: \"domain-search\" queries resolvectl and is shown in full mode only by default.\n",
381 );
382 out.push_str("# fields = [\n");
383 for chunk in FIELDS.chunks(PER_LINE) {
384 let quoted: Vec<String> = chunk.iter().map(|f| format!("\"{}\"", f.key)).collect();
385 out.push_str("# ");
386 out.push_str("ed.join(", "));
387 out.push_str(",\n");
388 }
389 if let Some(pos) = out.rfind(",\n") {
391 out.replace_range(pos..pos + 2, "\n");
392 }
393 out.push_str("# ]");
394 out
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400 use std::collections::HashSet;
401
402 #[test]
403 fn test_no_duplicate_keys() {
404 let mut seen = HashSet::new();
405 for f in FIELDS {
406 assert!(seen.insert(f.key), "duplicate field key: {}", f.key);
407 }
408 }
409
410 #[test]
411 fn test_strata_strictly_nested() {
412 let short: HashSet<_> = fields_for(Mode::Short).into_iter().collect();
413 let standard: HashSet<_> = fields_for(Mode::Standard).into_iter().collect();
414 let long: HashSet<_> = fields_for(Mode::Long).into_iter().collect();
415 let full: HashSet<_> = fields_for(Mode::Full).into_iter().collect();
416
417 assert!(
418 short.is_subset(&standard),
419 "short must be a subset of standard"
420 );
421 assert!(
422 standard.is_subset(&long),
423 "standard must be a subset of long"
424 );
425 assert!(long.is_subset(&full), "long must be a subset of full");
426 }
427
428 #[test]
429 fn test_strata_counts() {
430 assert_eq!(fields_for(Mode::Short).len(), 8, "short field count");
433 assert_eq!(fields_for(Mode::Standard).len(), 19, "standard field count");
434 assert_eq!(fields_for(Mode::Long).len(), 56, "long field count");
435 assert_eq!(fields_for(Mode::Full).len(), 68, "full field count");
436 }
437
438 #[test]
439 fn test_short_set_exact() {
440 let short: HashSet<_> = fields_for(Mode::Short).into_iter().collect();
441 let expected: HashSet<String> = [
442 "os", "kernel", "host", "cpu", "gpu", "memory", "disk", "net",
443 ]
444 .iter()
445 .map(|s| s.to_string())
446 .collect();
447 assert_eq!(short, expected);
448 }
449
450 #[test]
451 fn test_mode_membership_boundaries() {
452 let standard: HashSet<_> = fields_for(Mode::Standard).into_iter().collect();
454 assert!(standard.contains("phys-mem"));
455 assert!(standard.contains("cpu-cache"));
456 assert!(!standard.contains("bios"), "bios is long+, not standard");
457
458 let long: HashSet<_> = fields_for(Mode::Long).into_iter().collect();
459 assert!(long.contains("bios"));
460 assert!(long.contains("terminal-size"));
461 assert!(long.contains("wm"));
462 assert!(long.contains("login-manager"));
463 assert!(long.contains("brightness"));
464 assert!(long.contains("power-adapter"));
465 assert!(long.contains("keyboard"));
466 assert!(long.contains("mouse"));
467 assert!(long.contains("tpm"));
468 assert!(long.contains("player"));
469 assert!(long.contains("media"));
470 assert!(!standard.contains("keyboard"), "keyboard is long+");
472 assert!(!standard.contains("mouse"), "mouse is long+");
473 assert!(!standard.contains("tpm"), "tpm is long+");
474 assert!(!standard.contains("player"), "player is long+");
475 assert!(!standard.contains("media"), "media is long+");
476 assert!(
478 !standard.contains("brightness"),
479 "brightness is long+, not standard"
480 );
481 assert!(!long.contains("weather"), "weather is full-only");
482 assert!(!long.contains("gamepad"), "gamepad is full-only");
483 assert!(!long.contains("wm-theme"), "wm-theme is full-only");
484 assert!(!long.contains("wallpaper"), "wallpaper is full-only");
485 assert!(
486 !long.contains("terminal-theme"),
487 "terminal-theme is full-only"
488 );
489
490 let full: HashSet<_> = fields_for(Mode::Full).into_iter().collect();
491 assert!(full.contains("weather"));
492 assert!(full.contains("domain-search"));
493 assert!(full.contains("wm-theme"));
494 assert!(full.contains("wallpaper"));
495 assert!(full.contains("terminal-theme"));
496 }
497
498 #[test]
499 fn test_config_block_shape() {
500 let block = config_fields_block();
501 assert!(block.contains("# fields = ["));
502 assert!(block.trim_end().ends_with("# ]"));
503 for key in all_keys() {
505 assert!(
506 block.contains(&format!("\"{}\"", key)),
507 "config block missing key: {}",
508 key
509 );
510 }
511 for line in block.lines() {
513 assert!(
514 line.starts_with('#'),
515 "uncommented line in config block: {line:?}"
516 );
517 }
518 assert!(
520 !block.contains(",\n# ]"),
521 "trailing comma before closing bracket"
522 );
523 }
524}