>();
142 | let last_index = lines.len() - 1;
143 | for (index, line) in lines.into_iter().enumerate() {
144 | let mut line = line.to_owned();
145 | if index != last_index {
146 | line += "\n";
147 | }
148 | logo_parts.push(LogoPart {
149 | color: Color(Some(new_color)),
150 | content: line.into(),
151 | });
152 | }
153 | } else if !logo_part.is_empty() {
154 | let logo_part = logo_part.replace("\\\\", "\\");
155 | logo_parts.push(LogoPart {
156 | color: Color(None),
157 | content: logo_part.into(),
158 | });
159 | }
160 | }
161 |
162 | Some((
163 | pattern == "[Ll]inux*",
164 | Logo {
165 | primary_color: Color(Some(primary_color)),
166 | secondary_color: Color(Some(secondary_color)),
167 | pattern: pattern.to_owned().into(),
168 | logo_parts: logo_parts.into(),
169 | },
170 | ))
171 | }
172 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | pfetch-rs
2 | A rewrite of the pfetch system information tool by dylanaraps in Rust
3 | 
4 |
5 | ## About
6 |
7 | If you are familiar with the [pfetch](https://github.com/dylanaraps/pfetch)
8 | system information tool by [dylanaraps](https://github.com/dylanaraps), this
9 | does the exact same thing, but with an about _10x faster_ runtime. _pfetch_ is
10 | simple by design with some (but not many) configuration options and a
11 | minimalistic look.
12 |
13 | **Supported Platforms:** Linux, Android, macOS, Windows, FreeBSD, NetBSD
14 |
15 | **Included Logos:** Alma Linux _(new)_, Alpine Linux, Android, AmogOS _(new)_,
16 | Arch Linux, ArcoLinux, Artix Linux, Bazzite _(new)_, Bedrock Linux, Buildroot,
17 | CachyOS _(new)_, CelOS, CentOS, Crystal Linux, dahliaOS, Debian, Devuan, DietPi
18 | _(new)_, DragonflyBSD, Elementary OS, EndeavourOS, Fedora, Fiwix _(new)_,
19 | FreeBSD, Garuda Linux, Gentoo Linux, Gnu Hurd _(updated)_, Guix, Haiku, HydroOS,
20 | Hyperbola, instantOS, IRIX, KDE neon, Linux Lite, Linux, Mint, macOS, Mageia,
21 | Manjaro, Minix, MorphOS _(new)_, MX Linux, NetBSD, NixOS, Nobara Project
22 | _(new)_, OpenBSD, openSUSE Tumbleweed, openSUSE Leap, OpenWrt, Oracle Linux
23 | _(new)_, Parabola, Pop!\_OS _(updated)_, PureOS, Raspbian, Rocky Linux _(new)_,
24 | SerenityOS, Slackware, Solus, SteamOS _(new)_, Solaris, Ubuntu, Vanilla OS
25 | _(new)_, Void Linux, Windows _(new)_, Xeonix Linux
26 |
27 | You can check out how they look [here](./all_logos.md).
28 |
29 | For all other distributions, a penguin will be displayed.
30 |
31 | _Credit to [the original pfetch](https://github.com/dylanaraps/pfetch) and
32 | [its contributors](https://github.com/dylanaraps/pfetch/graphs/contributors)._
33 |
34 | If you want a logo to be added, feel free to open an issue or a PR.
35 |
36 | ## Installation
37 |
38 | _Note: On openSUSE, install the `rpm-devel` package for faster package count._
39 |
40 | ### Binary
41 |
42 | Download a binary from the
43 | [latest release](https://github.com/Gobidev/pfetch-rs/releases/latest).
44 |
45 | ### Cargo
46 |
47 | ```sh
48 | cargo install pfetch
49 | ```
50 |
51 | ### Homebrew
52 |
53 | ```sh
54 | brew install pfetch-rs
55 | ```
56 |
57 | ### Nixpkgs
58 |
59 | Install the
60 | [pfetch-rs](https://search.nixos.org/packages?channel=unstable&show=pfetch-rs)
61 | Nix package.
62 |
63 | ### AUR
64 |
65 | Install the [pfetch-rs](https://aur.archlinux.org/packages/pfetch-rs) or
66 | [pfetch-rs-bin](https://aur.archlinux.org/packages/pfetch-rs-bin) AUR package.
67 |
68 | ## Performance
69 |
70 | Benchmarks performed on an AMD Ryzen 5 3600. Execution time is measured using
71 | [hyperfine](https://github.com/sharkdp/hyperfine) with `-w 4 -m 500 -N` flags.
72 |
73 | | Implementation | Mean [ms] | Min [ms] | Max [ms] |
74 | | :---------------: | :--------: | :------: | :------: |
75 | | POSIX `sh` (bash) | 23.7 ± 0.9 | 22.3 | 29.3 |
76 | | POSIX `sh` (dash) | 15.9 ± 0.3 | 15.1 | 18.2 |
77 | | Rust (v2.3.0) | 2.2 ± 0.2 | 1.8 | 3.9 |
78 |
79 | _Note: This is with `pacman` and `flatpak` being the only installed package
80 | managers. For more info, see [Improving Performance](#imp_perf)._
81 |
82 |
83 |
84 | ### Improving Performance
85 |
86 | Counting packages of `zypper` can be sped up a lot by installing the `rpm-devel`
87 | package. If the `zypper` package count takes too long, it can be disabled by
88 | setting the `PF_FAST_PKG_COUNT` environment variable to any value.
89 |
90 | ## Configuration
91 |
92 | Like the original `pfetch`, `pfetch-rs` is configured through environment
93 | variables. Your existing config will probably still work, the main difference is
94 | how padding is configured.
95 |
96 | If you want to display a custom logo, use the `PF_CUSTOM_LOGOS` option, an
97 | example for a custom logos file can be found below.
98 |
99 | ```sh
100 | # Which information to display.
101 | # Default: first example below
102 | # Valid: space separated string
103 | #
104 | # OFF by default: shell editor wm de palette cpu
105 | PF_INFO="ascii title os host kernel uptime pkgs memory"
106 |
107 | # Example: Only ASCII.
108 | PF_INFO="ascii"
109 |
110 | # Example: Only Information.
111 | PF_INFO="title os host kernel uptime pkgs memory"
112 |
113 | # A file containing environment variables to source before running pfetch
114 | # Default: unset
115 | # Valid: A shell script
116 | PF_SOURCE=""
117 |
118 | # A file containing pfetch logos to overwrite default logos or add new logos
119 | # Default: unset
120 | # Valid: Path to a file containing pfetch logos (example below)
121 | PF_CUSTOM_LOGOS="~/.config/pfetch_logos"
122 |
123 | # Separator between info name and info data.
124 | # Default: unset
125 | # Valid: string
126 | PF_SEP=":"
127 |
128 | # Enable/Disable colors in output:
129 | # Default: 1
130 | # Valid: 1 (enabled), 0 (disabled)
131 | PF_COLOR=1
132 |
133 | # Color of info names:
134 | # Default: unset (auto)
135 | # Valid: 0-9
136 | PF_COL1=4
137 |
138 | # Color of info data:
139 | # Default: unset (auto)
140 | # Valid: 0-9
141 | PF_COL2=9
142 |
143 | # Color of title data:
144 | # Default: unset (auto)
145 | # Valid: 0-9, COL1 (copies COL1 value)
146 | PF_COL3=1
147 |
148 | # Alignment paddings (this is different to the original version).
149 | # Default: unset (auto)
150 | # Valid: int
151 | PF_PAD1=""
152 | PF_PAD2=""
153 | PF_PAD3=""
154 |
155 | # Which ascii art to use.
156 | # Default: unset (auto)
157 | # Valid: string
158 | PF_ASCII="openbsd"
159 |
160 | # The below environment variables control more
161 | # than just 'pfetch' and can be passed using
162 | # 'HOSTNAME=cool_pc pfetch' to restrict their
163 | # usage solely to 'pfetch'.
164 |
165 | # Which user to display.
166 | USER=""
167 |
168 | # Which hostname to display.
169 | HOSTNAME=""
170 |
171 | # Skip zypper package count if only slow method is available
172 | PF_FAST_PKG_COUNT=1
173 | ```
174 |
175 | A file containing custom pfetch logos could look like this (also found under
176 | `custom_logos_example`). This will turn the Arch Linux logo red, the Debian Logo
177 | blue and the Fedora logo yellow:
178 |
179 | ```
180 | [Aa]rch*)
181 | read_ascii 1 <<- EOF
182 | ${c1} /\\
183 | ${c1} / \\
184 | ${c1} /\\ \\
185 | ${c1} / \\
186 | ${c1} / ,, \\
187 | ${c1} / | | -\\
188 | ${c1} /_-'' ''-_\\
189 | EOF
190 | ;;
191 | [Dd]ebian*)
192 | read_ascii 4 <<- EOF
193 | ${c4} _____
194 | ${c4} / __ \\
195 | ${c4}| / |
196 | ${c4}| \\___-
197 | ${c4}-_
198 | ${c4} --_
199 | EOF
200 | ;;
201 | [Ff]edora*)
202 | read_ascii 3 <<- EOF
203 | ${c3},'''''.
204 | ${c3}| ,. |
205 | ${c3}| | '_'
206 | ${c3} ,....| |..
207 | ${c3}.' ,_;| ..'
208 | ${c3}| | | |
209 | ${c3}| ',_,' |
210 | ${c3} '. ,'
211 | ${c3}'''''
212 | EOF
213 | ```
214 |
215 | _Note: Make sure to use tabs for indentation and separate logos with `;;`, as
216 | seen above. You only need to add the logos you want to overwrite/add, the
217 | default logos will stay available. The included logos can be found at
218 | `./pfetch-extractor/logos.sh`._
219 |
--------------------------------------------------------------------------------
/assets/logos/bedrock.svg:
--------------------------------------------------------------------------------
1 |
167 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | use libmacchina::{
2 | traits::GeneralReadout as _, traits::KernelReadout as _, traits::MemoryReadout as _,
3 | traits::PackageReadout as _, GeneralReadout, KernelReadout, MemoryReadout, PackageReadout,
4 | };
5 | use pfetch_logo_parser::{Color, Logo, LogoPart};
6 | use std::{env, fmt::Display, str::FromStr};
7 | use unicode_width::UnicodeWidthStr;
8 |
9 | #[derive(Debug, PartialEq)]
10 | enum PfetchInfo {
11 | Ascii,
12 | Title,
13 | Os,
14 | Host,
15 | Kernel,
16 | Uptime,
17 | Pkgs,
18 | Cpu,
19 | Memory,
20 | Shell,
21 | Editor,
22 | Wm,
23 | De,
24 | Palette,
25 | BlankLine,
26 | }
27 |
28 | impl Display for PfetchInfo {
29 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 | write!(f, "{}", format!("{self:?}").to_lowercase())
31 | }
32 | }
33 |
34 | impl FromStr for PfetchInfo {
35 | type Err = String;
36 |
37 | fn from_str(info: &str) -> Result {
38 | match info {
39 | "ascii" => Ok(PfetchInfo::Ascii),
40 | "title" => Ok(PfetchInfo::Title),
41 | "os" => Ok(PfetchInfo::Os),
42 | "host" => Ok(PfetchInfo::Host),
43 | "kernel" => Ok(PfetchInfo::Kernel),
44 | "uptime" => Ok(PfetchInfo::Uptime),
45 | "pkgs" => Ok(PfetchInfo::Pkgs),
46 | "cpu" => Ok(PfetchInfo::Cpu),
47 | "memory" => Ok(PfetchInfo::Memory),
48 | "shell" => Ok(PfetchInfo::Shell),
49 | "editor" => Ok(PfetchInfo::Editor),
50 | "wm" => Ok(PfetchInfo::Wm),
51 | "de" => Ok(PfetchInfo::De),
52 | "palette" => Ok(PfetchInfo::Palette),
53 | unknown_info => Err(format!("Unknown pfetch info: {unknown_info}")),
54 | }
55 | }
56 | }
57 |
58 | fn pfetch(info: Vec<(Color, String, String)>, logo: Logo, logo_enabled: bool) {
59 | let raw_logo = if logo_enabled {
60 | logo.logo_parts
61 | .iter()
62 | .map(|LogoPart { content, .. }| content.as_ref())
63 | .collect::()
64 | } else {
65 | "".to_string()
66 | };
67 | let color_enabled = dotenvy::var("PF_COLOR").unwrap_or_default() != "0";
68 | let logo = if color_enabled {
69 | logo.to_string()
70 | } else {
71 | format!("{:#}", logo)
72 | };
73 | let mut logo_lines = logo.lines();
74 | let raw_logo_lines: Vec<_> = raw_logo.lines().collect();
75 | let logo_width = raw_logo_lines
76 | .iter()
77 | .map(|line| line.width())
78 | .max()
79 | .unwrap_or(0);
80 | let line_amount = usize::max(raw_logo_lines.len(), info.len());
81 |
82 | let info1_width = info
83 | .iter()
84 | .skip(1)
85 | .map(|(_, line, _)| {
86 | if line.starts_with("\x1b[4") {
87 | // exclude palette from info1 width
88 | 0
89 | } else {
90 | line.len()
91 | }
92 | })
93 | .max()
94 | .unwrap_or(0);
95 |
96 | let padding1 = match dotenvy::var("PF_PAD1") {
97 | Ok(padding0) => padding0.parse::().unwrap_or(0),
98 | Err(_) => 0,
99 | };
100 | let padding2 = match dotenvy::var("PF_PAD2") {
101 | Ok(padding1) => padding1.parse::().unwrap_or(0),
102 | Err(_) => 3,
103 | };
104 | let padding3 = match dotenvy::var("PF_PAD3") {
105 | Ok(padding2) => padding2.parse::().unwrap_or(0),
106 | Err(_) => 1,
107 | };
108 |
109 | let mut pfetch_str = String::new();
110 |
111 | for l in 0..line_amount {
112 | pfetch_str += &format!(
113 | "{padding1}{bold}{logo}{padding2}{color}{info1}{nobold}{separator}{padding3}{color2}{info2}\n",
114 | padding1 = " ".repeat(padding1),
115 | bold = if color_enabled {"\x1b[1m"} else {""},
116 | logo = if logo_enabled {
117 | logo_lines.next().unwrap_or("")
118 | } else {
119 | ""
120 | },
121 | padding2 = " ".repeat(
122 | logo_width - raw_logo_lines.get(l).map_or(0, |line| line.width())
123 | + if logo_enabled { padding2 } else { 0 }
124 | ),
125 | color = if color_enabled {info.get(l).map_or("".to_owned(), |line| line.0.to_string())} else {"".to_string()},
126 | info1 = info.get(l).map_or("", |line| &line.1),
127 | nobold = if color_enabled {"\x1b[0m"} else {""},
128 | separator = info.get(l).map_or("".to_string(), |line|
129 | if ! &line.2.is_empty() {
130 | dotenvy::var("PF_SEP").unwrap_or_default()
131 | } else { "".to_string() }
132 | ),
133 | padding3 = " ".repeat(
134 | info1_width.saturating_sub(info.get(l).map_or(0, |(_, line, _)| line.len()))
135 | + padding3
136 | ),
137 | color2 = if color_enabled {match dotenvy::var("PF_COL2") {
138 | Ok(newcolor) => {
139 | match Color::from_str(&newcolor) {
140 | Ok(newcolor) => format!("{newcolor}"),
141 | Err(_) => "".to_string(),
142 | }
143 | },
144 | Err(_) => "".to_string()
145 | }} else {"".to_string()},
146 | info2 = info.get(l).map_or("", |line| &line.2)
147 | )
148 | }
149 |
150 | // if colors are disabled, remove them from string
151 | // if dotenvy::var("PF_COLOR").unwrap_or_default() == "0" {
152 | // pfetch_str = pfetch_str
153 | // .split("\x1b[")
154 | // .map(|chunk| chunk.chars().skip(3).collect::())
155 | // .collect();
156 | // }
157 |
158 | // disable line wrap
159 | crossterm::execute!(std::io::stdout(), crossterm::terminal::DisableLineWrap)
160 | .unwrap_or_default();
161 |
162 | println!("{pfetch_str}");
163 |
164 | // enable line wrap
165 | crossterm::execute!(std::io::stdout(), crossterm::terminal::EnableLineWrap).unwrap_or_default();
166 | }
167 |
168 | struct Readouts {
169 | general_readout: GeneralReadout,
170 | package_readout: PackageReadout,
171 | memory_readout: MemoryReadout,
172 | kernel_readout: KernelReadout,
173 | }
174 |
175 | fn get_info(
176 | info: &PfetchInfo,
177 | readouts: &Readouts,
178 | skip_slow_package_managers: bool,
179 | ) -> Option {
180 | match info {
181 | PfetchInfo::Ascii => None,
182 | PfetchInfo::Title => pfetch::user_at_hostname(
183 | &readouts.general_readout,
184 | &dotenvy::var("USER").ok(),
185 | &dotenvy::var("HOSTNAME").ok(),
186 | ),
187 | PfetchInfo::Os => pfetch::os(&readouts.general_readout),
188 | PfetchInfo::Host => pfetch::host(&readouts.general_readout),
189 | PfetchInfo::Kernel => pfetch::kernel(&readouts.kernel_readout),
190 | PfetchInfo::Uptime => pfetch::uptime(&readouts.general_readout),
191 | PfetchInfo::Pkgs => Some(
192 | pfetch::total_packages(&readouts.package_readout, skip_slow_package_managers)
193 | .to_string(),
194 | ),
195 | PfetchInfo::Cpu => pfetch::cpu(&readouts.general_readout),
196 | PfetchInfo::Memory => pfetch::memory(&readouts.memory_readout),
197 | PfetchInfo::Shell => pfetch::shell(&readouts.general_readout),
198 | PfetchInfo::Editor => pfetch::editor(),
199 | PfetchInfo::Wm => pfetch::wm(&readouts.general_readout),
200 | PfetchInfo::De => pfetch::de(&readouts.general_readout),
201 | PfetchInfo::Palette => Some(pfetch::palette()),
202 | PfetchInfo::BlankLine => Some("".to_string()),
203 | }
204 | }
205 |
206 | fn main() {
207 | // parse arguements
208 | if std::env::args().any(|arg| arg.starts_with("-v") || arg.starts_with("--v")) {
209 | println!("pfetch-rs {}", env!("CARGO_PKG_VERSION"));
210 | std::process::exit(0);
211 | } else if std::env::args().len() > 1 {
212 | println!("pfetch show system information");
213 | println!("pfetch -v show version");
214 | std::process::exit(0);
215 | }
216 |
217 | // source file specified by env: PF_SOURCE
218 | if let Ok(filepath) = dotenvy::var("PF_SOURCE") {
219 | let _ = dotenvy::from_path(filepath);
220 | }
221 | // Check if SKIP_SLOW is enabled
222 | let skip_slow_package_managers = dotenvy::var("PF_FAST_PKG_COUNT").is_ok();
223 |
224 | let enabled_pf_info_base: Vec = match dotenvy::var("PF_INFO") {
225 | Ok(pfetch_infos) => pfetch_infos
226 | .trim()
227 | .split(' ')
228 | .map(PfetchInfo::from_str)
229 | .filter_map(|i| i.ok())
230 | .collect(),
231 | Err(_) => vec![
232 | PfetchInfo::Ascii,
233 | PfetchInfo::Title,
234 | PfetchInfo::Os,
235 | PfetchInfo::Host,
236 | PfetchInfo::Kernel,
237 | PfetchInfo::Uptime,
238 | PfetchInfo::Pkgs,
239 | PfetchInfo::Memory,
240 | ],
241 | };
242 |
243 | // insert blank lines before and after palettes
244 | let mut enabled_pf_info: Vec = vec![];
245 | let mut ascii_enabled: bool = false;
246 | for info in enabled_pf_info_base {
247 | match info {
248 | PfetchInfo::Palette => {
249 | enabled_pf_info.push(PfetchInfo::BlankLine);
250 | enabled_pf_info.push(PfetchInfo::Palette);
251 | enabled_pf_info.push(PfetchInfo::BlankLine);
252 | }
253 | PfetchInfo::Ascii => {
254 | ascii_enabled = true;
255 | }
256 | i => enabled_pf_info.push(i),
257 | }
258 | }
259 |
260 | let readouts = Readouts {
261 | general_readout: GeneralReadout::new(),
262 | package_readout: PackageReadout::new(),
263 | memory_readout: MemoryReadout::new(),
264 | kernel_readout: KernelReadout::new(),
265 | };
266 |
267 | let os = pfetch::os(&GeneralReadout::new()).unwrap_or_default();
268 |
269 | let logo_override = env::var("PF_ASCII");
270 |
271 | let logo_name = logo_override.unwrap_or(match env::consts::OS {
272 | "linux" => os.clone(),
273 | other => other.to_owned(),
274 | });
275 |
276 | let mut logo = pfetch::logo(&logo_name);
277 |
278 | // color overrides
279 | if let Ok(newcolor) = dotenvy::var("PF_COL1") {
280 | if let Ok(newcolor) = Color::from_str(&newcolor) {
281 | logo.primary_color = newcolor;
282 | }
283 | }
284 |
285 | if let Ok(newcolor) = dotenvy::var("PF_COL3") {
286 | if newcolor == "COL1" {
287 | logo.secondary_color = logo.primary_color;
288 | } else if let Ok(newcolor) = Color::from_str(&newcolor) {
289 | logo.secondary_color = newcolor;
290 | }
291 | }
292 |
293 | let gathered_pfetch_info: Vec<(Color, String, String)> = enabled_pf_info
294 | .iter()
295 | .filter_map(|info| match info {
296 | PfetchInfo::Os => Some((logo.primary_color, info.to_string(), os.clone())),
297 | _ => get_info(info, &readouts, skip_slow_package_managers).map(|info_str| match info {
298 | PfetchInfo::Title => (logo.secondary_color, info_str, "".to_string()),
299 | PfetchInfo::BlankLine => (logo.primary_color, "".to_string(), "".to_string()),
300 | PfetchInfo::Palette => (logo.primary_color, info_str, "".to_string()),
301 | _ => (logo.primary_color, info.to_string(), info_str),
302 | }),
303 | })
304 | .collect();
305 |
306 | pfetch(gathered_pfetch_info, logo, ascii_enabled);
307 | }
308 |
--------------------------------------------------------------------------------
/assets/logos/buildroot.svg:
--------------------------------------------------------------------------------
1 |
221 |
--------------------------------------------------------------------------------
/assets/logos/debian.svg:
--------------------------------------------------------------------------------
1 |
243 |
--------------------------------------------------------------------------------
/assets/logos/parabola.svg:
--------------------------------------------------------------------------------
1 |
245 |
--------------------------------------------------------------------------------
/assets/logos/ubuntu.svg:
--------------------------------------------------------------------------------
1 |
252 |
--------------------------------------------------------------------------------
/assets/logos/mageia.svg:
--------------------------------------------------------------------------------
1 |
265 |
--------------------------------------------------------------------------------
/assets/logos/irix.svg:
--------------------------------------------------------------------------------
1 |
271 |
--------------------------------------------------------------------------------
/assets/logos/xeonix.svg:
--------------------------------------------------------------------------------
1 |
253 |
--------------------------------------------------------------------------------
/assets/logos/solaris.svg:
--------------------------------------------------------------------------------
1 |
218 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | use std::{collections::VecDeque, env, fs, io::Result, process::Command};
2 |
3 | use glob::glob;
4 | use globset::Glob;
5 | use libmacchina::{
6 | traits::GeneralReadout as _, traits::KernelReadout as _, traits::MemoryReadout as _,
7 | traits::PackageReadout as _, GeneralReadout, KernelReadout, MemoryReadout, PackageReadout,
8 | };
9 | use pfetch_logo_parser::{parse_logo, Logo};
10 |
11 | #[derive(Debug)]
12 | pub enum PackageManager {
13 | Pacman,
14 | Dpkg,
15 | Xbps,
16 | Apk,
17 | Rpm,
18 | Flatpak,
19 | Crux,
20 | Guix,
21 | Opkg,
22 | Kiss,
23 | Portage,
24 | Pkgtool,
25 | Nix,
26 | }
27 |
28 | /// Obtain the amount of installed packages on the system by checking all installed supported package
29 | /// managers and adding the amounts
30 | pub fn total_packages(package_readout: &PackageReadout, skip_slow: bool) -> usize {
31 | match env::consts::OS {
32 | "linux" => {
33 | let macchina_package_count: Vec<(String, usize)> = package_readout
34 | .count_pkgs()
35 | .iter()
36 | .map(|(macchina_manager, count)| (macchina_manager.to_string(), *count))
37 | .collect();
38 | [
39 | PackageManager::Pacman,
40 | PackageManager::Dpkg,
41 | PackageManager::Xbps,
42 | PackageManager::Apk,
43 | PackageManager::Rpm,
44 | PackageManager::Flatpak,
45 | PackageManager::Crux,
46 | PackageManager::Guix,
47 | PackageManager::Opkg,
48 | PackageManager::Kiss,
49 | PackageManager::Portage,
50 | PackageManager::Pkgtool,
51 | PackageManager::Nix,
52 | ]
53 | .iter()
54 | .map(|mngr| packages(mngr, &macchina_package_count, skip_slow))
55 | .sum()
56 | }
57 | _ => package_readout.count_pkgs().iter().map(|elem| elem.1).sum(),
58 | }
59 | }
60 |
61 | fn get_macchina_package_count(
62 | macchina_result: &[(String, usize)],
63 | package_manager_name: &str,
64 | ) -> Option {
65 | macchina_result
66 | .iter()
67 | .find(|entry| entry.0 == package_manager_name)
68 | .map(|entry| entry.1)
69 | }
70 |
71 | /// return the amount of packages installed with a given linux package manager
72 | /// Return `0` if the package manager is not installed
73 | fn packages(
74 | pkg_manager: &PackageManager,
75 | macchina_package_count: &[(String, usize)],
76 | skip_slow: bool,
77 | ) -> usize {
78 | match pkg_manager {
79 | // libmacchina has very fast implementations for most package managers, so we use them
80 | // where we can, otherwise we fall back to method used by dylans version of pfetch
81 | PackageManager::Pacman
82 | | PackageManager::Flatpak
83 | | PackageManager::Dpkg
84 | | PackageManager::Xbps
85 | | PackageManager::Apk
86 | | PackageManager::Portage
87 | | PackageManager::Nix
88 | | PackageManager::Opkg => get_macchina_package_count(
89 | macchina_package_count,
90 | &format!("{pkg_manager:?}").to_lowercase(),
91 | )
92 | .unwrap_or(0),
93 | PackageManager::Rpm => match get_macchina_package_count(
94 | macchina_package_count,
95 | &format!("{pkg_manager:?}").to_lowercase(),
96 | ) {
97 | Some(count) => count,
98 | None => {
99 | if !skip_slow {
100 | run_and_count_lines("rpm", &["-qa"])
101 | } else {
102 | 0
103 | }
104 | }
105 | },
106 | PackageManager::Guix => run_and_count_lines("guix", &["package", "--list-installed"]),
107 | PackageManager::Crux => {
108 | if check_if_command_exists("crux") {
109 | run_and_count_lines("pkginfo", &["-i"])
110 | } else {
111 | 0
112 | }
113 | }
114 | PackageManager::Kiss => {
115 | if check_if_command_exists("kiss") {
116 | match glob("/var/db/kiss/installed/*/") {
117 | Ok(files) => files.count(),
118 | Err(_) => 0,
119 | }
120 | } else {
121 | 0
122 | }
123 | }
124 | PackageManager::Pkgtool => {
125 | if check_if_command_exists("pkgtool") {
126 | match glob("/var/log/packages/*") {
127 | Ok(files) => files.count(),
128 | Err(_) => 0,
129 | }
130 | } else {
131 | 0
132 | }
133 | }
134 | }
135 | }
136 |
137 | pub fn user_at_hostname(
138 | general_readout: &GeneralReadout,
139 | username_override: &Option,
140 | hostname_override: &Option,
141 | ) -> Option {
142 | let username = match username_override {
143 | Some(username) => Ok(username.to_string()),
144 | None => general_readout.username(),
145 | };
146 | let hostname = match hostname_override {
147 | Some(hostname) => Ok(hostname.to_string()),
148 | None => general_readout.hostname(),
149 | };
150 | if username.is_err() || hostname.is_err() {
151 | None
152 | } else {
153 | Some(format!(
154 | "{}@{}",
155 | username.unwrap_or_default(),
156 | hostname.unwrap_or_default()
157 | ))
158 | }
159 | }
160 |
161 | pub fn memory(memory_readout: &MemoryReadout) -> Option {
162 | let total_memory = memory_readout.total();
163 | let used_memory = memory_readout.used();
164 | if total_memory.is_err() || used_memory.is_err() {
165 | None
166 | } else {
167 | Some(format!(
168 | "{}M / {}M",
169 | used_memory.unwrap() / 1024,
170 | total_memory.unwrap() / 1024
171 | ))
172 | }
173 | }
174 |
175 | pub fn cpu(general_readout: &GeneralReadout) -> Option {
176 | general_readout.cpu_model_name().ok()
177 | }
178 |
179 | pub fn os(general_readout: &GeneralReadout) -> Option {
180 | match env::consts::OS {
181 | "linux" => {
182 | // check for Bedrock Linux
183 | if dotenvy::var("PATH")
184 | .unwrap_or_default()
185 | .contains("/bedrock/cross/")
186 | {
187 | return Some("Bedrock Linux".to_string());
188 | }
189 | let content = os_release::OsRelease::new().ok()?;
190 | let version = if !content.version.is_empty() {
191 | content.version
192 | } else {
193 | content.version_id
194 | };
195 | // check for Bazzite
196 | if content.pretty_name.contains("Bazzite") {
197 | return Some(format!("Bazzite {version}"));
198 | }
199 | if !version.is_empty() {
200 | return Some(format!("{} {}", content.name, version));
201 | }
202 | Some(content.name)
203 | }
204 | _ => Some(general_readout.os_name().ok()?.replace("Unknown", "")),
205 | }
206 | }
207 |
208 | pub fn kernel(kernel_readout: &KernelReadout) -> Option {
209 | kernel_readout.os_release().ok()
210 | }
211 |
212 | pub fn seconds_to_string(seconds: usize) -> String {
213 | let days = seconds / 86400;
214 | let hours = (seconds % 86400) / 3600;
215 | let minutes = (seconds % 3600) / 60;
216 |
217 | let mut result = String::with_capacity(10);
218 |
219 | if days > 0 {
220 | result.push_str(&format!("{}d", days));
221 | }
222 | if hours > 0 {
223 | if !result.is_empty() {
224 | result.push(' ');
225 | }
226 | result.push_str(&format!("{}h", hours));
227 | }
228 | if minutes > 0 || result.is_empty() {
229 | if !result.is_empty() {
230 | result.push(' ');
231 | }
232 | result.push_str(&format!("{}m", minutes));
233 | }
234 |
235 | result
236 | }
237 |
238 | pub fn uptime(general_readout: &GeneralReadout) -> Option {
239 | Some(seconds_to_string(general_readout.uptime().ok()?))
240 | }
241 |
242 | pub fn host(general_readout: &GeneralReadout) -> Option {
243 | match env::consts::OS {
244 | "linux" => {
245 | const BLACKLIST: &[&str] = &[
246 | "To",
247 | "Be",
248 | "be",
249 | "Filled",
250 | "filled",
251 | "By",
252 | "by",
253 | "O.E.M.",
254 | "OEM",
255 | "Not",
256 | "Applicable",
257 | "Specified",
258 | "System",
259 | "Product",
260 | "Name",
261 | "Version",
262 | "Undefined",
263 | "Default",
264 | "string",
265 | "INVALID",
266 | "�",
267 | "os",
268 | "Type1ProductConfigId",
269 | "",
270 | ];
271 |
272 | // get device from system files
273 | let product_name =
274 | fs::read_to_string("/sys/devices/virtual/dmi/id/product_name").unwrap_or_default();
275 | let product_name = product_name.trim();
276 | let product_version = fs::read_to_string("/sys/devices/virtual/dmi/id/product_version")
277 | .unwrap_or_default();
278 | let product_version = product_version.trim();
279 | let product_model =
280 | fs::read_to_string("/sys/firmware/devicetree/base/model").unwrap_or_default();
281 | let product_model = product_model.trim();
282 |
283 | let final_str = format!("{product_name} {product_version} {product_model}")
284 | .split(' ')
285 | .filter(|word| !BLACKLIST.contains(word))
286 | .collect::>()
287 | .join(" ");
288 |
289 | // if string is empty, display system architecture instead
290 | let final_str = if final_str.is_empty() {
291 | run_system_command("uname", &["-m"]).unwrap_or("Unknown".to_owned())
292 | } else {
293 | final_str
294 | };
295 | if final_str.is_empty() {
296 | None
297 | } else {
298 | Some(final_str)
299 | }
300 | }
301 | // on non-linux systems, try general_readout.machine(), use cpu model name as fallback
302 | _ => general_readout
303 | .machine()
304 | .ok()
305 | .or_else(|| general_readout.cpu_model_name().ok()),
306 | }
307 | }
308 |
309 | fn parse_custom_logos(filename: &str) -> Vec