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);