1use crate::error::Error;
2use crate::pm_vendor::*;
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
8#[cfg_attr(feature = "schematic", derive(schematic::Schematic))]
9#[serde(rename_all = "kebab-case")]
10pub enum SystemPackageManager {
11 Pkg,
13 Pkgin,
14
15 Apk,
17 Apt,
18 Dnf,
19 Pacman,
20 Yum,
21
22 #[serde(alias = "homebrew")]
24 Brew,
25
26 #[serde(alias = "chocolatey")]
28 Choco,
29 Scoop,
30
31 #[serde(alias = "*")]
33 All,
34}
35
36impl SystemPackageManager {
37 pub fn detect() -> Result<Self, Error> {
44 #[cfg(target_os = "linux")]
45 {
46 let release = std::fs::read_to_string("/etc/os-release").unwrap_or_default();
47
48 if let Some(id) = release.lines().find(|l| l.starts_with("ID=")) {
49 return match id[3..].trim_matches('"') {
50 "debian" | "ubuntu" | "pop-os" | "deepin" | "elementary OS" | "kali"
51 | "linuxmint" => Ok(SystemPackageManager::Apt),
52 "arch" | "manjaro" => Ok(SystemPackageManager::Pacman),
53 "centos" | "redhat" | "rhel" => Ok(SystemPackageManager::Yum),
54 "fedora" => Ok(SystemPackageManager::Dnf),
55 "alpine" => Ok(SystemPackageManager::Apk),
56 name => Err(Error::UnknownPackageManager(name.to_owned())),
57 };
58 }
59 }
60
61 #[cfg(any(
62 target_os = "dragonfly",
63 target_os = "freebsd",
64 target_os = "netbsd",
65 target_os = "openbsd"
66 ))]
67 {
68 use crate::is_command_on_path;
69
70 if is_command_on_path("pkg") {
71 return Ok(SystemPackageManager::Pkg);
72 }
73
74 if is_command_on_path("pkgin") {
75 return Ok(SystemPackageManager::Pkgin);
76 }
77 }
78
79 #[cfg(target_os = "macos")]
80 {
81 use crate::is_command_on_path;
82
83 if is_command_on_path("brew") {
84 return Ok(SystemPackageManager::Brew);
85 }
86 }
87
88 #[cfg(target_os = "windows")]
89 {
90 use crate::is_command_on_path;
91
92 if is_command_on_path("choco") {
93 return Ok(SystemPackageManager::Choco);
94 }
95
96 if is_command_on_path("scoop") {
97 return Ok(SystemPackageManager::Scoop);
98 }
99 }
100
101 Err(Error::MissingPackageManager)
102 }
103
104 pub fn get_config(&self) -> PackageManagerConfig {
106 match self {
107 Self::Apk => apk(),
108 Self::Apt => apt(),
109 Self::Dnf => dnf(),
110 Self::Pacman => pacman(),
111 Self::Pkg => pkg(),
112 Self::Pkgin => pkgin(),
113 Self::Yum => yum(),
114 Self::Brew => brew(),
115 Self::Choco => choco(),
116 Self::Scoop => scoop(),
117 Self::All => unreachable!(),
118 }
119 }
120
121 pub fn get_elevated_command(&self) -> Option<&str> {
124 if matches!(self, Self::Brew | Self::All) {
126 return None;
127 }
128
129 #[cfg(unix)]
130 {
131 use crate::is_command_on_path;
132
133 if is_command_on_path("doas") {
134 Some("doas")
135 } else if is_command_on_path("sudo") {
136 Some("sudo")
137 } else {
138 None
139 }
140 }
141
142 #[cfg(any(windows, target_arch = "wasm32"))]
143 None
144 }
145}
146
147impl fmt::Display for SystemPackageManager {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 write!(f, "{}", format!("{self:?}").to_lowercase())
150 }
151}