leenfetch_core/modules/linux/
packages.rs

1use 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}