please_install/
vendors.rs

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
160//----------------------------------------------------------------------------//
161use 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}