leenfetch_core/modules/
helper.rs

1use clap::{ArgAction, Parser, ValueEnum};
2use std::collections::{HashMap, HashSet};
3
4/// Captures configuration overrides controlled by CLI switches.
5#[derive(Default, Debug)]
6pub struct CliOverrides {
7    pub flags: HashMap<String, String>,
8    pub only_modules: Option<Vec<String>>,
9    pub hide_modules: HashSet<String>,
10    pub config_path: Option<String>,
11    pub use_defaults: bool,
12    pub output_format: OutputFormat,
13    pub ssh_hosts: Vec<String>,
14}
15
16impl CliOverrides {
17    fn set_bool(&mut self, key: &str, value: bool) {
18        self.flags.insert(
19            key.to_string(),
20            if value { "true" } else { "false" }.to_string(),
21        );
22    }
23
24    fn set_string(&mut self, key: &str, value: String) {
25        self.flags.insert(key.to_string(), value);
26    }
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
30pub enum OutputFormat {
31    Pretty,
32    Json,
33}
34
35impl Default for OutputFormat {
36    fn default() -> Self {
37        OutputFormat::Pretty
38    }
39}
40
41#[derive(Parser, Debug)]
42#[command(
43    name = "leenfetch",
44    about = "Minimal, stylish system info for your terminal",
45    version,
46    disable_help_flag = true
47)]
48pub struct Args {
49    /// Show this help message and exit
50    #[arg(short = 'h', long = "help", action = ArgAction::SetTrue)]
51    pub help: bool,
52
53    /// Output format: pretty (default) or json
54    #[arg(long, value_enum, default_value_t = OutputFormat::Pretty)]
55    pub format: OutputFormat,
56
57    /// Create the default config file in ~/.config/leenfetch/
58    #[arg(short = 'i', long, action = ArgAction::SetTrue)]
59    pub init: bool,
60
61    /// Reinitialize the config file to defaults
62    #[arg(short = 'r', long, action = ArgAction::SetTrue)]
63    pub reinit: bool,
64
65    /// Show all available config options and values
66    #[arg(short = 'l', long = "list-options", action = ArgAction::SetTrue)]
67    pub list_options: bool,
68
69    /// Load configuration from a custom file
70    #[arg(long = "config")]
71    pub config_path: Option<String>,
72
73    /// Ignore config files and use built-in defaults
74    #[arg(long = "no-config", action = ArgAction::SetTrue)]
75    pub no_config: bool,
76
77    #[arg(long = "ascii_distro")]
78    pub ascii_distro: Option<String>,
79    #[arg(long = "ascii_colors")]
80    pub ascii_colors: Option<String>,
81    #[arg(long = "custom_ascii_path")]
82    pub custom_ascii_path: Option<String>,
83    #[arg(long = "battery-display")]
84    pub battery_display: Option<String>,
85    #[arg(long = "disk-display")]
86    pub disk_display: Option<String>,
87    #[arg(long = "disk-subtitle")]
88    pub disk_subtitle: Option<String>,
89    #[arg(long = "memory-unit")]
90    pub memory_unit: Option<String>,
91    #[arg(long = "packages", alias = "package-managers")]
92    pub package_managers: Option<String>,
93    #[arg(long = "uptime")]
94    pub uptime_shorthand: Option<String>,
95    #[arg(long = "os-age")]
96    pub os_age_shorthand: Option<String>,
97    #[arg(long = "distro-display")]
98    pub distro_display: Option<String>,
99    #[arg(long = "color-blocks")]
100    pub color_blocks: Option<String>,
101    #[arg(long = "cpu-temp-unit")]
102    pub cpu_temp: Option<String>,
103    #[arg(long = "only")]
104    pub only_modules: Option<String>,
105    #[arg(long = "hide")]
106    pub hide_modules: Option<String>,
107
108    #[arg(long = "memory-percent", action = ArgAction::SetTrue)]
109    pub memory_percent_on: bool,
110    #[arg(long = "no-memory-percent", action = ArgAction::SetFalse)]
111    pub memory_percent_off: bool,
112    #[arg(long = "cpu-show-temp", action = ArgAction::SetTrue)]
113    pub cpu_show_temp_on: bool,
114    #[arg(long = "no-cpu-show-temp", action = ArgAction::SetFalse)]
115    pub cpu_show_temp_off: bool,
116    #[arg(long = "cpu-speed", action = ArgAction::SetTrue)]
117    pub cpu_speed_on: bool,
118    #[arg(long = "no-cpu-speed", action = ArgAction::SetFalse)]
119    pub cpu_speed_off: bool,
120    #[arg(long = "cpu-frequency", action = ArgAction::SetTrue)]
121    pub cpu_frequency_on: bool,
122    #[arg(long = "no-cpu-frequency", action = ArgAction::SetFalse)]
123    pub cpu_frequency_off: bool,
124    #[arg(long = "cpu-cores", action = ArgAction::SetTrue)]
125    pub cpu_cores_on: bool,
126    #[arg(long = "no-cpu-cores", action = ArgAction::SetFalse)]
127    pub cpu_cores_off: bool,
128    #[arg(long = "cpu-brand", action = ArgAction::SetTrue)]
129    pub cpu_brand_on: bool,
130    #[arg(long = "no-cpu-brand", action = ArgAction::SetFalse)]
131    pub cpu_brand_off: bool,
132    #[arg(long = "shell-path", action = ArgAction::SetTrue)]
133    pub shell_path_on: bool,
134    #[arg(long = "no-shell-path", action = ArgAction::SetFalse)]
135    pub shell_path_off: bool,
136    #[arg(long = "shell-version", action = ArgAction::SetTrue)]
137    pub shell_version_on: bool,
138    #[arg(long = "no-shell-version", action = ArgAction::SetFalse)]
139    pub shell_version_off: bool,
140    #[arg(long = "refresh-rate", action = ArgAction::SetTrue)]
141    pub refresh_rate_on: bool,
142    #[arg(long = "no-refresh-rate", action = ArgAction::SetFalse)]
143    pub refresh_rate_off: bool,
144    #[arg(long = "de-version", action = ArgAction::SetTrue)]
145    pub de_version_on: bool,
146    #[arg(long = "no-de-version", action = ArgAction::SetFalse)]
147    pub de_version_off: bool,
148
149    /// Fetch info from remote hosts via SSH (e.g., user@host or host:port)
150    #[arg(long = "ssh", value_name = "HOST")]
151    pub ssh_hosts: Vec<String>,
152}
153
154impl Args {
155    pub fn into_overrides(self) -> CliOverrides {
156        let mut overrides = CliOverrides::default();
157        overrides.use_defaults = self.no_config;
158        overrides.config_path = self.config_path.clone();
159        overrides.output_format = self.format;
160
161        if let Some(val) = self.ascii_distro {
162            overrides.set_string("ascii_distro", val);
163        }
164        if let Some(val) = self.ascii_colors {
165            overrides.set_string("ascii_colors", val);
166        }
167        if let Some(val) = self.custom_ascii_path {
168            overrides.set_string("custom_ascii_path", val);
169        }
170        if let Some(val) = self.battery_display {
171            overrides.set_string("battery_display", val);
172        }
173        if let Some(val) = self.disk_display {
174            overrides.set_string("disk_display", val);
175        }
176        if let Some(val) = self.disk_subtitle {
177            overrides.set_string("disk_subtitle", val);
178        }
179        if let Some(val) = self.memory_unit {
180            overrides.set_string("memory_unit", val);
181        }
182        if let Some(val) = self.package_managers {
183            overrides.set_string("package_managers", val);
184        }
185        if let Some(val) = self.uptime_shorthand {
186            overrides.set_string("uptime_shorthand", val);
187        }
188        if let Some(val) = self.os_age_shorthand {
189            overrides.set_string("os_age_shorthand", val);
190        }
191        if let Some(val) = self.distro_display {
192            overrides.set_string("distro_display", val);
193        }
194        if let Some(val) = self.color_blocks {
195            overrides.set_string("color_blocks", val);
196        }
197        if let Some(val) = self.cpu_temp {
198            overrides.set_string("cpu_temp", val);
199        }
200
201        if let Some(only) = self.only_modules {
202            let modules = only
203                .split(',')
204                .map(|item| item.trim().to_string())
205                .filter(|item| !item.is_empty())
206                .collect::<Vec<_>>();
207            overrides.only_modules = if modules.is_empty() {
208                None
209            } else {
210                Some(modules)
211            };
212        }
213
214        if let Some(hide) = self.hide_modules {
215            for entry in hide.split(',') {
216                let trimmed = entry.trim();
217                if !trimmed.is_empty() {
218                    overrides.hide_modules.insert(trimmed.to_string());
219                }
220            }
221        }
222
223        apply_bool_override(
224            &mut overrides,
225            "memory_percent",
226            self.memory_percent_on,
227            self.memory_percent_off,
228        );
229        apply_bool_override(
230            &mut overrides,
231            "cpu_show_temp",
232            self.cpu_show_temp_on,
233            self.cpu_show_temp_off,
234        );
235        apply_bool_override(
236            &mut overrides,
237            "cpu_speed",
238            self.cpu_speed_on,
239            self.cpu_speed_off,
240        );
241        apply_bool_override(
242            &mut overrides,
243            "cpu_frequency",
244            self.cpu_frequency_on,
245            self.cpu_frequency_off,
246        );
247        apply_bool_override(
248            &mut overrides,
249            "cpu_cores",
250            self.cpu_cores_on,
251            self.cpu_cores_off,
252        );
253        apply_bool_override(
254            &mut overrides,
255            "cpu_brand",
256            self.cpu_brand_on,
257            self.cpu_brand_off,
258        );
259        apply_bool_override(
260            &mut overrides,
261            "shell_path",
262            self.shell_path_on,
263            self.shell_path_off,
264        );
265        apply_bool_override(
266            &mut overrides,
267            "shell_version",
268            self.shell_version_on,
269            self.shell_version_off,
270        );
271        apply_bool_override(
272            &mut overrides,
273            "refresh_rate",
274            self.refresh_rate_on,
275            self.refresh_rate_off,
276        );
277        apply_bool_override(
278            &mut overrides,
279            "de_version",
280            self.de_version_on,
281            self.de_version_off,
282        );
283
284        overrides.ssh_hosts = self.ssh_hosts.clone();
285
286        overrides
287    }
288}
289
290fn apply_bool_override(overrides: &mut CliOverrides, key: &str, enable: bool, disable: bool) {
291    if enable {
292        overrides.set_bool(key, true);
293    } else if disable {
294        overrides.set_bool(key, false);
295    }
296}
297
298pub fn print_custom_help() {
299    println!(
300        "{}",
301        r#"🧠 leenfetch — Minimal, Stylish System Info for Your Terminal
302
303USAGE:
304  leenfetch [OPTIONS]
305
306OPTIONS:
307  -V, --version            Print version information and exit
308  -h, --help               Show this help message and exit
309  -i, --init               Create the default config file in ~/.config/leenfetch/
310  -r, --reinit             Reinitialize the config file to defaults
311  -l, --list-options       Show all available config options and values
312      --config <path>      Load configuration from a custom file
313      --no-config          Ignore config files and use built-in defaults
314      --ssh <host>         Fetch info from remote hosts via SSH (repeatable)
315      --format <kind>      Output format: pretty (default) or json
316
317  --ascii_distro <s>       Override detected distro (e.g., ubuntu, arch, arch_small)
318  --ascii_colors <s>       Override color palette (e.g., 2,7,3 or "distro")
319  --custom_ascii_path <p>  Use ASCII art from the given file path
320
321  --battery-display <mode> Battery output style (off, bar, infobar, barinfo)
322  --disk-display <mode>    Disk output style (info, percentage, infobar, barinfo, bar)
323  --disk-subtitle <mode>   Disk subtitle (name, dir, none, mount)
324  --memory-unit <unit>     Force memory unit (kib, mib, gib)
325  --packages <mode>        Package summary verbosity (off, on, tiny)
326  --uptime <mode>          Uptime shorthand (full, tiny, seconds)
327  --os-age <mode>          OS age shorthand (full, tiny, seconds)
328  --distro-display <mode>  Distro detail level (name, name_version, ...)
329  --color-blocks <glyph>   Glyph used for color swatches
330  --cpu-temp-unit <unit>   CPU temperature unit (C, F, off)
331  --only <list>            Render only listed modules (comma-separated)
332  --hide <list>            Hide listed modules (comma-separated)
333
334  --memory-percent / --no-memory-percent
335  --cpu-show-temp   / --no-cpu-show-temp
336  --cpu-speed       / --no-cpu-speed
337  --cpu-frequency   / --no-cpu-frequency
338  --cpu-cores       / --no-cpu-cores
339  --cpu-brand       / --no-cpu-brand
340  --shell-path      / --no-shell-path
341  --shell-version   / --no-shell-version
342  --refresh-rate    / --no-refresh-rate
343  --de-version      / --no-de-version
344
345DESCRIPTION:
346  leenfetch is a modern, minimal, and the fastest system info tool,
347  written in Rust, designed for terminal enthusiasts.
348
349  It fetches and prints system information like:
350    • OS, Kernel, Uptime
351    • CPU, GPU, Memory, Disks
352    • Shell, WM, DE, Theme
353    • Resolution, Battery, Current Song
354
355  🛠️  Configuration:
356    • Linux:   ~/.config/leenfetch/config.jsonc
357    • Windows: %APPDATA%/leenfetch/config.jsonc
358    One JSONC file with inline comments covering flags and a Fastfetch-style modules array.
359    Edit it to control appearance, spacing (via "break" entries), and output order.
360
361EXAMPLES:
362  leenfetch                         🚀 Run normally with your config
363  leenfetch --init                  🔧 Create the default config file
364  leenfetch --ssh user@server       🌐 Fetch from a remote host over SSH
365  leenfetch --ssh host1 --ssh host2 🛰️ Fetch multiple hosts sequentially
366  leenfetch --ascii_distro arch     🎨 Use Arch logo manually
367  leenfetch --ascii_colors 2,7,3    🌈 Use custom colors
368  leenfetch --packages tiny         📦 Compact package summary for screenshots
369  leenfetch --only cpu,memory       🧩 Focus on specific modules temporarily
370  leenfetch --list-options          📜 View all available configuration keys
371
372TIPS:
373  • Adjust styles in the `flags` section (e.g., ascii_distro, disk_display, battery_display)
374  • Reorder entries in the `modules` array (use "break" for spacing)
375  • Tweak `logo.padding` to add margins around the ASCII art
376
377For more, see the README or run `leenfetch --list-options`.
378        "#
379    );
380}
381
382pub fn list_options() {
383    println!(
384        "{}",
385        r#"
386
387📄 LeenFetch Configuration Options Reference
388──────────────────────────────────────────────
389
390📁 LeenFetch stores everything in a single JSONC file:
391  • Linux:   ~/.config/leenfetch/config.jsonc
392  • Windows: %APPDATA%/leenfetch/config.jsonc
393
394🗂️  Sections inside config.jsonc:
395  • 🖼️ flags — Display and formatting options
396  • 🧱 modules — Output order and custom rows
397──────────────────────────────────────────────
398🖼️ flags — Display and Formatting Options
399──────────────────────────────────────────────
400  ascii_distro        = "auto" | <name>
401      Which ASCII art to use. "auto" detects your distro or specify a distro name (e.g., "arch").
402  
403  ascii_colors        = "distro" | <list>
404      Color palette for ASCII art. "distro" uses default, or provide a comma-separated list (e.g., "1,2,3,4").
405  
406  custom_ascii_path   = "" | <path>
407      Path to a custom ASCII art file. Empty for default.
408  
409  battery_display     = "off" | "bar" | "infobar" | "barinfo"
410      How to show battery info: none, bar only, info+bar, or bar+info.
411  
412  color_blocks        = <string>
413      String used for color blocks (e.g., "███", "\#\#\#").
414  
415  cpu_brand           = true | false
416      Show CPU brand name.
417  
418  cpu_cores           = true | false
419      Show CPU core count.
420  
421  cpu_frequency       = true | false
422      Show CPU frequency.
423  
424  cpu_speed           = true | false
425      Show CPU speed.
426  
427  cpu_temp            = "C" | "F"
428      Temperature unit for CPU: Celsius or Fahrenheit.
429  
430  cpu_show_temp       = true | false
431      Show CPU temperature.
432  
433  de_version          = true | false
434      Show desktop environment version.
435  
436  distro_display      = "name" | "name_version" | "name_arch" | "name_model" | "name_model_version" | "name_model_arch" | "name_model_version_arch"
437      How much detail to show for OS info.
438  
439  disk_display        = "info" | "percentage" | "infobar" | "barinfo" | "bar"
440      Disk usage display style.
441  
442  disk_subtitle       = "name" | "dir" | "none" | "mount"
443      Disk label: device, last dir, none, or full mount point.
444  
445  memory_percent      = true | false
446      Show memory as percent.
447  
448  memory_unit         = "mib" | "gib" | "kib"
449      Memory unit.
450  
451  package_managers    = "off" | "on" | "tiny"
452      Package info: none, full, or compact.
453  
454  refresh_rate        = true | false
455      Show display refresh rate.
456  
457  shell_path          = true | false
458      Show full shell path.
459  
460  shell_version       = true | false
461      Show shell version.
462  
463  uptime_shorthand    = "full" | "tiny" | "seconds"
464      Uptime format: verbose, compact, or seconds only.
465 
466  os_age_shorthand    = "full" | "tiny" | "seconds"
467      Format for the OS install age module.
468
469──────────────────────────────────────────────
470🖼 logo — ASCII Art Overrides
471──────────────────────────────────────────────
472  type              = "auto" | "file"
473      Select built-in art or load from disk.
474
475  source            = <path>
476      Path to a custom ASCII art file (used when type is "file").
477
478  padding.top       = <number>
479      Add blank lines above the ASCII logo.
480
481  padding.right     = <number>
482      Add spacing between the ASCII logo and information column.
483
484  padding.left      = <number>
485      Indent the ASCII logo horizontally.
486
487──────────────────────────────────────────────
488🧱 modules — Output Order and Custom Rows
489──────────────────────────────────────────────
490  Each entry is either:
491    • "break" — insert a blank spacer line
492    • { "type": <field>, "key": <label>, ... } — render a module
493    • { "type": "custom", "text": "hello" } — literal text
494
495  Common module fields:
496    - "titles", "os", "distro", "model", "kernel", "os_age"
497    - "uptime", "packages", "shell", "wm", "de", "cpu", "gpu"
498    - "memory", "disk", "resolution", "theme", "battery", "song", "colors"
499"#
500    );
501}