1use std::collections::HashSet;
2use std::fmt::Display;
3
4#[allow(non_camel_case_types)]
5#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6pub enum DistroId {
8 Arch,
14
15 Debian,
21
22 Ubuntu,
28
29 Mint,
35
36 RHEL,
42
43 Fedora,
49
50 OpenSUSE,
58
59 Gentoo,
65
66 NixOS,
70
71 Other(String)
73}
74
75impl DistroId {
76 pub fn list_similar(&self) -> Vec<Self> {
79 match self {
80 Self::Arch => vec![
81 Self::Arch
82 ],
83
84 Self::Debian => vec![
85 Self::Debian,
86 Self::Ubuntu,
87 Self::Mint
88 ],
89
90 Self::Ubuntu => vec![
91 Self::Ubuntu,
92 Self::Debian,
93 Self::Mint
94 ],
95
96 Self::Mint => vec![
97 Self::Mint,
98 Self::Debian,
99 Self::Ubuntu
100 ],
101
102 Self::RHEL => vec![
103 Self::RHEL,
104 Self::Fedora,
105 Self::OpenSUSE
106 ],
107
108 Self::Fedora => vec![
109 Self::Fedora,
110 Self::RHEL,
111 Self::OpenSUSE
112 ],
113
114 Self::OpenSUSE => vec![
115 Self::OpenSUSE,
116 Self::Fedora,
117 Self::RHEL
118 ],
119
120 Self::Gentoo => vec![
121 Self::Gentoo
122 ],
123
124 Self::NixOS => vec![
125 Self::NixOS
126 ],
127
128 Self::Other(id) => vec![
129 Self::Other(id.clone())
130 ]
131 }
132 }
133
134 #[inline]
135 pub fn is_similar<T: Into<Self>>(&self, other: T) -> bool {
137 self.list_similar().contains(&other.into())
138 }
139}
140
141impl<T> From<T> for DistroId where T: AsRef<str> {
142 fn from(str: T) -> Self {
143 match str.as_ref() {
144 "arch" => Self::Arch,
145 "debian" => Self::Debian,
146 "ubuntu" => Self::Ubuntu,
147
148 "mint" => Self::Mint,
149 "linuxmint" => Self::Mint,
150
151 "rhel" => Self::RHEL,
152 "fedora" => Self::Fedora,
153
154 "suse" => Self::OpenSUSE,
155 "opensuse" => Self::OpenSUSE,
156 "opensuse_tumbleweed" => Self::OpenSUSE,
157
158 "gentoo" => Self::Gentoo,
159 "nixos" => Self::NixOS,
160
161 id => Self::Other(id.to_string())
162 }
163 }
164}
165
166impl Display for DistroId {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 match self {
169 Self::Arch => write!(f, "arch"),
170 Self::Debian => write!(f, "debian"),
171 Self::Ubuntu => write!(f, "ubuntu"),
172 Self::Mint => write!(f, "linuxmint"),
173 Self::RHEL => write!(f, "rhel"),
174 Self::Fedora => write!(f, "fedora"),
175 Self::OpenSUSE => write!(f, "opensuse"),
176 Self::Gentoo => write!(f, "gentoo"),
177 Self::NixOS => write!(f, "nixos"),
178
179 Self::Other(id) => write!(f, "{id}")
180 }
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub struct Distro {
186 name: String,
187 id: DistroId,
188 similar_ids: HashSet<DistroId>
189}
190
191impl Distro {
192 #[inline]
193 pub fn current() -> Option<Self> {
195 identify()
196 }
197
198 #[inline]
199 pub fn name(&self) -> &str {
201 &self.name
202 }
203
204 #[inline]
205 pub fn id(&self) -> &DistroId {
207 &self.id
208 }
209
210 #[inline]
211 pub fn similar_ids(&self) -> &HashSet<DistroId> {
220 &self.similar_ids
221 }
222
223 #[inline]
224 pub fn is_similar<T: Into<DistroId>>(&self, other: T) -> bool {
234 let other = other.into();
235
236 self.similar_ids.contains(&other) || self.id.is_similar(other)
237 }
238}
239
240pub fn identify() -> Option<Distro> {
249 let mut id: Option<DistroId> = None;
250 let mut name: Option<String> = None;
251 let mut similar_ids: Option<HashSet<DistroId>> = None;
252
253 if let Ok(release) = std::fs::read_to_string("/etc/os-release") {
254 for line in release.lines() {
255 if let Some(distro_id) = line.strip_prefix("ID=") {
256 id = Some(distro_id.into());
257 }
258
259 else if let Some(distro_name) = line.strip_prefix("NAME=") {
260 name = Some(distro_name.to_string());
261 }
262
263 else if let Some(ids) = line.strip_prefix("ID_LIKE=") {
264 similar_ids = Some(ids.split_whitespace().map(|id| id.into()).collect());
265 }
266 }
267
268 let Some(id) = id else {
269 return None;
270 };
271
272 let Some(name) = name else {
274 return None;
275 };
276
277 Some(Distro {
278 id,
279 name,
280 similar_ids: similar_ids.unwrap_or_default()
281 })
282 }
283
284 else {
285 None
286 }
287}