please_install/
vendors.rs

1use std::borrow::Cow;
2use std::fmt;
3use std::{
4    ffi::OsString,
5    fmt::Display,
6    sync::LazyLock,
7};
8
9use clap::ValueEnum;
10use eyre::{eyre, Result};
11use serde::{Serialize, Deserialize};
12use strum::{EnumIter, IntoEnumIterator};
13
14use crate::package::Package;
15use crate::pls_command::PlsCommand;
16use crate::reinstall::reinstall_all;
17use crate::run_command::run_command;
18use crate::track;
19use crate::vendor_data::VendorData;
20
21
22#[derive(Debug, Clone, Copy, Default, EnumIter, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
23pub enum Vendor {
24    #[default]
25    Unknown,
26    Upt,
27    Cargo,
28    Go,
29    Npm,
30    Uv,
31    #[cfg(not(target_os = "windows"))]
32    Pkgx,
33    #[cfg(target_os = "linux")]
34    Apt,
35    #[cfg(target_os = "linux")]
36    Yay,
37    #[cfg(target_os = "linux")]
38    Yum,
39    #[cfg(target_os = "linux")]
40    Pacman,
41    #[cfg(target_os = "linux")]
42    Rua,
43    #[cfg(target_os = "linux")]
44    Apk,
45    #[cfg(target_os = "linux")]
46    Emerge,
47    #[cfg(target_os = "linux")]
48    Guix,
49    #[cfg(target_os = "linux")]
50    NixEnv,
51    #[cfg(target_os = "linux")]
52    Slackpkg,
53    #[cfg(target_os = "linux")]
54    Cards,
55    #[cfg(target_os = "linux")]
56    Dnf,
57    #[cfg(target_os = "linux")]
58    Eopkg,
59    #[cfg(target_os = "linux")]
60    Opkg,
61    #[cfg(target_os = "linux")]
62    Urpm,
63    #[cfg(target_os = "linux")]
64    Xbps,
65    #[cfg(target_os = "linux")]
66    Zypper,
67    #[cfg(target_os = "linux")]
68    Flatpak,
69    #[cfg(target_os = "linux")]
70    Snap,
71    #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly", target_os = "netbsd"))]
72    Pkg,
73    #[cfg(target_os = "haiku")]
74    Pkgman,
75    #[cfg(target_os = "macos")]
76    Brew,
77    #[cfg(target_os = "macos")]
78    Ports,
79    #[cfg(target_os = "windows")]
80    Scoop,
81    #[cfg(target_os = "windows")]
82    Choco,
83    #[cfg(target_os = "windows")]
84    Winget,
85    #[cfg(target_os = "android")]
86    Termux,
87}
88
89impl Vendor {
90    pub fn new() -> Result<Self> {
91        for vendor in Vendor::iter() {
92            if vendor.is_available()? {
93                return Ok(vendor)
94            }
95        }
96        Err(eyre!(
97            "no vendor installed, candidates are: {}",
98            Vendor::iter().map(|vendor| vendor.to_string()).collect::<Vec<String>>().join(", "),
99        ))
100    }
101
102    pub fn is_available(&self) -> Result<bool> {
103        let vendor_data: VendorData = (*self).try_into()?;
104        Ok(which::which(vendor_data.1[0]).is_ok())
105    }
106
107    pub fn execute(
108        self,
109        pls_command: PlsCommand,
110        args: Vec<String>,
111        yes: bool,
112        su: bool,
113        dry_run: bool,
114        pager: Option<String>,
115        supplied_vendor: Option<Vendor>,
116    ) -> Result<i32> {
117        if pls_command == PlsCommand::ReinstallAll {
118            return reinstall_all(supplied_vendor, yes, su, dry_run, pager);
119        }
120
121        let vendor_data: VendorData = self.try_into()?;
122        let packages: Vec<Package> = args.iter()
123            .map(|arg| arg.as_str().into())
124            .map(|mut package: Package| {
125                package.vendor = self;
126                package
127            })
128            .collect();
129
130        let command = pls_command.format(vendor_data, &packages, yes, pager.clone());
131
132        if command.is_empty() {
133            eprintln!("command not supported by the current vendor");
134            return Ok(1)
135        }
136
137        if dry_run {
138            println!("{}", command);
139            return Ok(0);
140        }
141
142        let status = run_command(&command, su)?;
143
144        if !dry_run && status == 0 {
145            match pls_command {
146                PlsCommand::Install => track::save_installed_packages(packages)?,
147                PlsCommand::Remove => track::remove_installed_packages(packages)?,
148                _ => (),
149            }
150        }
151
152        Ok(status)
153    }
154
155    pub fn version_sep(&self) -> Cow<'static, str> {
156        match self {
157            Self::Pkgx |
158            Self::Npm => Cow::Borrowed("@"),
159            Self::Flatpak => Cow::Borrowed("//"),
160            _ => Cow::Borrowed("="),
161        }
162    }
163}
164
165impl TryFrom<OsString> for Vendor {
166    type Error = String;
167
168    fn try_from(value: OsString) -> Result<Self, Self::Error> {
169        let value = value.to_string_lossy().to_lowercase();
170        for vendor in Vendor::iter() {
171            if vendor.to_string().to_lowercase() == value {
172                return Ok(vendor);
173            }
174        }
175        Err(format!("invalid vendor name {}", value))
176    }
177}
178
179impl Display for Vendor {
180    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
181        match self {
182            Self::Unknown => write!(f, "unknown"),
183            Self::Upt => write!(f, "upt"),
184            Self::Cargo => write!(f, "cargo"),
185            Self::Go => write!(f, "go"),
186            Self::Uv => write!(f, "uv"),
187            Self::Npm => write!(f, "npm"),
188            #[cfg(not(target_os = "windows"))]
189            Self::Pkgx => write!(f, "pkgx"),
190            #[cfg(target_os = "linux")]
191            Self::Apt => write!(f, "apt"),
192            #[cfg(target_os = "linux")]
193            Self::Yay => write!(f, "yay"),
194            #[cfg(target_os = "linux")]
195            Self::Yum => write!(f, "yum"),
196            #[cfg(target_os = "linux")]
197            Self::Pacman => write!(f, "pacman"),
198            #[cfg(target_os = "linux")]
199            Self::Rua => write!(f, "rua"),
200            #[cfg(target_os = "linux")]
201            Self::Apk => write!(f, "apk"),
202            #[cfg(target_os = "linux")]
203            Self::Emerge => write!(f, "emerge"),
204            #[cfg(target_os = "linux")]
205            Self::Guix => write!(f, "guix"),
206            #[cfg(target_os = "linux")]
207            Self::NixEnv => write!(f, "nix-env"),
208            #[cfg(target_os = "linux")]
209            Self::Slackpkg => write!(f, "slackpkg"),
210            #[cfg(target_os = "linux")]
211            Self::Cards => write!(f, "cards"),
212            #[cfg(target_os = "linux")]
213            Self::Dnf => write!(f, "dnf"),
214            #[cfg(target_os = "linux")]
215            Self::Eopkg => write!(f, "eopkg"),
216            #[cfg(target_os = "linux")]
217            Self::Opkg => write!(f, "opkg"),
218            #[cfg(target_os = "linux")]
219            Self::Urpm => write!(f, "urpm"),
220            #[cfg(target_os = "linux")]
221            Self::Xbps => write!(f, "xbps"),
222            #[cfg(target_os = "linux")]
223            Self::Zypper => write!(f, "zypper"),
224            #[cfg(target_os = "linux")]
225            Self::Flatpak => write!(f, "flatpak"),
226            #[cfg(target_os = "linux")]
227            Self::Snap => write!(f, "snap"),
228            #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly", target_os = "netbsd"))]
229            Self::Pkg => write!(f, "pkg"),
230            #[cfg(target_os = "haiku")]
231            Self::Pkgman => write!(f, "pkgman"),
232            #[cfg(target_os = "macos")]
233            Self::Brew => write!(f, "brew"),
234            #[cfg(target_os = "macos")]
235            Self::Ports => write!(f, "ports"),
236            #[cfg(target_os = "windows")]
237            Self::Scoop => write!(f, "scoop"),
238            #[cfg(target_os = "windows")]
239            Self::Choco => write!(f, "choco"),
240            #[cfg(target_os = "windows")]
241            Self::Winget => write!(f, "winget"),
242            #[cfg(target_os = "android")]
243            Self::Termux => write!(f, "termux"),
244        }
245    }
246}
247
248impl TryFrom<&str> for Vendor {
249    type Error = eyre::Error;
250
251    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
252        let value = value.to_lowercase();
253        for vendor in Vendor::iter() {
254            if vendor.to_string().to_lowercase() == value {
255                return Ok(vendor);
256            }
257        }
258        Err(eyre!("invalid vendor name {}", value))
259    }
260}
261
262impl ValueEnum for Vendor {
263    fn value_variants<'a>() -> &'a [Self] {
264        &VENDORS_SLICE
265    }
266
267    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
268        Some(clap::builder::PossibleValue::new(self.to_string()))
269    }
270}
271
272static INNER_VENDORS: LazyLock<Vec<Vendor>> = LazyLock::new(|| Vendor::iter().collect());
273static VENDORS_SLICE: LazyLock<&[Vendor]> = LazyLock::new(|| &INNER_VENDORS);