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