please_install/
vendors.rs

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