1use clap::{ArgAction, Parser, ValueEnum};
2use std::collections::{HashMap, HashSet};
3
4#[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 #[arg(short = 'h', long = "help", action = ArgAction::SetTrue)]
51 pub help: bool,
52
53 #[arg(long, value_enum, default_value_t = OutputFormat::Pretty)]
55 pub format: OutputFormat,
56
57 #[arg(short = 'i', long, action = ArgAction::SetTrue)]
59 pub init: bool,
60
61 #[arg(short = 'r', long, action = ArgAction::SetTrue)]
63 pub reinit: bool,
64
65 #[arg(short = 'l', long = "list-options", action = ArgAction::SetTrue)]
67 pub list_options: bool,
68
69 #[arg(long = "config")]
71 pub config_path: Option<String>,
72
73 #[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 #[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}