1#![allow(clippy::print_literal, clippy::field_reassign_with_default)]
2
3use clap::{ArgAction, Parser, ValueEnum};
4use std::collections::{HashMap, HashSet};
5
6#[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 #[arg(short = 'h', long = "help", action = ArgAction::SetTrue)]
48 pub help: bool,
49
50 #[arg(long, value_enum, default_value_t = OutputFormat::Pretty)]
52 pub format: OutputFormat,
53
54 #[arg(short = 'i', long, action = ArgAction::SetTrue)]
56 pub init: bool,
57
58 #[arg(short = 'r', long, action = ArgAction::SetTrue)]
60 pub reinit: bool,
61
62 #[arg(long = "choose-config", action = ArgAction::SetTrue)]
64 pub choose_config: bool,
65
66 #[arg(short = 'l', long = "list-options", action = ArgAction::SetTrue)]
68 pub list_options: bool,
69
70 #[arg(long = "config")]
72 pub config_path: Option<String>,
73
74 #[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 #[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 #[arg(long = "ssh", value_name = "HOST")]
143 pub ssh_hosts: Vec<String>,
144
145 #[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}