Skip to main content

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 = "package_managers", alias = "packages")]
92    pub package_managers: Option<String>,
93    #[arg(long = "uptime_shorthand", alias = "uptime")]
94    pub uptime_shorthand: Option<String>,
95    #[arg(long = "os_age_shorthand")]
96    pub os_age_shorthand: Option<String>,
97    #[arg(long = "distro_shorthand", alias = "distro-display")]
98    pub distro_shorthand: Option<String>,
99    #[arg(long = "color_blocks")]
100    pub color_blocks: Option<String>,
101    #[arg(long = "cpu_temp", alias = "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    #[arg(long = "disable")]
108    pub disable_modules: Option<String>,
109
110    #[arg(long = "memory_percent")]
111    pub memory_percent: Option<bool>,
112    #[arg(long = "cpu_speed")]
113    pub cpu_speed: Option<bool>,
114    #[arg(long = "cpu_frequency")]
115    pub cpu_frequency: Option<bool>,
116    #[arg(long = "cpu_cores")]
117    pub cpu_cores: Option<bool>,
118    #[arg(long = "cpu_brand")]
119    pub cpu_brand: Option<bool>,
120    #[arg(long = "shell_path")]
121    pub shell_path: Option<bool>,
122    #[arg(long = "shell_version")]
123    pub shell_version: Option<bool>,
124    #[arg(long = "de_version")]
125    pub de_version: Option<bool>,
126    #[arg(long = "gpu_brand")]
127    pub gpu_brand: Option<bool>,
128    #[arg(long = "kernel_shorthand")]
129    pub kernel_shorthand: Option<bool>,
130    #[arg(long = "speed_shorthand")]
131    pub speed_shorthand: Option<bool>,
132    #[arg(long = "disk_percent")]
133    pub disk_percent: Option<bool>,
134    #[arg(long = "gpu_type")]
135    pub gpu_type: Option<String>,
136    #[arg(long = "disk_show")]
137    pub disk_show: Option<String>,
138
139    /// Fetch info from remote hosts via SSH (e.g., user@host or host:port)
140    #[arg(long = "ssh", value_name = "HOST")]
141    pub ssh_hosts: Vec<String>,
142
143    /// Print the default config to stdout and exit
144    #[arg(long = "print_config", action = ArgAction::SetTrue)]
145    pub print_config: bool,
146}
147
148impl Args {
149    pub fn into_overrides(self) -> CliOverrides {
150        let mut overrides = CliOverrides::default();
151        overrides.use_defaults = self.no_config;
152        overrides.config_path = self.config_path.clone();
153        overrides.output_format = self.format;
154
155        if let Some(val) = self.ascii_distro {
156            overrides.set_string("ascii_distro", val);
157        }
158        if let Some(val) = self.ascii_colors {
159            overrides.set_string("ascii_colors", val);
160        }
161        if let Some(val) = self.custom_ascii_path {
162            overrides.set_string("custom_ascii_path", val);
163        }
164        if let Some(val) = self.battery_display {
165            overrides.set_string("battery_display", val);
166        }
167        if let Some(val) = self.disk_display {
168            overrides.set_string("disk_display", val);
169        }
170        if let Some(val) = self.disk_subtitle {
171            overrides.set_string("disk_subtitle", val);
172        }
173        if let Some(val) = self.memory_unit {
174            overrides.set_string("memory_unit", val);
175        }
176        if let Some(val) = self.package_managers {
177            overrides.set_string("package_managers", val);
178        }
179        if let Some(val) = self.uptime_shorthand {
180            overrides.set_string("uptime_shorthand", val);
181        }
182        if let Some(val) = self.os_age_shorthand {
183            overrides.set_string("os_age_shorthand", val);
184        }
185        if let Some(val) = self.distro_shorthand {
186            overrides.set_string("distro_shorthand", val);
187        }
188        if let Some(val) = self.color_blocks {
189            overrides.set_string("color_blocks", val);
190        }
191        if let Some(val) = self.cpu_temp {
192            overrides.set_string("cpu_temp", val);
193        }
194        if let Some(val) = self.gpu_type {
195            overrides.set_string("gpu_type", val);
196        }
197        if let Some(val) = self.disk_show {
198            overrides.set_string("disk_show", 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        if let Some(disable) = self.disable_modules {
224            for entry in disable.split(',') {
225                let trimmed = entry.trim();
226                if !trimmed.is_empty() {
227                    overrides.hide_modules.insert(trimmed.to_string());
228                }
229            }
230        }
231
232        apply_bool_override(&mut overrides, "memory_percent", self.memory_percent);
233        apply_bool_override(&mut overrides, "cpu_speed", self.cpu_speed);
234        apply_bool_override(&mut overrides, "cpu_frequency", self.cpu_frequency);
235        apply_bool_override(&mut overrides, "cpu_cores", self.cpu_cores);
236        apply_bool_override(&mut overrides, "cpu_brand", self.cpu_brand);
237        apply_bool_override(&mut overrides, "shell_path", self.shell_path);
238        apply_bool_override(&mut overrides, "shell_version", self.shell_version);
239        apply_bool_override(&mut overrides, "de_version", self.de_version);
240        apply_bool_override(&mut overrides, "gpu_brand", self.gpu_brand);
241        apply_bool_override(&mut overrides, "kernel_shorthand", self.kernel_shorthand);
242        apply_bool_override(&mut overrides, "speed_shorthand", self.speed_shorthand);
243        apply_bool_override(&mut overrides, "disk_percent", self.disk_percent);
244
245        overrides.ssh_hosts = self.ssh_hosts.clone();
246
247        overrides
248    }
249}
250
251fn apply_bool_override(overrides: &mut CliOverrides, key: &str, value: Option<bool>) {
252    if let Some(value) = value {
253        overrides.set_bool(key, value);
254    }
255}
256
257pub fn print_custom_help() {
258    println!(
259        "{}",
260        r#"🧠 leenfetch — Minimal, Stylish System Info for Your Terminal
261
262USAGE:
263  leenfetch [OPTIONS]
264
265OPTIONS:
266  -V, --version            Print version information and exit
267  -h, --help               Show this help message and exit
268  -i, --init               Create the default config file in ~/.config/leenfetch/
269  -r, --reinit             Reinitialize the config file to defaults
270  -l, --list-options       Show all available config options and values
271      --config <path>      Load configuration from a custom file
272      --no-config          Ignore config files and use built-in defaults
273      --ssh <host>         Fetch info from remote hosts via SSH (repeatable)
274      --format <kind>      Output format: pretty (default) or json
275
276  --ascii_distro <s>       Override detected distro (e.g., ubuntu, arch, arch_small)
277  --ascii_colors <s>       Override color palette (e.g., 2,7,3 or "distro")
278  --custom_ascii_path <p>  Use ASCII art from the given file path
279
280  --battery_display <mode> Battery output style (off, bar, infobar, barinfo)
281  --disk_display <mode>    Disk output style (info, percentage, infobar, barinfo, bar)
282  --disk_subtitle <mode>   Disk subtitle (name, dir, none, mount)
283  --disk_percent <bool>    Show disk percentage
284  --disk_show <path>       Which disks to display (comma-separated mount points)
285  --memory_unit <unit>     Force memory unit (kib, mib, gib)
286  --package_managers <mode> Package summary verbosity (off, on, tiny)
287  --uptime_shorthand <mode> Uptime shorthand (full, tiny, seconds)
288  --os_age_shorthand <mode> OS age shorthand (full, tiny, seconds)
289  --distro_shorthand <mode> Distro detail level (name, name_version, ...)
290  --color_blocks <glyph>   Glyph used for color swatches
291  --cpu_temp <unit>        CPU temperature unit (C, F, off)
292  --gpu_type <type>        Which GPU to display (all, dedicated, integrated)
293  --only <list>            Render only listed modules (comma-separated)
294  --hide <list>            Hide listed modules (comma-separated)
295  --disable <list>         Alias for --hide
296
297  --memory_percent  <true|false>
298  --cpu_speed       <true|false>
299  --cpu_frequency   <true|false>
300  --cpu_cores       <true|false>
301  --cpu_brand       <true|false>
302  --shell_path      <true|false>
303  --shell_version   <true|false>
304  --de_version      <true|false>
305  --gpu_brand       <true|false>
306  --kernel_shorthand <true|false>
307  --speed_shorthand <true|false>
308  --disk_percent    <true|false>
309
310DESCRIPTION:
311  leenfetch is a modern, minimal, and the fastest system info tool,
312  written in Rust, designed for terminal enthusiasts.
313
314  It fetches and prints system information like:
315    • OS, Kernel, Uptime
316    • CPU, GPU, Memory, Disks
317    • Shell, WM, DE, Theme
318    • Resolution, Battery, Current Song
319
320  🛠️  Configuration:
321    • Linux:   ~/.config/leenfetch/config.jsonc
322    • Windows: %APPDATA%/leenfetch/config.jsonc
323    One JSONC file with inline comments covering flags and a Fastfetch-style modules array.
324    Edit it to control appearance, spacing (via "break" entries), and output order.
325
326EXAMPLES:
327  leenfetch                         🚀 Run normally with your config
328  leenfetch --init                  🔧 Create the default config file
329  leenfetch --ssh user@server       🌐 Fetch from a remote host over SSH
330  leenfetch --ssh host1 --ssh host2 🛰️ Fetch multiple hosts sequentially
331  leenfetch --ascii_distro arch     🎨 Use Arch logo manually
332  leenfetch --ascii_colors 2,7,3    🌈 Use custom colors
333  leenfetch --package_managers tiny 📦 Compact package summary for screenshots
334  leenfetch --only cpu,memory       🧩 Focus on specific modules temporarily
335  leenfetch --list-options          📜 View all available configuration keys
336
337TIPS:
338  • Adjust styles in the `flags` section (e.g., ascii_distro, disk_display, battery_display)
339  • Reorder entries in the `modules` array (use "break" for spacing)
340  • Tweak `logo.padding` to add margins around the ASCII art
341
342For more, see the README or run `leenfetch --list-options`.
343        "#
344    );
345}
346
347pub fn list_options() {
348    println!(
349        "{}",
350        r#"
351
352📄 leenfetch Configuration Options Reference
353──────────────────────────────────────────────
354
355📁 leenfetch stores everything in a single JSONC file:
356  • Linux:   ~/.config/leenfetch/config.jsonc
357  • Windows: %APPDATA%/leenfetch/config.jsonc
358
359🗂️  Sections inside config.jsonc:
360  • 🖼️ flags — Display and formatting options
361  • 🧱 modules — Output order and custom rows
362──────────────────────────────────────────────
363🖼️ flags — Display and Formatting Options
364──────────────────────────────────────────────
365  ascii_distro        = "auto" | <name>
366      Which ASCII art to use. "auto" detects your distro or specify a distro name (e.g., "arch").
367  
368  ascii_colors        = "distro" | <list>
369      Color palette for ASCII art. "distro" uses default, or provide a comma-separated list (e.g., "1,2,3,4").
370  
371  custom_ascii_path   = "" | <path>
372      Path to a custom ASCII art file. Empty for default.
373  
374  battery_display     = "off" | "bar" | "infobar" | "barinfo"
375      How to show battery info: none, bar only, info+bar, or bar+info.
376  
377  color_blocks        = <string>
378      String used for color blocks (e.g., "███", "\#\#\#").
379  
380  cpu_brand           = true | false
381      Show CPU brand name.
382  
383  cpu_cores           = true | false
384      Show CPU core count.
385  
386  cpu_frequency       = true | false
387      Show CPU frequency.
388  
389  cpu_speed           = true | false
390      Show CPU speed.
391  
392  cpu_temp            = "C" | "F" | "off"
393      Temperature unit for CPU: Celsius, Fahrenheit, or off.
394  
395  de_version          = true | false
396      Show desktop environment version.
397  
398  distro_shorthand    = "name" | "name_version" | "name_arch" | "name_model" | "name_model_version" | "name_model_arch" | "name_model_version_arch"
399      How much detail to show for OS info.
400  
401  disk_display        = "info" | "percentage" | "infobar" | "barinfo" | "bar"
402      Disk usage display style.
403  
404  disk_percent        = true | false
405      Show disk usage percentage.
406  
407  disk_show           = <path>
408      Which disks to display (default "/").
409  
410  disk_subtitle       = "name" | "dir" | "none" | "mount"
411      Disk label: device, last dir, none, or full mount point.
412  
413  gpu_brand           = true | false
414      Show GPU vendor name.
415  
416  gpu_type            = "all" | "dedicated" | "integrated"
417      Which GPU to display.
418  
419  kernel_shorthand    = true | false
420      Shorten kernel output.
421  
422  memory_percent      = true | false
423      Show memory as percent.
424  
425  memory_unit         = "mib" | "gib" | "kib"
426      Memory unit.
427  
428  package_managers    = "off" | "on" | "tiny"
429      Package info: none, full, or compact.
430  
431  shell_path          = true | false
432      Show full shell path.
433  
434  shell_version       = true | false
435      Show shell version.
436  
437  speed_shorthand     = true | false
438      Show CPU speed without decimals.
439  
440  uptime_shorthand    = "full" | "tiny" | "seconds"
441      Uptime format: verbose, compact, or seconds only.
442  
443  os_age_shorthand    = "full" | "tiny" | "seconds"
444      Format for the OS install age module.
445
446──────────────────────────────────────────────
447🖼 logo — ASCII Art Overrides
448──────────────────────────────────────────────
449  type              = "auto" | "file"
450      Select built-in art or load from disk.
451
452  source            = <path>
453      Path to a custom ASCII art file (used when type is "file").
454
455  padding.top       = <number>
456      Add blank lines above the ASCII logo.
457
458  padding.right     = <number>
459      Add spacing between the ASCII logo and information column.
460
461  padding.left      = <number>
462      Indent the ASCII logo horizontally.
463
464──────────────────────────────────────────────
465🧱 modules — Output Order and Custom Rows
466──────────────────────────────────────────────
467  Each entry is either:
468    • "break" — insert a blank spacer line
469    • { "type": <field>, "key": <label>, ... } — render a module
470    • { "type": "custom", "text": "hello" } — literal text
471
472  Common module fields:
473    - "titles", "os", "distro", "model", "kernel", "os_age"
474    - "uptime", "packages", "shell", "wm", "de", "cpu", "gpu"
475    - "memory", "disk", "resolution", "theme", "battery", "song", "colors"
476"#
477    );
478}