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