use core::cell::RefCell;
use std::{
borrow::Cow,
collections::BTreeMap,
ffi::OsString,
fmt::{self, Display},
sync::LazyLock,
};
use clap::ValueEnum;
use eyre::{eyre, Result};
use serde::{Serialize, Deserialize};
use strum::{EnumIter, IntoEnumIterator};
use crate::package::Package;
use crate::pls_command::PlsCommand;
use crate::reinstall::reinstall_all;
use crate::revendor::change_vendor;
use crate::run_command::run_command;
use crate::track;
use crate::vendor_data::VendorData;
#[derive(Debug, Clone, Copy, Default, EnumIter, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Vendor {
#[default]
Unknown,
Upt,
Cargo,
Go,
Npm,
Uv,
#[cfg(not(target_os = "windows"))]
Pkgx,
#[cfg(target_os = "linux")]
Apt,
#[cfg(target_os = "linux")]
Yay,
#[cfg(target_os = "linux")]
Yum,
#[cfg(target_os = "linux")]
Pacman,
#[cfg(target_os = "linux")]
Rua,
#[cfg(target_os = "linux")]
Apk,
#[cfg(target_os = "linux")]
Emerge,
#[cfg(target_os = "linux")]
Guix,
#[cfg(target_os = "linux")]
NixEnv,
#[cfg(target_os = "linux")]
Slackpkg,
#[cfg(target_os = "linux")]
Cards,
#[cfg(target_os = "linux")]
Dnf,
#[cfg(target_os = "linux")]
Eopkg,
#[cfg(target_os = "linux")]
Opkg,
#[cfg(target_os = "linux")]
Urpm,
#[cfg(target_os = "linux")]
Xbps,
#[cfg(target_os = "linux")]
Zypper,
#[cfg(target_os = "linux")]
Flatpak,
#[cfg(target_os = "linux")]
Snap,
#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly", target_os = "netbsd"))]
Pkg,
#[cfg(target_os = "haiku")]
Pkgman,
#[cfg(target_os = "macos")]
Brew,
#[cfg(target_os = "macos")]
Ports,
#[cfg(target_os = "windows")]
Scoop,
#[cfg(target_os = "windows")]
Choco,
#[cfg(target_os = "windows")]
Winget,
#[cfg(target_os = "android")]
Termux,
}
impl Vendor {
pub fn new() -> Result<Self> {
for vendor in Vendor::iter() {
if vendor.is_available()? {
return Ok(vendor)
}
}
Err(eyre!(
"no vendor installed, candidates are: {}",
Vendor::iter().map(|vendor| vendor.to_string()).collect::<Vec<String>>().join(", "),
))
}
#[allow(static_mut_refs)]
pub fn is_available(&self) -> Result<bool> {
unsafe {
if let Some(&available) = AVAILABILITY.borrow().get(self) {
return Ok(available);
}
}
let vendor_data: VendorData = (*self).try_into()?;
let available = which::which(vendor_data.1[0]).is_ok();
unsafe {
AVAILABILITY.borrow_mut().insert(self.clone(), available);
}
Ok(available)
}
pub fn execute(
self,
pls_command: &PlsCommand,
args: Vec<String>,
yes: bool,
su: bool,
dry_run: bool,
pager: Option<String>,
supplied_vendor: Option<Vendor>,
) -> Result<i32> {
let pager = if pls_command.support_pager() {
pager
} else {
None
};
if pls_command.to_owned() == PlsCommand::ReinstallAll {
return reinstall_all(supplied_vendor, yes, su, dry_run, &pager, &pls_command);
}
if !dry_run {
if let PlsCommand::Move { origin, destination } = pls_command {
return change_vendor(*origin, *destination);
}
}
let vendor_data: VendorData = self.try_into()?;
let packages: Vec<Package> = args.iter()
.map(|arg| arg.as_str().into())
.map(|mut package: Package| {
package.vendor = self;
package
})
.collect();
let command = pls_command.format(vendor_data, &packages, yes, &pager);
if command.is_empty() {
eprintln!("command not supported by the current vendor");
return Ok(1)
}
if dry_run {
println!("{}", command);
return Ok(0);
}
let status = run_command(&command, su, &pager, &pls_command)?;
if !dry_run && status == 0 {
match pls_command {
PlsCommand::Install => track::save_installed_packages(packages, true)?,
PlsCommand::Remove => track::remove_installed_packages(packages)?,
_ => (),
}
}
Ok(status)
}
pub fn version_sep(&self) -> Cow<'static, str> {
match self {
Self::Pkgx |
Self::Npm => Cow::Borrowed("@"),
Self::Flatpak => Cow::Borrowed("//"),
_ => Cow::Borrowed("="),
}
}
}
impl TryFrom<OsString> for Vendor {
type Error = String;
fn try_from(value: OsString) -> Result<Self, Self::Error> {
let value = value.to_string_lossy().to_lowercase();
for vendor in Vendor::iter() {
if vendor.to_string().to_lowercase() == value {
return Ok(vendor);
}
}
Err(format!("invalid vendor name {}", value))
}
}
impl Display for Vendor {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Unknown => write!(f, "unknown"),
Self::Upt => write!(f, "upt"),
Self::Cargo => write!(f, "cargo"),
Self::Go => write!(f, "go"),
Self::Uv => write!(f, "uv"),
Self::Npm => write!(f, "npm"),
#[cfg(not(target_os = "windows"))]
Self::Pkgx => write!(f, "pkgx"),
#[cfg(target_os = "linux")]
Self::Apt => write!(f, "apt"),
#[cfg(target_os = "linux")]
Self::Yay => write!(f, "yay"),
#[cfg(target_os = "linux")]
Self::Yum => write!(f, "yum"),
#[cfg(target_os = "linux")]
Self::Pacman => write!(f, "pacman"),
#[cfg(target_os = "linux")]
Self::Rua => write!(f, "rua"),
#[cfg(target_os = "linux")]
Self::Apk => write!(f, "apk"),
#[cfg(target_os = "linux")]
Self::Emerge => write!(f, "emerge"),
#[cfg(target_os = "linux")]
Self::Guix => write!(f, "guix"),
#[cfg(target_os = "linux")]
Self::NixEnv => write!(f, "nix-env"),
#[cfg(target_os = "linux")]
Self::Slackpkg => write!(f, "slackpkg"),
#[cfg(target_os = "linux")]
Self::Cards => write!(f, "cards"),
#[cfg(target_os = "linux")]
Self::Dnf => write!(f, "dnf"),
#[cfg(target_os = "linux")]
Self::Eopkg => write!(f, "eopkg"),
#[cfg(target_os = "linux")]
Self::Opkg => write!(f, "opkg"),
#[cfg(target_os = "linux")]
Self::Urpm => write!(f, "urpm"),
#[cfg(target_os = "linux")]
Self::Xbps => write!(f, "xbps"),
#[cfg(target_os = "linux")]
Self::Zypper => write!(f, "zypper"),
#[cfg(target_os = "linux")]
Self::Flatpak => write!(f, "flatpak"),
#[cfg(target_os = "linux")]
Self::Snap => write!(f, "snap"),
#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly", target_os = "netbsd"))]
Self::Pkg => write!(f, "pkg"),
#[cfg(target_os = "haiku")]
Self::Pkgman => write!(f, "pkgman"),
#[cfg(target_os = "macos")]
Self::Brew => write!(f, "brew"),
#[cfg(target_os = "macos")]
Self::Ports => write!(f, "ports"),
#[cfg(target_os = "windows")]
Self::Scoop => write!(f, "scoop"),
#[cfg(target_os = "windows")]
Self::Choco => write!(f, "choco"),
#[cfg(target_os = "windows")]
Self::Winget => write!(f, "winget"),
#[cfg(target_os = "android")]
Self::Termux => write!(f, "termux"),
}
}
}
impl TryFrom<&str> for Vendor {
type Error = eyre::Error;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
let value = value.to_lowercase();
for vendor in Vendor::iter() {
if vendor.to_string().to_lowercase() == value {
return Ok(vendor);
}
}
Err(eyre!("invalid vendor name {}", value))
}
}
impl ValueEnum for Vendor {
fn value_variants<'a>() -> &'a [Self] {
&VENDORS_SLICE
}
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
Some(clap::builder::PossibleValue::new(self.to_string()))
}
}
static mut AVAILABILITY: LazyLock<RefCell<BTreeMap<Vendor, bool>>> = LazyLock::new(|| RefCell::new(BTreeMap::new()));
static INNER_VENDORS: LazyLock<Vec<Vendor>> = LazyLock::new(|| Vendor::iter().collect());
static VENDORS_SLICE: LazyLock<&[Vendor]> = LazyLock::new(|| &INNER_VENDORS);