1use eyre::{eyre, Result};
2use strum::{EnumIter, IntoEnumIterator};
3use std::{ffi::OsString, fmt::Display, process::Command};
4
5
6#[derive(Debug, Clone, Copy, EnumIter, PartialEq)]
7pub enum Vendor {
8 #[cfg(target_os = "linux")]
9 Apt,
10 #[cfg(target_os = "linux")]
11 Yay,
12 #[cfg(target_os = "linux")]
13 Yum,
14 #[cfg(target_os = "linux")]
15 Pacman,
16 #[cfg(target_os = "linux")]
17 Apk,
18 #[cfg(target_os = "linux")]
19 Emerge,
20 #[cfg(target_os = "linux")]
21 Guix,
22 #[cfg(target_os = "linux")]
23 NixEnv,
24 #[cfg(target_os = "linux")]
25 Slackpkg,
26 #[cfg(target_os = "linux")]
27 Cards,
28 #[cfg(target_os = "linux")]
29 Dnf,
30 #[cfg(target_os = "linux")]
31 Eopkg,
32 #[cfg(target_os = "linux")]
33 Opkg,
34 #[cfg(target_os = "linux")]
35 Urpm,
36 #[cfg(target_os = "linux")]
37 Xbps,
38 #[cfg(target_os = "linux")]
39 Zypper,
40 #[cfg(target_os = "linux")]
41 Flatpak,
42 #[cfg(target_os = "linux")]
43 Snap,
44 #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly", target_os = "netbsd"))]
45 Pkg,
46 #[cfg(target_os = "haiku")]
47 Pkgman,
48 #[cfg(target_os = "macos")]
49 Brew,
50 #[cfg(target_os = "macos")]
51 Ports,
52 #[cfg(target_os = "windows")]
53 Scoop,
54 #[cfg(target_os = "windows")]
55 Choco,
56 #[cfg(target_os = "windows")]
57 Winget,
58 #[cfg(target_os = "android")]
59 Termux,
60}
61
62#[derive(Debug, Clone, Copy)]
63pub enum PlsCommand {
64 Install,
65 Remove,
66 Upgrade,
67 Search,
68 Info,
69 Update,
70 UpgradeAll,
71 List,
72}
73
74impl Vendor {
75 pub fn new() -> Result<Self> {
76 for vendor in Vendor::iter() {
77 if vendor.is_available() {
78 return Ok(vendor)
79 }
80 }
81 Err(eyre!(
82 "no vendor installed, candidates are: {}",
83 Vendor::iter().map(|vendor| vendor.to_string()).collect::<Vec<String>>().join(", "),
84 ))
85 }
86
87 pub fn is_available(&self) -> bool {
88 let vendor_data: VendorData = (*self).into();
89 which::which(vendor_data.1[0]).is_ok()
90 }
91
92 pub fn execute(self, command: PlsCommand, args: &str, yes: bool, su: bool, dry_run: bool, pager: Option<String>) -> Result<i32> {
93 let vendor_data: VendorData = self.into();
94 let command = command.format(vendor_data, args, yes, pager);
95
96 if command.is_empty() {
97 eprintln!("command not supported by the current vendor");
98 return Ok(1)
99 }
100
101 if dry_run {
102 eprintln!("{}", command);
103 return Ok(0);
104 }
105
106 #[cfg(target_os = "windows")]
107 let status = Command::new("cmd").args(["/C", &command]).status()?;
108 #[cfg(not(target_os = "windows"))]
109 let status = if su {
110 Command::new("sudo").args(command.split(" ")).status()?
111 } else {
112 Command::new("sh").args(["-c", &command]).status()?
113 };
114
115 Ok(status.code().unwrap_or_default())
116 }
117}
118
119impl PlsCommand {
120 fn format(self, vendor: VendorData, args: &str, yes: bool, pager: Option<String>) -> String {
121 match self {
122 PlsCommand::Install => vendor.1[2].to_owned(),
123 PlsCommand::Remove => vendor.1[3].to_owned(),
124 PlsCommand::Upgrade => vendor.1[4].to_owned(),
125 PlsCommand::Info => vendor.1[6].to_owned(),
126 PlsCommand::Update => vendor.1[7].to_owned(),
127 PlsCommand::UpgradeAll => vendor.1[8].to_owned(),
128 PlsCommand::Search => {
129 if let Some(pager) = pager {
130 format!("{} | {}", vendor.1[5], pager)
131 } else {
132 vendor.1[5].to_owned()
133 }
134 }
135 PlsCommand::List => {
136 if let Some(pager) = pager {
137 format!("{} | {}", vendor.1[9], pager)
138 } else {
139 vendor.1[9].to_owned()
140 }
141 }
142 }
143 .replace("$yes", if yes {vendor.1[1]} else {""})
144 .replace("$args", args)
145 }
146}
147
148impl From<OsString> for Vendor {
149 fn from(value: OsString) -> Self {
150 let value = value.to_string_lossy().to_lowercase();
151 for vendor in Vendor::iter() {
152 if vendor.to_string().to_lowercase() == value {
153 return vendor;
154 }
155 }
156 panic!("invalid vendor name {}", value);
157 }
158}
159
160use Vendor::*;
162
163#[derive(Debug, Clone, Copy, PartialEq)]
164struct VendorData(Vendor, [&'static str; 10]);
165
166static VENDORS: &[VendorData] = &[
167 #[cfg(target_os = "linux")]
168 VendorData(Apk, [
169 "apk",
170 "",
171 "apk add $args",
172 "apk del $args",
173 "apk upgrade $args",
174 "apk search $args",
175 "apk info $args",
176 "apk update",
177 "apk upgrade",
178 "apk list --installed",
179 ]),
180 #[cfg(target_os = "linux")]
181 VendorData(Apt, [
182 "apt",
183 "--yes",
184 "apt install $yes $args",
185 "apt remove $yes $args",
186 "apt install --only-upgrade $yes $args",
187 "apt search $args",
188 "apt show $args",
189 "apt update $yes",
190 "apt upgrade $yes",
191 "apt list --installed",
192 ]),
193 #[cfg(target_os = "macos")]
194 VendorData(Brew, [
195 "brew",
196 "",
197 "brew install $args",
198 "brew uninstall $args",
199 "brew upgrade $args",
200 "brew search $args",
201 "brew info $args",
202 "brew update",
203 "brew upgrade",
204 "brew list",
205 ]),
206 #[cfg(target_os = "linux")]
207 VendorData(Cards, [
208 "cards",
209 "",
210 "cards install $args",
211 "cards remove $args",
212 "cards install --upgrade $args",
213 "cards search $args",
214 "cards info $args",
215 "cards sync",
216 "cards upgrade",
217 "cards list",
218 ]),
219 #[cfg(target_os = "windows")]
220 VendorData(Choco, [
221 "choco",
222 "--yes",
223 "choco install $yes $args",
224 "choco uninstall $yes $args",
225 "choco upgrade $yes $args",
226 "choco search $args",
227 "choco info $args",
228 "",
229 "choco upgrade all $yes",
230 "choco list",
231 ]),
232 #[cfg(target_os = "linux")]
233 VendorData(Dnf, [
234 "dnf",
235 "--assumeyes",
236 "dnf install $yes $args",
237 "dnf remove $yes $args",
238 "dnf upgrade $yes $args",
239 "dnf search $args",
240 "dnf info $args",
241 "dnf check-update $yes",
242 "dnf update $yes",
243 "dnf list --installed",
244 ]),
245 #[cfg(target_os = "linux")]
246 VendorData(Emerge, [
247 "emerge",
248 "",
249 "emerge $args",
250 "emerge --depclean $args",
251 "emerge --update $args",
252 "emerge --search $args",
253 "emerge --info $args",
254 "emerge --sync",
255 "emerge -vuDN @world",
256 "qlist -Iv",
257 ]),
258 #[cfg(target_os = "linux")]
259 VendorData(Eopkg, [
260 "eopkg",
261 "--yes-all",
262 "eopkg install $yes $args",
263 "eopkg remove $yes $args",
264 "eopkg upgrade $yes $args",
265 "eopkg search $args",
266 "eopkg info $args",
267 "eopkg update-repo $yes",
268 "eopkg upgrade $yes",
269 "eopkg list-installed",
270 ]),
271 #[cfg(target_os = "linux")]
272 VendorData(Flatpak, [
273 "flatpak",
274 "--assumeyes",
275 "flatpak --user install $yes $args",
276 "flatpak --user uninstall $yes $args",
277 "flatpak --user update $yes $args",
278 "flatpak --user search $args",
279 "flatpak --user info $args",
280 "",
281 "flatpak --user update $yes",
282 "flatpak --user list",
283 ]),
284 #[cfg(target_os = "linux")]
285 VendorData(Guix, [
286 "guix",
287 "",
288 "guix install $yes $args",
289 "guix remove $yes $args",
290 "guix upgrade $yes $args",
291 "guix search $args",
292 "guix show $args",
293 "guix refresh $yes",
294 "guix upgrade $yes",
295 "guix package --list-installed",
296 ]),
297 #[cfg(target_os = "linux")]
298 VendorData(NixEnv, [
299 "nix-env",
300 "",
301 "nix-env --install $args",
302 "nix-env --uninstall $args",
303 "nix-env --upgrade $args",
304 "nix-env -qaP $args",
305 "nix-env -qa --description $args",
306 "nix-channel --update",
307 "nix-env --upgrade",
308 "nix-env --query --installed",
309 ]),
310 #[cfg(target_os = "linux")]
311 VendorData(Opkg, [
312 "opkg",
313 "",
314 "opkg install $args",
315 "opkg remove $args",
316 "opkg upgrade $args",
317 "opkg find $args",
318 "opkg info $args",
319 "opkg update",
320 "opkg upgrade",
321 "opkg list-installed",
322 ]),
323 #[cfg(target_os = "linux")]
324 VendorData(Pacman, [
325 "pacman",
326 "--noconfirm",
327 "pacman -S $yes $args",
328 "pacman -Rs $yes $args",
329 "pacman -S $yes $args",
330 "pacman -Ss $args",
331 "pacman -Si $args",
332 "pacman -Sy $yes",
333 "pacman -Syu $yes",
334 "pacman -Q",
335 ]),
336 #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly", target_os = "netbsd"))]
337 VendorData(Pkg, [
338 "pkg",
339 "--yes",
340 "pkg install $yes $args",
341 "pkg remove $yes $args",
342 "pkg install $yes $args",
343 "pkg search $args",
344 "pkg info $args",
345 "pkg update $yes",
346 "pkg upgrade $yes",
347 "pkg info --all",
348 ]),
349 #[cfg(target_os = "haiku")]
350 VendorData(Pkgman, [
351 "pkgman",
352 "-y",
353 "pkgman install $yes $args",
354 "pkgman uninstall $yes $args",
355 "pkgman update $yes $args",
356 "pkgman search $args",
357 "",
358 "pkgman refresh $yes",
359 "pkgman update $yes",
360 "pkgman search --installed-only --all",
361 ]),
362 #[cfg(target_os = "macos")]
363 VendorData(Ports, [
364 "prt-get",
365 "",
366 "prt-get install $args",
367 "prt-get remove $args",
368 "prt-get update $args",
369 "prt-get search $args",
370 "prt-get info $args",
371 "ports -u",
372 "prt-get sysup",
373 "prt-get listinst",
374 ]),
375 #[cfg(target_os = "windows")]
376 VendorData(Scoop, [
377 "scoop",
378 "",
379 "scoop install $args",
380 "scoop uninstall $args",
381 "scoop update $args",
382 "scoop search $args",
383 "scoop info $args",
384 "scoop update",
385 "scoop update *",
386 "scoop list",
387 ]),
388 #[cfg(target_os = "linux")]
389 VendorData(Slackpkg, [
390 "slackpkg",
391 "",
392 "slackpkg install $args",
393 "slackpkg remove $args",
394 "slackpkg upgrade $args",
395 "slackpkg search $args",
396 "slackpkg info $args",
397 "slackpkg update",
398 "slackpkg upgrade-all",
399 "ls -1 /var/log/packages",
400 ]),
401 #[cfg(target_os = "linux")]
402 VendorData(Snap, [
403 "snap",
404 "",
405 "snap install --classic $args",
406 "snap remove $args",
407 "snap refresh $args",
408 "snap find $args",
409 "snap info $args",
410 "",
411 "snap refresh",
412 "snap list",
413 ]),
414 #[cfg(target_os = "android")]
415 VendorData(Termux, [
416 "termux",
417 "--yes",
418 "pkg install $yes $args",
419 "pkg uninstall $yes $args",
420 "pkg install $yes $args",
421 "pkg search $args",
422 "pkg show $args",
423 "pkg update $yes",
424 "pkg upgrade $yes",
425 "pkg list-installed",
426 ]),
427 #[cfg(target_os = "linux")]
428 VendorData(Urpm, [
429 "urpm",
430 "",
431 "urpmi $args",
432 "urpme $args",
433 "urpmi $args",
434 "urpmq --fuzzy $args",
435 "urpmq -i $args",
436 "urpmi.update -a",
437 "urpmi --auto-update",
438 "rpm --query --all",
439 ]),
440 #[cfg(target_os = "windows")]
441 VendorData(Winget, [
442 "winget",
443 "",
444 "winget install $args",
445 "winget uninstall $args",
446 "winget upgrade $args",
447 "winget search $args",
448 "winget show $args",
449 "",
450 "winget upgrade --all",
451 "winget list",
452 ]),
453 #[cfg(target_os = "linux")]
454 VendorData(Xbps, [
455 "xbps",
456 "--yes",
457 "xbps-install $yes $args",
458 "xbps-remove $yes $args",
459 "xbps-install --update $yes $args",
460 "xbps-query -Rs $args",
461 "xbps-query -RS $args",
462 "xbps-install --sync $yes",
463 "xbps-install --update $yes",
464 "xbps-query --list-pkgs",
465 ]),
466 #[cfg(target_os = "linux")]
467 VendorData(Yay, [
468 "yay",
469 "--noconfirm",
470 "yay --topdown --cleanafter -S $yes $args",
471 "pacman -Rs $yes $args",
472 "yay --topdown --cleanafter -S $yes $args",
473 "yay --topdown -Ss $args",
474 "yay --topdown -Si $args",
475 "yay --topdown -Sy $yes",
476 "yay --topdown -Syu $yes",
477 "pacman -Q",
478 ]),
479 #[cfg(target_os = "linux")]
480 VendorData(Yum, [
481 "yum",
482 "--assumeyes",
483 "yum install $yes $args",
484 "yum remove $yes $args",
485 "yum update $yes $args",
486 "yum search $args",
487 "yum info $args",
488 "yum check-update $yes",
489 "yum update $yes",
490 "yum list --installed",
491 ]),
492 #[cfg(target_os = "linux")]
493 VendorData(Zypper, [
494 "zypper",
495 "--no-confirm",
496 "zypper install $yes $args",
497 "zypper remove $yes $args",
498 "zypper update $yes $args",
499 "zypper search $args",
500 "zypper info $args",
501 "zypper refresh $yes",
502 "zypper update $yes",
503 "zypper search --installed-only",
504 ]),
505];
506
507impl Display for Vendor {
508 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
509 write!(f, "{:?}", self)
510 }
511}
512
513impl From<Vendor> for VendorData {
514 fn from(value: Vendor) -> Self {
515 for vendor in VENDORS.iter() {
516 if vendor.0 == value {
517 return *vendor;
518 }
519 }
520 panic!("unreachable code reached for vendor {:?}", value);
521 }
522}
523
524impl TryFrom<&str> for Vendor {
525 type Error = String;
526
527 fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
528 let value = value.to_lowercase();
529 for vendor in Vendor::iter() {
530 if vendor.to_string().to_lowercase() == value {
531 return Ok(vendor);
532 }
533 }
534 Err(format!("invalid vendor name {}", value))
535 }
536}