Skip to main content

leenfetch_core/modules/
helper.rs

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