leenfetch_core/modules/linux/
packages.rs1use std::{fs, process::Command};
2
3use crate::modules::enums::PackageShorthand;
4
5pub fn get_packages(shorthand: PackageShorthand) -> Option<String> {
6 let mut packages = 0u64;
7 let mut managers = vec![];
8 let mut manager_string = vec![];
9
10 if is_installed("dpkg") {
11 if let Some(count) = count_dpkg_packages() {
12 packages += count;
13 managers.push(format!("{} ({})", count, "dpkg"));
14 manager_string.push("dpkg");
15 }
16 }
17
18 if is_installed("pacman") {
19 if let Some(count) = count_pacman_packages() {
20 packages += count;
21 managers.push(format!("{} ({})", count, "pacman"));
22 manager_string.push("pacman");
23 }
24 }
25
26 if is_installed("rpm") {
27 if let Some(count) = count_via_shell("rpm -qa | wc -l") {
28 packages += count;
29 managers.push(format!("{} ({})", count, "rpm"));
30 manager_string.push("rpm");
31 }
32 }
33
34 if is_installed("flatpak") {
35 if let Some(count) = count_via_shell("flatpak list --app --columns=application | wc -l") {
36 packages += count;
37 managers.push(format!("{} ({})", count, "flatpak"));
38 manager_string.push("flatpak");
39 }
40 }
41
42 if is_installed("snap") && is_snapd_running() {
43 if let Some(count) = count_via_shell("snap list | tail -n +2 | wc -l") {
44 packages += count;
45 managers.push(format!("{} ({})", count, "snap"));
46 manager_string.push("snap");
47 }
48 }
49
50 if packages == 0 {
51 return None;
52 }
53
54 match shorthand {
55 PackageShorthand::Off => Some(format!("{} total", packages)),
56 PackageShorthand::On => Some(managers.join(", ")),
57 PackageShorthand::Tiny => Some(format!("{} ({})", packages, manager_string.join(", "))),
58 }
59}
60
61fn is_installed(cmd: &str) -> bool {
62 Command::new("which")
63 .arg(cmd)
64 .output()
65 .map(|o| o.status.success())
66 .unwrap_or(false)
67}
68
69fn is_snapd_running() -> bool {
70 Command::new("ps")
71 .args(["-eo", "comm="])
72 .output()
73 .map(|o| {
74 String::from_utf8_lossy(&o.stdout)
75 .lines()
76 .any(|line| line.trim().eq_ignore_ascii_case("snapd"))
77 })
78 .unwrap_or(false)
79}
80
81fn count_dpkg_packages() -> Option<u64> {
82 let status = fs::read_to_string("/var/lib/dpkg/status").ok()?;
83 let count = status
84 .lines()
85 .filter(|line| line.starts_with("Package: "))
86 .count() as u64;
87 Some(count)
88}
89
90fn count_pacman_packages() -> Option<u64> {
91 let entries = fs::read_dir("/var/lib/pacman/local").ok()?;
92 let count = entries
93 .filter_map(|entry| entry.ok())
94 .filter(|entry| entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false))
95 .count() as u64;
96 Some(count)
97}
98
99fn count_via_shell(command: &str) -> Option<u64> {
100 let output = Command::new("sh").arg("-c").arg(command).output().ok()?;
101 if !output.status.success() {
102 return None;
103 }
104 let stdout = String::from_utf8_lossy(&output.stdout);
105 stdout
106 .trim()
107 .split_whitespace()
108 .last()
109 .and_then(|s| s.parse::<u64>().ok())
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use crate::test_utils::EnvLock;
116
117 #[test]
118 fn returns_none_when_no_managers_found() {
119 let env_lock = EnvLock::acquire(&["PATH"]);
120 env_lock.set_var("PATH", "/nonexistent");
121 let result = get_packages(PackageShorthand::Off);
122 assert!(result.is_none());
123 drop(env_lock);
124 }
125}