1use crate::cli::Cli;
9use crate::config::Config;
10use crate::fetch::SystemInfo;
11use crate::logo;
12use crate::theme::Theme;
13use owo_colors::OwoColorize;
14
15impl SystemInfo {
16 pub fn display(&self, cli: &Cli, _config: &Config) -> anyhow::Result<()> {
22 println!();
23 let theme_name = _config.theme.as_deref().or(cli.theme.as_deref());
24 let mut theme = match theme_name {
25 Some(name) => Theme::from_name(name),
26 None => Theme::detect_system_theme(), };
28
29 if let Some(custom) = &_config.custom_theme {
31 theme = Theme::with_custom_overrides(theme, custom);
32 }
33
34 let show_logo = _config.show_logo.unwrap_or(true) && !cli.no_logo;
35 if show_logo {
36 let distro_hint = _config.logo.clone().or_else(logo::detect_distro);
37 #[cfg(feature = "graphics")]
38 let mut printed_logo = false;
39 #[cfg(not(feature = "graphics"))]
40 let printed_logo = false;
41
42 if !cli.ascii_only {
43 let user_logo = if let Some(config_dir) = dirs::config_dir() {
45 let p = config_dir.join("retch").join("logo.png");
46 if p.exists() {
47 Some(p)
48 } else {
49 None
50 }
51 } else {
52 None
53 };
54
55 #[cfg(feature = "graphics")]
57 if !printed_logo && logo::supports_kitty() {
58 if let Some(path) = &user_logo {
59 logo::print_graphical_logo_from_path(path);
60 printed_logo = true;
61 } else if let Some(distro) = &distro_hint {
62 if let Some(bytes) = logo::get_embedded_logo(Some(distro)) {
63 logo::print_graphical_logo(bytes);
64 printed_logo = true;
65 }
66 }
67 }
68
69 #[cfg(feature = "graphics")]
71 if !printed_logo && logo::supports_iterm2() {
72 if let Some(path) = &user_logo {
73 logo::print_iterm2_logo_from_path(path);
74 printed_logo = true;
75 } else if let Some(distro) = &distro_hint {
76 if let Some(bytes) = logo::get_embedded_logo(Some(distro)) {
77 logo::print_iterm2_logo(bytes);
78 printed_logo = true;
79 }
80 }
81 }
82
83 #[cfg(feature = "graphics")]
85 if !printed_logo && logo::supports_sixel() {
86 if let Some(path) = &user_logo {
87 logo::print_sixel_logo_from_path(path);
88 printed_logo = true;
89 } else if let Some(distro) = &distro_hint {
90 if let Some(bytes) = logo::get_embedded_logo(Some(distro)) {
91 logo::print_sixel_logo(bytes);
92 printed_logo = true;
93 }
94 }
95 }
96
97 if !printed_logo && logo::chafa_available() {
99 if let Some(path) = &user_logo {
100 if logo::print_with_chafa(path) {
101 printed_logo = true;
102 }
103 } else if distro_hint.is_some() {
104 }
107 }
108 }
109
110 if !printed_logo {
112 logo::print_distro_logo_with_ascii(distro_hint.as_deref(), cli.ascii_only);
114 }
115 println!(); }
117
118 let allowed_fields: Option<Vec<String>> = if cli.long {
120 None } else if cli.short {
122 Some(vec![
123 "os".to_string(),
124 "kernel".to_string(),
125 "host".to_string(),
126 "cpu".to_string(),
127 "gpu".to_string(),
128 "memory".to_string(),
129 "disk".to_string(),
130 "net".to_string(),
131 ])
132 } else if let Some(fields) = &_config.fields {
133 Some(fields.iter().map(|s| s.to_lowercase()).collect())
134 } else {
135 Some(vec![
137 "os".to_string(),
138 "kernel".to_string(),
139 "host".to_string(),
140 "cpu".to_string(),
141 "motherboard".to_string(),
142 "bios".to_string(),
143 "gpu".to_string(),
144 "display".to_string(),
145 "audio".to_string(),
146 "memory".to_string(),
147 "swap".to_string(),
148 "load".to_string(),
149 "disk".to_string(),
150 "net".to_string(),
151 "uptime".to_string(),
152 ])
153 };
154
155 let should_show = |label: &str| -> bool {
156 match &allowed_fields {
157 Some(fields) => {
158 let norm_label = label.to_lowercase().replace(['-', '_'], " ");
159 let norm_label_no_spaces = norm_label.replace(' ', "");
160 fields.iter().any(|f| {
161 let norm_f = f.to_lowercase().replace(['-', '_'], " ");
162 norm_f == norm_label || norm_f.replace(' ', "") == norm_label_no_spaces
163 })
164 }
165 None => true,
166 }
167 };
168
169 let label_width = 10;
171 let print_line = |label: &str, value: &str| {
172 if should_show(label) {
173 println!(
174 "{:>width$}{} {}",
175 theme.color_label(label),
176 theme.color_separator(":"),
177 theme.color_value(value),
178 width = label_width
179 );
180 }
181 };
182
183 print_line("OS", &self.os);
184 if let Some(kernel) = &self.kernel {
185 print_line("Kernel", kernel);
186 }
187 if let Some(host) = &self.hostname {
188 print_line("Host", host);
189 }
190 if let Some(user) = &self.current_user {
191 print_line("User", user);
192 }
193 print_line("Arch", &self.arch);
194 print_line("CPU", &format!("{} ({} cores)", self.cpu, self.cpu_cores));
195 if let Some(freq) = &self.cpu_freq {
196 print_line("CPU Freq", freq);
197 }
198 if let Some(motherboard) = &self.motherboard {
199 print_line("Motherboard", motherboard);
200 }
201 if let Some(bios) = &self.bios {
202 print_line("BIOS", bios);
203 }
204 if should_show("GPU") {
205 for gpu in &self.gpu {
206 print_line("GPU", gpu);
207 }
208 }
209 if should_show("Display") {
210 for display in &self.displays {
211 print_line("Display", display);
212 }
213 }
214 if let Some(audio) = &self.audio {
215 print_line("Audio", audio);
216 }
217 print_line("Memory", &self.memory);
218 print_line("Swap", &self.swap);
219 print_line("Procs", &self.processes.to_string());
220 if let Some(load) = &self.load_avg {
221 print_line("Load", load);
222 }
223
224 if should_show("Disk") {
225 for disk in &self.disks {
226 print_line("Disk", disk);
227 }
228 }
229
230 if should_show("Temp") {
231 for temp in &self.temps {
232 print_line("Temp", temp);
233 }
234 }
235
236 if should_show("Net") {
237 if cli.long {
238 for net in &self.networks {
239 if let Some(ref active) = self.active_interface {
240 if net.contains(active) {
241 print_line("Net", &net.bright_blue().to_string());
242 }
243 }
244 }
245 for net in &self.networks {
246 if let Some(ref active) = self.active_interface {
247 if net.contains(active) {
248 continue;
249 }
250 }
251 print_line("Net", net);
252 }
253 } else {
254 let mut printed = false;
255 if let Some(ref active) = self.active_interface {
256 for net in &self.networks {
257 if net.contains(active) {
258 print_line("Net", net);
259 printed = true;
260 break;
261 }
262 }
263 }
264 if !printed {
265 for net in &self.networks {
266 if net.contains("[Up]") {
267 print_line("Net", net);
268 break;
269 }
270 }
271 }
272 }
273 }
274
275 if let Some(ip) = &self.public_ip {
276 print_line("Public IP", ip);
277 }
278
279 if let Some(wifi) = &self.wifi {
280 print_line("Wi-Fi", wifi);
281 }
282
283 if let Some(bt) = &self.bluetooth {
284 print_line("Bluetooth", bt);
285 }
286
287 let uptime_str = format_uptime(&self.uptime);
289 let boot_display = format!("{} since {}", uptime_str, self.boot_time);
290 print_line("Uptime", &boot_display);
291
292 if let Some(bat) = &self.battery {
293 print_line("Battery", bat);
294 }
295
296 if let Some(shell) = &self.shell {
297 print_line("Shell", shell);
298 }
299 if let Some(term) = &self.terminal {
300 print_line("Terminal", term);
301 }
302 if let Some(de) = &self.desktop {
303 print_line("Desktop", de);
304 }
305 if let Some(ui_theme) = &self.ui_theme {
306 print_line("Theme", ui_theme);
307 }
308 if let Some(icons) = &self.icons {
309 print_line("Icons", icons);
310 }
311 if let Some(cursor) = &self.cursor {
312 print_line("Cursor", cursor);
313 }
314 if let Some(font) = &self.font {
315 print_line("Font", font);
316 }
317 print_line("Users", &self.users.to_string());
318 if let Some(pkgs) = self.packages {
319 if pkgs > 0 {
320 print_line("Packages", &pkgs.to_string());
321 }
322 }
323
324 Ok(())
325 }
326}
327
328fn format_uptime(uptime: &str) -> String {
332 let seconds: u64 = uptime.trim_end_matches('s').parse().unwrap_or(0);
334
335 let years = seconds / (365 * 24 * 3600);
336 let days = (seconds % (365 * 24 * 3600)) / (24 * 3600);
337 let hours = (seconds % (24 * 3600)) / 3600;
338 let minutes = (seconds % 3600) / 60;
339 let secs = seconds % 60;
340
341 let mut parts = Vec::new();
342 if years > 0 {
343 parts.push(format!("{}y", years));
344 }
345 if days > 0 {
346 parts.push(format!("{}d", days));
347 }
348 if hours > 0 {
349 parts.push(format!("{}h", hours));
350 }
351 if minutes > 0 {
352 parts.push(format!("{}m", minutes));
353 }
354 if secs > 0 || parts.is_empty() {
355 parts.push(format!("{}s", secs));
356 }
357
358 parts.join(" ")
359}
360
361#[cfg(test)]
362mod tests {
363 use super::*;
364
365 #[test]
366 fn test_format_uptime() {
367 assert_eq!(format_uptime("60s"), "1m");
368 assert_eq!(format_uptime("3600s"), "1h");
369 assert_eq!(format_uptime("3661s"), "1h 1m 1s");
370 assert_eq!(format_uptime("86400s"), "1d");
371 assert_eq!(format_uptime("90061s"), "1d 1h 1m 1s");
372 assert_eq!(format_uptime("31536000s"), "1y");
373 assert_eq!(format_uptime("31626061s"), "1y 1d 1h 1m 1s");
374 assert_eq!(format_uptime("0s"), "0s");
375 }
376}