Skip to main content

retch_cli/
display.rs

1use crate::cli::Cli;
2use crate::config::Config;
3use crate::fetch::SystemInfo;
4use crate::logo;
5use crate::theme::Theme;
6
7impl SystemInfo {
8    pub fn display(&self, cli: &Cli, _config: &Config) -> anyhow::Result<()> {
9        let theme_name = _config.theme.as_deref().or(cli.theme.as_deref());
10        let mut theme = match theme_name {
11            Some(name) => Theme::from_name(name),
12            None => Theme::detect_system_theme(), // Default to system preference
13        };
14
15        // Apply custom theme overrides from config if present
16        if let Some(custom) = &_config.custom_theme {
17            theme = Theme::with_custom_overrides(theme, custom);
18        }
19
20        let show_logo = _config.show_logo.unwrap_or(true) && !cli.no_logo;
21        if show_logo {
22            #[cfg(feature = "graphics")]
23            let mut printed_logo = false;
24            #[cfg(not(feature = "graphics"))]
25            let printed_logo = false;
26
27            if !cli.ascii_only {
28                // 1. Try user-provided custom logo first (config override)
29                let user_logo = if let Some(config_dir) = dirs::config_dir() {
30                    let p = config_dir.join("retch").join("logo.png");
31                    if p.exists() {
32                        Some(p)
33                    } else {
34                        None
35                    }
36                } else {
37                    None
38                };
39
40                // 2. Graphics mode (Kitty / iTerm2)
41                #[cfg(feature = "graphics")]
42                if !printed_logo && logo::supports_graphical_logo() {
43                    if let Some(path) = &user_logo {
44                        logo::print_graphical_logo_from_path(path);
45                        printed_logo = true;
46                    } else if let Some(distro) = logo::detect_distro() {
47                        if let Some(bytes) = logo::get_embedded_logo(Some(&distro)) {
48                            logo::print_graphical_logo(bytes);
49                            printed_logo = true;
50                        }
51                    }
52                }
53
54                // 3. Chafa fallback (high-quality symbols)
55                if !printed_logo && logo::chafa_available() {
56                    if let Some(path) = &user_logo {
57                        if logo::print_with_chafa(path) {
58                            printed_logo = true;
59                        }
60                    } else if logo::detect_distro().is_some() {
61                        // For chafa we need a file, so we skip embedded for now
62                        // (user can provide logo.png for chafa path)
63                    }
64                }
65            }
66
67            // 3. Final fallback: Real Fastfetch ASCII logo
68            if !printed_logo {
69                let distro_hint = logo::detect_distro();
70                // Use the unified priority logic (graphic -> chafa -> ASCII)
71                logo::print_distro_logo_with_ascii(distro_hint.as_deref(), cli.ascii_only);
72            }
73            println!(); // spacing after logo
74        }
75
76        // Determine which fields to show
77        let allowed_fields: Option<Vec<String>> = if cli.long {
78            None // show everything
79        } else if cli.short {
80            Some(vec![
81                "os".to_string(),
82                "kernel".to_string(),
83                "host".to_string(),
84                "cpu".to_string(),
85                "gpu".to_string(),
86                "memory".to_string(),
87                "disk".to_string(),
88            ])
89        } else if let Some(fields) = &_config.fields {
90            Some(fields.iter().map(|s| s.to_lowercase()).collect())
91        } else {
92            // Default set
93            Some(vec![
94                "os".to_string(),
95                "kernel".to_string(),
96                "host".to_string(),
97                "cpu".to_string(),
98                "gpu".to_string(),
99                "memory".to_string(),
100                "swap".to_string(),
101                "load".to_string(),
102                "disk".to_string(),
103                "net".to_string(),
104                "uptime".to_string(),
105            ])
106        };
107
108        let should_show = |label: &str| -> bool {
109            match &allowed_fields {
110                Some(fields) => fields.contains(&label.to_lowercase()),
111                None => true,
112            }
113        };
114
115        // Helper for right-aligned labels
116        let label_width = 10;
117        let print_line = |label: &str, value: &str| {
118            if should_show(label) {
119                println!(
120                    "{:>width$}{} {}",
121                    theme.color_label(label),
122                    theme.color_separator(":"),
123                    theme.color_value(value),
124                    width = label_width
125                );
126            }
127        };
128
129        print_line("OS", &self.os);
130        if let Some(kernel) = &self.kernel {
131            print_line("Kernel", kernel);
132        }
133        if let Some(host) = &self.hostname {
134            print_line("Host", host);
135        }
136        if let Some(user) = &self.current_user {
137            print_line("User", user);
138        }
139        print_line("Arch", &self.arch);
140        print_line("CPU", &format!("{} ({} cores)", self.cpu, self.cpu_cores));
141        if let Some(freq) = &self.cpu_freq {
142            print_line("CPU Freq", freq);
143        }
144        if should_show("GPU") {
145            for gpu in &self.gpu {
146                print_line("GPU", gpu);
147            }
148        }
149        print_line("Memory", &self.memory);
150        print_line("Swap", &self.swap);
151        print_line("Procs", &self.processes.to_string());
152        if let Some(load) = &self.load_avg {
153            print_line("Load", load);
154        }
155
156        if should_show("Disk") {
157            for disk in &self.disks {
158                print_line("Disk", disk);
159            }
160        }
161
162        if should_show("Temp") {
163            for temp in &self.temps {
164                print_line("Temp", temp);
165            }
166        }
167
168        if should_show("Net") {
169            for net in &self.networks {
170                print_line("Net", net);
171            }
172        }
173
174        // Uptime: human duration first, then ISO boot time with timezone
175        let uptime_str = format_uptime(&self.uptime);
176        let boot_display = format!("{} since {}", uptime_str, self.boot_time);
177        print_line("Uptime", &boot_display);
178
179        if let Some(bat) = &self.battery {
180            print_line("Battery", bat);
181        }
182
183        if let Some(shell) = &self.shell {
184            print_line("Shell", shell);
185        }
186        if let Some(term) = &self.terminal {
187            print_line("Terminal", term);
188        }
189        if let Some(de) = &self.desktop {
190            print_line("Desktop", de);
191        }
192        print_line("Users", &self.users.to_string());
193        if let Some(pkgs) = self.packages {
194            if pkgs > 0 {
195                print_line("Packages", &pkgs.to_string());
196            }
197        }
198
199        Ok(())
200    }
201}
202
203fn format_uptime(uptime: &str) -> String {
204    // Parse the uptime string (e.g. "45224s")
205    let seconds: u64 = uptime.trim_end_matches('s').parse().unwrap_or(0);
206
207    let years = seconds / (365 * 24 * 3600);
208    let days = (seconds % (365 * 24 * 3600)) / (24 * 3600);
209    let hours = (seconds % (24 * 3600)) / 3600;
210    let minutes = (seconds % 3600) / 60;
211    let secs = seconds % 60;
212
213    let mut parts = Vec::new();
214    if years > 0 {
215        parts.push(format!("{}y", years));
216    }
217    if days > 0 {
218        parts.push(format!("{}d", days));
219    }
220    if hours > 0 {
221        parts.push(format!("{}h", hours));
222    }
223    if minutes > 0 {
224        parts.push(format!("{}m", minutes));
225    }
226    if secs > 0 || parts.is_empty() {
227        parts.push(format!("{}s", secs));
228    }
229
230    parts.join(" ")
231}