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