use std::any::Any; use std::cmp::{Eq, Ord}; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::hash::Hash; use std::process::Command; use std::rc::Rc; use anyhow::{Context, Result}; use crate::cmd::run_external_command; use crate::{Group, Package}; pub(in crate::backend) type Switches = &'static [&'static str]; pub(in crate::backend) type Text = &'static str; /// The trait of a struct that is used as a backend. pub trait Backend: Debug { /// Return the actual binary. Iff the backend supports different /// binaries, you will need to overwrite this implementation to return /// the binary that was loaded at runtime. See /// [`Backend::get_binary_default()`]. fn get_binary(&self) -> Text { self.get_binary_default() } /// Return the default binary as defined in the constant of the module. /// See [`Backend::get_binary()`]. fn get_binary_default(&self) -> Text; /// Get the name of the section in the group files. fn get_section(&self) -> Text; /// Get CLI switches for the package manager to show information for /// packages. fn get_switches_info(&self) -> Switches; /// Get CLI switches for the package manager to install packages. fn get_switches_install(&self) -> Switches; /// Get CLI switches for the package manager to perform `sync` and `clean` without /// confirmation. fn get_switches_noconfirm(&self) -> Switches; /// Get CLI switches for the package manager to remove packages. fn get_switches_remove(&self) -> Switches; /// Get CLI switches for the package manager to mark packages as /// dependency. This is not supported by all package managers. See /// [`Backend::supports_as_dependency`]. fn get_switches_make_dependency(&self) -> Switches; /// Load all packages from a set of groups. The backend will visit all groups, /// find its own section, and clone all packages into its own struct. fn load(&mut self, groups: &HashSet); /// Get all managed packages for this backend, i.e. all packages /// under the corresponding section in all group files. fn get_managed_packages(&self) -> &HashSet; /// Get all packages that are installed in the system. /// /// # Errors /// /// This function shall return an error if the installed packages cannot be /// determined. fn get_all_installed_packages(&self) -> Result>; /// Get all packages that were installed in the system explicitly. /// /// # Errors /// /// This function shall return an error if the explicitly installed packages /// cannot be determined. fn get_explicitly_installed_packages(&self) -> Result>; /// Assign each of the packages to an individual group by editing the /// group files. fn assign_group(&self, to_assign: Vec<(Package, Rc)>) -> Result<()> { let group_package_map = to_hashmap(to_assign); let section_header = format!("[{}]", self.get_section()); for (group, packages) in group_package_map { group.save_packages(§ion_header, &packages)?; } Ok(()) } /// Install the specified packages. If `noconfirm` is `true`, pass the corresponding /// switch to the package manager. Return the [`ExitStatus`] from the package manager. /// /// # Errors /// /// This function will return an error if the package manager cannot be run or it /// returns an error. fn install_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> { let mut cmd = Command::new(self.get_binary()); cmd.args(self.get_switches_install()); if noconfirm { cmd.args(self.get_switches_noconfirm()); } for p in packages { cmd.arg(format!("{p}")); } run_external_command(cmd) } /// Mark the packages as non-explicit / dependency using the underlying /// package manager. /// /// # Panics /// /// This method shall panic when the backend does not support depedent packages. fn make_dependency(&self, packages: &[Package]) -> Result<()> { let mut cmd = Command::new(self.get_binary()); cmd.args(self.get_switches_make_dependency()); for p in packages { cmd.arg(format!("{p}")); } run_external_command(cmd) } /// Remove the specified packages. /// fn remove_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> { let mut cmd = Command::new(self.get_binary()); cmd.args(self.get_switches_remove()); if noconfirm { cmd.args(self.get_switches_noconfirm()); } for p in packages { cmd.arg(format!("{p}")); } run_external_command(cmd) } /// Get missing packages, sorted alphabetically. fn get_missing_packages_sorted(&self) -> Result> { let installed = self .get_all_installed_packages() .context("could not get installed packages")?; let managed = self.get_managed_packages(); let mut diff: Vec<_> = managed.difference(&installed).cloned().collect(); diff.sort_unstable(); Ok(diff) } /// Show information from package manager for package. fn show_package_info(&self, package: &Package) -> Result<()> { let mut cmd = Command::new(self.get_binary()); cmd.args(self.get_switches_info()); cmd.arg(format!("{package}")); run_external_command(cmd) } /// Get unmanaged packages, sorted alphabetically. fn get_unmanaged_packages_sorted(&self) -> Result> { let installed = self .get_explicitly_installed_packages() .context("could not get explicitly installed packages")?; let required = self.get_managed_packages(); let mut diff: Vec<_> = installed.difference(required).cloned().collect(); diff.sort_unstable(); Ok(diff) } /// Return a mutable reference to self as `Any`. Required for downcasting. fn as_any_mut(&mut self) -> &mut dyn Any; /// Whether the underlying package manager supports dependency packages. fn supports_as_dependency(&self) -> bool; } /// For a vector of tuples containing a `V` and `K`, where a `K` may occur more than /// once and each `V` exactly once, create a `HashMap` that associates each `K` with /// a `Vec`. fn to_hashmap(to_assign: Vec<(V, K)>) -> HashMap> where K: Hash + Eq, V: Ord, { let mut map = HashMap::new(); for (value, key) in to_assign { let inner: &mut Vec = map.entry(key).or_default(); inner.push(value); } for vecs in map.values_mut() { vecs.sort_unstable(); } map }