refact(backend,grouping): overhaul

- Refactor the confusing/bodgy consts backend macro to use a single
  BackendInfo struct
- Combine supports_as_dependency() and the switches_dependency into a
  single Option type as that is a better representation as it is an
  optional backend feature.
- Add more types like Packages, Sections for uniformity.
- Added a crate-private prelude and switches all imports to use a
  prelude glob import (excluding functions because it's nice to only be
  importing types)
- Refactored Backend section name in logging by implementing Display for
  AnyBackend that just prints the section

Implemented in #77.
This commit is contained in:
steven-omaha
2024-04-27 09:23:46 +02:00
25 changed files with 590 additions and 636 deletions
+28 -39
View File
@@ -5,52 +5,37 @@ use alpm::Alpm;
use alpm::PackageReason::Explicit;
use anyhow::{Context, Result};
use crate::backend::backend_trait::{Backend, Switches, Text};
use crate::backend::macros::impl_backend_constants;
use crate::cmd::run_external_command;
use crate::Package;
use crate::prelude::*;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Arch {
pub binary: String,
pub aur_rm_args: Vec<String>,
pub packages: HashSet<Package>,
}
impl Arch {
pub fn new() -> Self {
pub fn new(config: &Config) -> Self {
Self {
binary: BINARY.to_string(),
aur_rm_args: vec![],
packages: HashSet::new(),
binary: config.aur_helper.clone(),
aur_rm_args: config.aur_rm_args.clone(),
}
}
}
impl Default for Arch {
fn default() -> Self {
Self::new()
}
}
const BINARY: Text = "pacman";
const SECTION: Text = "arch";
const SWITCHES_INFO: Switches = &["--query", "--info"];
const SWITCHES_INSTALL: Switches = &["--sync"];
const SWITCHES_MAKE_DEPENDENCY: Switches = &["--database", "--asdeps"];
const SWITCHES_NOCONFIRM: Switches = &["--noconfirm"];
const SWITCHES_REMOVE: Switches = &["--remove", "--recursive"];
const SUPPORTS_AS_DEPENDENCY: bool = true;
impl Backend for Arch {
impl_backend_constants!();
fn get_binary(&self) -> Text {
let r#box = self.binary.clone().into_boxed_str();
Box::leak(r#box)
fn backend_info(&self) -> BackendInfo {
BackendInfo {
binary: self.binary.clone(),
section: "arch",
switches_info: &["--query", "--info"],
switches_install: &["--sync"],
switches_noconfirm: &["--noconfirm"],
switches_remove: &["--remove", "--recursive"],
switches_make_dependency: Some(&["--database", "--asdeps"]),
}
}
fn get_all_installed_packages(&self) -> Result<HashSet<Package>> {
fn get_all_installed_packages(&self) -> Result<Packages> {
let alpm_packages = get_all_installed_packages_from_alpm()
.context("getting all installed packages from alpm")?;
@@ -58,7 +43,7 @@ impl Backend for Arch {
Ok(result)
}
fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>> {
fn get_explicitly_installed_packages(&self) -> Result<Packages> {
let alpm_packages = get_explicitly_installed_packages_from_alpm()
.context("getting all installed packages from alpm")?;
let result = convert_to_pacdef_packages(alpm_packages);
@@ -66,13 +51,15 @@ impl Backend for Arch {
}
/// Install the specified packages.
fn install_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> {
fn install_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = Command::new(&self.binary);
cmd.args(self.get_switches_install());
cmd.args(backend_info.switches_install);
if noconfirm {
cmd.args(self.get_switches_noconfirm());
cmd.args(backend_info.switches_noconfirm);
}
for p in packages {
@@ -83,14 +70,16 @@ impl Backend for Arch {
}
/// Remove the specified packages.
fn remove_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> {
fn remove_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = Command::new(&self.binary);
cmd.args(self.get_switches_remove());
cmd.args(backend_info.switches_remove);
cmd.args(&self.aur_rm_args);
if noconfirm {
cmd.args(self.get_switches_noconfirm());
cmd.args(backend_info.switches_noconfirm);
}
for p in packages {
@@ -124,7 +113,7 @@ fn get_explicitly_installed_packages_from_alpm() -> Result<HashSet<String>> {
Ok(result)
}
fn convert_to_pacdef_packages(packages: HashSet<String>) -> HashSet<Package> {
fn convert_to_pacdef_packages(packages: HashSet<String>) -> Packages {
packages.into_iter().map(Package::from).collect()
}
+32 -37
View File
@@ -1,24 +1,16 @@
use std::collections::HashSet;
use anyhow::Result;
use rust_apt::cache::PackageSort;
use rust_apt::new_cache;
use crate::backend::backend_trait::{Backend, Switches, Text};
use crate::backend::macros::impl_backend_constants;
use crate::backend::root::build_base_command_with_privileges;
use crate::cmd::run_external_command;
use crate::Package;
use crate::prelude::*;
#[derive(Debug, Clone)]
pub struct Debian {
pub packages: HashSet<Package>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Debian {}
impl Debian {
pub fn new() -> Self {
Self {
packages: HashSet::new(),
}
Self {}
}
}
impl Default for Debian {
@@ -27,43 +19,42 @@ impl Default for Debian {
}
}
const BINARY: Text = "apt";
const SECTION: Text = "debian";
const SWITCHES_INFO: Switches = &["show"];
const SWITCHES_INSTALL: Switches = &["install"];
const SWITCHES_MAKE_DEPENDENCY: Switches = &[]; // not needed
const SWITCHES_NOCONFIRM: Switches = &["--yes"];
const SWITCHES_REMOVE: Switches = &["remove"];
const SUPPORTS_AS_DEPENDENCY: bool = true;
impl Backend for Debian {
impl_backend_constants!();
fn backend_info(&self) -> BackendInfo {
BackendInfo {
binary: "apt".to_string(),
section: "debian",
switches_info: &["show"],
switches_install: &["install"],
switches_noconfirm: &["--yes"],
switches_remove: &["remove"],
switches_make_dependency: Some(&[]),
}
}
fn get_all_installed_packages(&self) -> Result<HashSet<Package>> {
fn get_all_installed_packages(&self) -> Result<Packages> {
let cache = new_cache!()?;
let sort = PackageSort::default().installed();
let mut result = HashSet::new();
let mut result = Packages::new();
for pkg in cache.packages(&sort)? {
result.insert(Package::from(pkg.name().to_string()));
}
Ok(result)
}
fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>> {
fn get_explicitly_installed_packages(&self) -> Result<Packages> {
let cache = new_cache!()?;
let sort = PackageSort::default().installed().manually_installed();
let mut result = HashSet::new();
let mut result = Packages::new();
for pkg in cache.packages(&sort)? {
result.insert(Package::from(pkg.name().to_string()));
}
Ok(result)
}
fn make_dependency(&self, packages: &[Package]) -> Result<()> {
fn make_dependency(&self, packages: &Packages) -> Result<()> {
let mut cmd = build_base_command_with_privileges("apt-mark");
cmd.arg("auto");
for p in packages {
@@ -74,13 +65,15 @@ impl Backend for Debian {
}
/// Install the specified packages.
fn install_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> {
let mut cmd = build_base_command_with_privileges(self.get_binary());
fn install_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let backend_info = self.backend_info();
cmd.args(self.get_switches_install());
let mut cmd = build_base_command_with_privileges(&backend_info.binary);
cmd.args(backend_info.switches_install);
if noconfirm {
cmd.args(self.get_switches_noconfirm());
cmd.args(backend_info.switches_noconfirm);
}
for p in packages {
@@ -91,12 +84,14 @@ impl Backend for Debian {
}
/// Remove the specified packages.
fn remove_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> {
let mut cmd = build_base_command_with_privileges(self.get_binary());
cmd.args(self.get_switches_remove());
fn remove_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = build_base_command_with_privileges(&backend_info.binary);
cmd.args(backend_info.switches_remove);
if noconfirm {
cmd.args(self.get_switches_noconfirm());
cmd.args(backend_info.switches_noconfirm);
}
for p in packages {
+39 -41
View File
@@ -1,22 +1,15 @@
use std::collections::HashSet;
use std::process::Command;
use anyhow::Result;
use crate::backend::backend_trait::{Backend, Switches, Text};
use crate::backend::macros::impl_backend_constants;
use crate::cmd::run_external_command;
use crate::Package;
use crate::prelude::*;
#[derive(Debug, Clone)]
pub struct Fedora {
pub packages: HashSet<Package>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Fedora {}
impl Fedora {
pub fn new() -> Self {
Self {
packages: HashSet::new(),
}
Self {}
}
}
impl Default for Fedora {
@@ -25,16 +18,9 @@ impl Default for Fedora {
}
}
const BINARY: Text = "dnf";
const SECTION: Text = "fedora";
const SWITCHES_INFO: Switches = &["info"];
const SWITCHES_INSTALL: Switches = &["install"];
const SWITCHES_MAKE_DEPENDENCY: Switches = &[];
const SWITCHES_NOCONFIRM: Switches = &["--assumeyes"];
const SWITCHES_REMOVE: Switches = &["remove"];
const SUPPORTS_AS_DEPENDENCY: bool = false;
/// These repositories are ignored when storing the packages
/// as these are present by default on any sane fedora system
const DEFAULT_REPOS: [&str; 5] = ["koji", "fedora", "updates", "anaconda", "@"];
/// These switches are responsible for
/// getting the packages explicitly installed by the user
@@ -54,15 +40,21 @@ const SWITCHES_FETCH_GLOBAL: Switches = &[
"%{from_repo}/%{name}",
];
/// These repositories are ignored when storing the packages
/// as these are present by default on any sane fedora system
const DEFAULT_REPOS: [&str; 5] = ["koji", "fedora", "updates", "anaconda", "@"];
impl Backend for Fedora {
impl_backend_constants!();
fn backend_info(&self) -> BackendInfo {
BackendInfo {
binary: "dnf".to_string(),
section: "fedora",
switches_info: &["info"],
switches_install: &["install"],
switches_noconfirm: &["--assumeyes"],
switches_remove: &["remove"],
switches_make_dependency: None,
}
}
fn get_all_installed_packages(&self) -> Result<HashSet<Package>> {
let mut cmd = Command::new(self.get_binary());
fn get_all_installed_packages(&self) -> Result<Packages> {
let mut cmd = Command::new(self.backend_info().binary);
cmd.args(SWITCHES_FETCH_GLOBAL);
let output = String::from_utf8(cmd.output()?.stdout)?;
@@ -71,8 +63,8 @@ impl Backend for Fedora {
Ok(packages)
}
fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>> {
let mut cmd = Command::new(self.get_binary());
fn get_explicitly_installed_packages(&self) -> Result<Packages> {
let mut cmd = Command::new(self.backend_info().binary);
cmd.args(SWITCHES_FETCH_USER);
let output = String::from_utf8(cmd.output()?.stdout)?;
@@ -82,13 +74,15 @@ impl Backend for Fedora {
}
/// Install the specified packages.
fn install_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> {
fn install_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = Command::new("sudo");
cmd.arg(self.get_binary());
cmd.args(self.get_switches_install());
cmd.arg(backend_info.binary);
cmd.args(backend_info.switches_install);
if noconfirm {
cmd.args(self.get_switches_noconfirm());
cmd.args(backend_info.switches_noconfirm);
}
for p in packages {
@@ -106,13 +100,15 @@ impl Backend for Fedora {
}
/// Show information from package manager for package.
fn remove_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> {
fn remove_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = Command::new("sudo");
cmd.arg(self.get_binary());
cmd.args(self.get_switches_remove());
cmd.arg(backend_info.binary);
cmd.args(backend_info.switches_remove);
if noconfirm {
cmd.args(self.get_switches_noconfirm());
cmd.args(backend_info.switches_noconfirm);
}
for p in packages {
@@ -123,14 +119,16 @@ impl Backend for Fedora {
}
fn show_package_info(&self, package: &Package) -> Result<()> {
let mut cmd = Command::new(self.get_binary());
cmd.args(self.get_switches_info());
let backend_info = self.backend_info();
let mut cmd = Command::new(backend_info.binary);
cmd.args(backend_info.switches_info);
cmd.arg(&package.name);
run_external_command(cmd)
}
fn make_dependency(&self, _: &[Package]) -> Result<()> {
fn make_dependency(&self, _: &Packages) -> Result<()> {
panic!("Not supported by the package manager!")
}
}
+38 -46
View File
@@ -1,23 +1,18 @@
use std::collections::HashSet;
use std::process::Command;
use anyhow::Result;
use crate::backend::backend_trait::{Backend, Switches, Text};
use crate::backend::macros::impl_backend_constants;
use crate::cmd::run_external_command;
use crate::Package;
use crate::prelude::*;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Flatpak {
pub packages: HashSet<Package>,
pub systemwide: bool,
}
impl Flatpak {
pub fn new() -> Self {
pub fn new(config: &Config) -> Self {
Self {
packages: HashSet::new(),
systemwide: true,
systemwide: config.flatpak_systemwide,
}
}
@@ -29,8 +24,8 @@ impl Flatpak {
}
}
fn get_installed_packages(&self, include_implicit: bool) -> Result<HashSet<Package>> {
let mut cmd = Command::new(BINARY);
fn get_installed_packages(&self, include_implicit: bool) -> Result<Packages> {
let mut cmd = Command::new(self.backend_info().binary);
cmd.args(["list", "--columns=application"]);
if !include_implicit {
cmd.arg("--app");
@@ -40,48 +35,41 @@ impl Flatpak {
}
let output = String::from_utf8(cmd.output()?.stdout)?;
Ok(output
.lines()
.map(Package::from)
.collect::<HashSet<Package>>())
Ok(output.lines().map(Package::from).collect::<Packages>())
}
}
impl Default for Flatpak {
fn default() -> Self {
Self::new()
}
}
const BINARY: Text = "flatpak";
const SECTION: Text = "flatpak";
const SWITCHES_INSTALL: Switches = &["install"];
const SWITCHES_INFO: Switches = &["info"];
const SWITCHES_MAKE_DEPENDENCY: Switches = &[];
const SWITCHES_NOCONFIRM: Switches = &["--assumeyes"];
const SWITCHES_REMOVE: Switches = &["uninstall"];
const SUPPORTS_AS_DEPENDENCY: bool = false;
impl Backend for Flatpak {
impl_backend_constants!();
fn backend_info(&self) -> BackendInfo {
BackendInfo {
binary: "flatpak".to_string(),
section: "flatpak",
switches_info: &["info"],
switches_install: &["install"],
switches_noconfirm: &["--assumeyes"],
switches_remove: &["uninstall"],
switches_make_dependency: None,
}
}
fn get_all_installed_packages(&self) -> Result<HashSet<Package>> {
fn get_all_installed_packages(&self) -> Result<Packages> {
self.get_installed_packages(true)
}
fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>> {
fn get_explicitly_installed_packages(&self) -> Result<Packages> {
self.get_installed_packages(false)
}
/// Install the specified packages.
fn install_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> {
let mut cmd = Command::new(self.get_binary());
cmd.args(self.get_switches_install());
fn install_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = Command::new(backend_info.binary);
cmd.args(backend_info.switches_install);
cmd.args(self.get_switches_runtime());
if noconfirm {
cmd.args(self.get_switches_noconfirm());
cmd.args(backend_info.switches_noconfirm);
}
for p in packages {
@@ -91,18 +79,20 @@ impl Backend for Flatpak {
run_external_command(cmd)
}
fn make_dependency(&self, _: &[Package]) -> Result<()> {
panic!("not supported by {}", BINARY)
fn make_dependency(&self, _: &Packages) -> Result<()> {
panic!("not supported by {}", self.backend_info().binary)
}
/// 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());
fn remove_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = Command::new(backend_info.binary);
cmd.args(backend_info.switches_remove);
cmd.args(self.get_switches_runtime());
if noconfirm {
cmd.args(self.get_switches_noconfirm());
cmd.args(backend_info.switches_noconfirm);
}
for p in packages {
@@ -114,8 +104,10 @@ impl Backend for Flatpak {
/// 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());
let backend_info = self.backend_info();
let mut cmd = Command::new(backend_info.binary);
cmd.args(backend_info.switches_info);
cmd.args(self.get_switches_runtime());
cmd.arg(format!("{package}"));
+29 -45
View File
@@ -1,13 +1,10 @@
use std::collections::HashSet;
use std::process::Command;
use anyhow::Context;
use anyhow::Result;
use serde_json::Value;
use crate::backend::backend_trait::{Backend, Switches, Text};
use crate::backend::macros::impl_backend_constants;
use crate::Package;
use crate::prelude::*;
macro_rules! ERROR{
($bin:expr) => {
@@ -15,81 +12,68 @@ macro_rules! ERROR{
};
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Python {
pub binary: String,
pub packages: HashSet<Package>,
}
impl Python {
pub fn new() -> Self {
pub fn new(config: &Config) -> Self {
Self {
binary: BINARY.to_string(),
packages: HashSet::new(),
binary: config.pip_binary.to_string(),
}
}
fn get_switches_runtime(&self) -> Switches {
match self.get_binary() {
match self.backend_info().binary.as_str() {
"pip" => &["list", "--format", "json", "--not-required", "--user"],
"pipx" => &["list", "--json"],
_ => ERROR!(self.get_binary()),
_ => ERROR!(self.backend_info().binary),
}
}
fn get_switches_explicit(&self) -> Switches {
match self.get_binary() {
match self.backend_info().binary.as_str() {
"pip" => &["list", "--format", "json", "--user"],
"pipx" => &["list", "--json"],
_ => ERROR!(self.get_binary()),
_ => ERROR!(self.backend_info().binary),
}
}
fn extract_packages(&self, output: Value) -> Result<HashSet<Package>> {
match self.get_binary() {
fn extract_packages(&self, output: Value) -> Result<Packages> {
match self.backend_info().binary.as_str() {
"pip" => extract_pacdef_packages(output),
"pipx" => extract_pacdef_packages_pipx(output),
_ => ERROR!(self.get_binary()),
_ => ERROR!(self.backend_info().binary),
}
}
}
impl Default for Python {
fn default() -> Self {
Self::new()
}
}
const BINARY: Text = "pip";
const SECTION: Text = "python";
const SWITCHES_INFO: Switches = &["show"];
const SWITCHES_INSTALL: Switches = &["install"];
const SWITCHES_MAKE_DEPENDENCY: Switches = &[]; // not needed
const SWITCHES_NOCONFIRM: Switches = &[]; // not needed
const SWITCHES_REMOVE: Switches = &["uninstall"];
const SUPPORTS_AS_DEPENDENCY: bool = false;
impl Backend for Python {
impl_backend_constants!();
fn get_binary(&self) -> Text {
let r#box = self.binary.clone().into_boxed_str();
Box::leak(r#box)
fn backend_info(&self) -> BackendInfo {
BackendInfo {
binary: self.binary.clone(),
section: "python",
switches_info: &["show"],
switches_install: &["install"],
switches_noconfirm: &[],
switches_remove: &["uninstall"],
switches_make_dependency: None,
}
}
fn get_all_installed_packages(&self) -> Result<HashSet<Package>> {
let mut cmd = Command::new(self.get_binary());
fn get_all_installed_packages(&self) -> Result<Packages> {
let mut cmd = Command::new(self.backend_info().binary);
let output = run_pip_command(&mut cmd, self.get_switches_runtime())?;
self.extract_packages(output)
}
fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>> {
let mut cmd = Command::new(self.get_binary());
fn get_explicitly_installed_packages(&self) -> Result<Packages> {
let mut cmd = Command::new(self.backend_info().binary);
let output = run_pip_command(&mut cmd, self.get_switches_explicit())?;
self.extract_packages(output)
}
fn make_dependency(&self, _packages: &[Package]) -> Result<()> {
panic!("not supported by {}", BINARY)
fn make_dependency(&self, _packages: &Packages) -> Result<()> {
panic!("not supported by {}", self.binary)
}
}
@@ -100,7 +84,7 @@ fn run_pip_command(cmd: &mut Command, args: &[&str]) -> Result<Value> {
Ok(val)
}
fn extract_pacdef_packages(value: Value) -> Result<HashSet<Package>> {
fn extract_pacdef_packages(value: Value) -> Result<Packages> {
let result = value
.as_array()
.context("getting inner json array")?
@@ -111,7 +95,7 @@ fn extract_pacdef_packages(value: Value) -> Result<HashSet<Package>> {
Ok(result)
}
fn extract_pacdef_packages_pipx(value: Value) -> Result<HashSet<Package>> {
fn extract_pacdef_packages_pipx(value: Value) -> Result<Packages> {
let result = value["venvs"]
.as_object()
.context("getting inner json object")?
+23 -30
View File
@@ -1,4 +1,3 @@
use std::collections::HashSet;
use std::fs::read_to_string;
use std::io::ErrorKind::NotFound;
use std::path::PathBuf;
@@ -6,19 +5,13 @@ use std::path::PathBuf;
use anyhow::{bail, Context, Result};
use serde_json::Value;
use crate::backend::backend_trait::{Backend, Switches, Text};
use crate::backend::macros::impl_backend_constants;
use crate::Package;
use crate::prelude::*;
#[derive(Debug, Clone)]
pub struct Rust {
pub packages: HashSet<Package>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Rust {}
impl Rust {
pub fn new() -> Self {
Self {
packages: HashSet::new(),
}
Self {}
}
}
impl Default for Rust {
@@ -27,28 +20,27 @@ impl Default for Rust {
}
}
const BINARY: Text = "cargo";
const SECTION: Text = "rust";
const SWITCHES_INSTALL: Switches = &["install"];
const SWITCHES_INFO: Switches = &["search", "--limit", "1"];
const SWITCHES_MAKE_DEPENDENCY: Switches = &[];
const SWITCHES_NOCONFIRM: Switches = &[]; // not needed
const SWITCHES_REMOVE: Switches = &["uninstall"];
const SUPPORTS_AS_DEPENDENCY: bool = false;
impl Backend for Rust {
impl_backend_constants!();
fn backend_info(&self) -> BackendInfo {
BackendInfo {
binary: "cargo".to_string(),
section: "rust",
switches_info: &["search", "--limit", "1"],
switches_install: &["install"],
switches_noconfirm: &[],
switches_remove: &["uninstall"],
switches_make_dependency: None,
}
}
fn get_all_installed_packages(&self) -> Result<HashSet<Package>> {
fn get_all_installed_packages(&self) -> Result<Packages> {
let file = get_crates_file().context("getting path to crates file")?;
let content = match read_to_string(file) {
Ok(string) => string,
Err(err) if err.kind() == NotFound => {
log::warn!("no crates file found for cargo. Assuming no crates installed yet.");
return Ok(HashSet::new());
return Ok(Packages::new());
}
Err(err) => bail!(err),
};
@@ -58,18 +50,18 @@ impl Backend for Rust {
extract_packages(&json).context("extracting packages from crates file")
}
fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>> {
fn get_explicitly_installed_packages(&self) -> Result<Packages> {
self.get_all_installed_packages()
.context("getting all installed packages")
}
fn make_dependency(&self, _: &[Package]) -> Result<()> {
panic!("not supported by {}", BINARY)
fn make_dependency(&self, _: &Packages) -> Result<()> {
panic!("not supported by {}", self.backend_info().binary)
}
}
fn extract_packages(json: &Value) -> Result<HashSet<Package>> {
let result: HashSet<_> = json
fn extract_packages(json: &Value) -> Result<Packages> {
let result: Packages = json
.get("installs")
.context("get 'installs' field from json")?
.as_object()
@@ -83,6 +75,7 @@ fn extract_packages(json: &Value) -> Result<HashSet<Package>> {
})
.map(|name| Package::try_from(name).expect("name is valid"))
.collect();
Ok(result)
}
+30 -38
View File
@@ -1,12 +1,9 @@
mod helpers;
mod types;
use crate::backend::backend_trait::{Backend, Switches, Text};
use crate::backend::macros::impl_backend_constants;
use crate::cmd::run_external_command;
use crate::Package;
use crate::prelude::*;
use anyhow::{bail, Context, Result};
use std::collections::HashSet;
use std::process::Command;
use self::helpers::{
@@ -14,15 +11,11 @@ use self::helpers::{
};
use self::types::{Repotype, RustupPackage};
#[derive(Debug, Clone)]
pub struct Rustup {
pub packages: HashSet<Package>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Rustup {}
impl Rustup {
pub fn new() -> Self {
Self {
packages: HashSet::new(),
}
Self {}
}
}
impl Default for Rustup {
@@ -31,38 +24,37 @@ impl Default for Rustup {
}
}
const BINARY: Text = "rustup";
const SECTION: Text = "rustup";
const SWITCHES_INSTALL: Switches = &["component", "add"];
const SWITCHES_INFO: Switches = &["component", "list", "--installed"];
const SWITCHES_MAKE_DEPENDENCY: Switches = &[];
const SWITCHES_NOCONFIRM: Switches = &[];
const SWITCHES_REMOVE: Switches = &["component", "remove"];
const SUPPORTS_AS_DEPENDENCY: bool = false;
impl Backend for Rustup {
impl_backend_constants!();
fn backend_info(&self) -> BackendInfo {
BackendInfo {
binary: "rustup".to_string(),
section: "rustup",
switches_install: &["component", "add"],
switches_info: &["component", "list", "--installed"],
switches_noconfirm: &[],
switches_remove: &["component", "remove"],
switches_make_dependency: None,
}
}
fn get_all_installed_packages(&self) -> Result<HashSet<Package>> {
fn get_all_installed_packages(&self) -> Result<Packages> {
let toolchains_vec = self
.run_toolchain_command(Repotype::Toolchain.get_info_switches())
.context("Getting installed toolchains")?;
let toolchains: HashSet<Package> = toolchains_vec
let toolchains: Packages = toolchains_vec
.iter()
.map(|name| ["toolchain", name].join("/").into())
.collect();
let components: HashSet<Package> = self
let components: Packages = self
.run_component_command(Repotype::Component.get_info_switches(), &toolchains_vec)
.context("Getting installed components")?
.iter()
.map(|name| ["component", name].join("/").into())
.collect();
let mut packages = HashSet::new();
let mut packages = Packages::new();
packages.extend(toolchains);
packages.extend(components);
@@ -70,16 +62,16 @@ impl Backend for Rustup {
Ok(packages)
}
fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>> {
fn get_explicitly_installed_packages(&self) -> Result<Packages> {
self.get_all_installed_packages()
.context("Getting all installed packages")
}
fn make_dependency(&self, _: &[Package]) -> Result<()> {
panic!("Not supported by {}", self.get_binary())
fn make_dependency(&self, _: &Packages) -> Result<()> {
panic!("Not supported by {}", self.backend_info().binary)
}
fn install_packages(&self, packages: &[Package], _: bool) -> Result<()> {
fn install_packages(&self, packages: &Packages, _: bool) -> Result<()> {
let packages = RustupPackage::from_pacdef_packages(packages)?;
let (toolchains, components) =
@@ -91,7 +83,7 @@ impl Backend for Rustup {
Ok(())
}
fn remove_packages(&self, packages: &[Package], _: bool) -> Result<()> {
fn remove_packages(&self, packages: &Packages, _: bool) -> Result<()> {
let rustup_packages = RustupPackage::from_pacdef_packages(packages)?;
let (toolchains, components) =
@@ -110,7 +102,7 @@ impl Rustup {
let mut val = Vec::new();
for toolchain in toolchains {
let mut cmd = Command::new(self.get_binary());
let mut cmd = Command::new(self.backend_info().binary);
cmd.args(args).arg(toolchain);
let output = String::from_utf8(cmd.output()?.stdout)?;
@@ -124,7 +116,7 @@ impl Rustup {
}
fn run_toolchain_command(&self, args: &[&str]) -> Result<Vec<String>> {
let mut cmd = Command::new(self.get_binary());
let mut cmd = Command::new(self.backend_info().binary);
cmd.args(args);
let output = String::from_utf8(cmd.output()?.stdout)?;
@@ -146,7 +138,7 @@ impl Rustup {
if toolchains.is_empty() {
return Ok(());
}
let mut cmd = Command::new(self.get_binary());
let mut cmd = Command::new(self.backend_info().binary);
cmd.args(Repotype::Toolchain.get_install_switches());
for toolchain in toolchains {
@@ -166,7 +158,7 @@ impl Rustup {
let components_by_toolchain = group_components_by_toolchains(components);
for components_for_one_toolchain in components_by_toolchain {
let mut cmd = Command::new(self.get_binary());
let mut cmd = Command::new(self.backend_info().binary);
cmd.args(Repotype::Component.get_install_switches());
let the_toolchain = &components_for_one_toolchain
@@ -195,7 +187,7 @@ impl Rustup {
fn remove_toolchains(&self, toolchains: Vec<RustupPackage>) -> Result<Vec<String>> {
let mut removed_toolchains = vec![];
if !toolchains.is_empty() {
let mut cmd = Command::new(self.get_binary());
let mut cmd = Command::new(self.backend_info().binary);
cmd.args(Repotype::Toolchain.get_remove_switches());
for toolchain_package in &toolchains {
@@ -216,7 +208,7 @@ impl Rustup {
removed_toolchains: Vec<String>,
) -> Result<()> {
for component_package in components {
let mut cmd = Command::new(self.get_binary());
let mut cmd = Command::new(self.backend_info().binary);
cmd.args(Repotype::Component.get_remove_switches());
if toolchain_of_component_was_already_removed(&removed_toolchains, &component_package) {
@@ -1,6 +1,6 @@
use anyhow::{bail, Context, Result};
use crate::{backend::backend_trait::Switches, Package};
use crate::prelude::*;
#[derive(Debug)]
pub enum Repotype {
@@ -95,7 +95,7 @@ impl RustupPackage {
(toolchains, components)
}
pub fn from_pacdef_packages(packages: &[Package]) -> Result<Vec<Self>> {
pub fn from_pacdef_packages(packages: &Packages) -> Result<Vec<Self>> {
let mut result = vec![];
for package in packages {
+38 -33
View File
@@ -1,24 +1,17 @@
use std::collections::HashSet;
use std::process::Command;
use anyhow::Result;
use regex::Regex;
use crate::backend::backend_trait::{Backend, Switches, Text};
use crate::backend::macros::impl_backend_constants;
use crate::backend::root::build_base_command_with_privileges;
use crate::cmd::run_external_command;
use crate::Package;
use crate::prelude::*;
#[derive(Debug, Clone)]
pub struct Void {
pub packages: HashSet<Package>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Void {}
impl Void {
pub fn new() -> Self {
Self {
packages: HashSet::new(),
}
Self {}
}
}
impl Default for Void {
@@ -27,25 +20,25 @@ impl Default for Void {
}
}
const BINARY: Text = "xbps-install";
const INSTALL_BINARY: Text = "xbps-install";
const REMOVE_BINARY: Text = "xbps-remove";
const QUERY_BINARY: Text = "xbps-query";
const PKGDB_BINARY: Text = "xbps-pkgdb";
const SECTION: Text = "void";
const SWITCHES_INFO: Switches = &[];
const SWITCHES_INSTALL: Switches = &["-S"];
const SWITCHES_MAKE_DEPENDENCY: Switches = &["-m", "auto"];
const SWITCHES_NOCONFIRM: Switches = &["-y"];
const SWITCHES_REMOVE: Switches = &["-R"];
const SUPPORTS_AS_DEPENDENCY: bool = true;
impl Backend for Void {
impl_backend_constants!();
fn backend_info(&self) -> BackendInfo {
BackendInfo {
binary: "xbps-install".to_string(),
section: "void",
switches_info: &[],
switches_install: &["-S"],
switches_noconfirm: &["-y"],
switches_remove: &["-R"],
switches_make_dependency: Some(&["-m", "auto"]),
}
}
fn get_all_installed_packages(&self) -> Result<HashSet<Package>> {
fn get_all_installed_packages(&self) -> Result<Packages> {
// Removes the package status and description from output
let re_str_1 = r"^ii |^uu |^hr |^\?\? | .*";
// Removes the package version from output
@@ -67,7 +60,7 @@ impl Backend for Void {
Ok(packages)
}
fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>> {
fn get_explicitly_installed_packages(&self) -> Result<Packages> {
// Removes the package version from output
let re_str = r"-[^-]*$";
let re = Regex::new(re_str)?;
@@ -86,12 +79,14 @@ impl Backend for Void {
}
/// Install the specified packages.
fn install_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> {
fn install_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = build_base_command_with_privileges(INSTALL_BINARY);
cmd.args(self.get_switches_install());
cmd.args(backend_info.switches_install);
if noconfirm {
cmd.args(self.get_switches_noconfirm());
cmd.args(backend_info.switches_noconfirm);
}
for p in packages {
@@ -101,12 +96,14 @@ impl Backend for Void {
run_external_command(cmd)
}
fn remove_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> {
fn remove_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = build_base_command_with_privileges(REMOVE_BINARY);
cmd.args(self.get_switches_remove());
cmd.args(backend_info.switches_remove);
if noconfirm {
cmd.args(self.get_switches_noconfirm());
cmd.args(backend_info.switches_noconfirm);
}
for p in packages {
@@ -116,9 +113,15 @@ impl Backend for Void {
run_external_command(cmd)
}
fn make_dependency(&self, packages: &[Package]) -> Result<()> {
fn make_dependency(&self, packages: &Packages) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = build_base_command_with_privileges(PKGDB_BINARY);
cmd.args(self.get_switches_make_dependency());
cmd.args(
backend_info
.switches_make_dependency
.expect("void should support make make dependency"),
);
for p in packages {
cmd.arg(format!("{p}"));
@@ -129,8 +132,10 @@ impl Backend for Void {
/// Show information from package manager for package.
fn show_package_info(&self, package: &Package) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = Command::new(QUERY_BINARY);
cmd.args(self.get_switches_info());
cmd.args(backend_info.switches_info);
cmd.arg(format!("{package}"));
run_external_command(cmd)
+77 -108
View File
@@ -1,68 +1,52 @@
use std::cmp::{Eq, Ord};
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use std::collections::BTreeMap;
use std::process::Command;
use anyhow::{Context, Result};
use anyhow::Result;
use crate::cmd::run_external_command;
use crate::{Group, Groups, Package};
use crate::prelude::*;
pub type Switches = &'static [&'static str];
pub type Text = &'static str;
/// A bundle of small of bits of info associated with a backend.
pub struct BackendInfo {
/// The binary name when calling the backend.
pub binary: String,
/// The name of the section in the group files.
pub section: Text,
/// CLI switches for the package manager to show information for
/// packages.
pub switches_info: Switches,
/// CLI switches for the package manager to install packages.
pub switches_install: Switches,
/// CLI switches for the package manager to perform `sync` and `clean` without
/// confirmation.
pub switches_noconfirm: Switches,
/// CLI switches for the package manager to remove packages.
pub switches_remove: Switches,
/// CLI switches for the package manager to mark packages as
/// dependency. This is not supported by all package managers.
pub switches_make_dependency: Option<Switches>,
}
/// The trait of a struct that is used as a backend.
#[enum_dispatch::enum_dispatch]
pub trait Backend {
/// 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 [`BackendInfo`] associated with this backend.
fn backend_info(&self) -> BackendInfo;
fn supports_as_dependency(&self) -> bool {
self.backend_info().switches_make_dependency.is_some()
}
/// 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: &Groups);
/// 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<Package>;
/// 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<HashSet<Package>>;
fn get_all_installed_packages(&self) -> Result<Packages>;
/// Get all packages that were installed in the system explicitly.
///
@@ -70,13 +54,22 @@ pub trait Backend {
///
/// This function shall return an error if the explicitly installed packages
/// cannot be determined.
fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>>;
fn get_explicitly_installed_packages(&self) -> Result<Packages>;
/// Assign each of the packages to an individual group by editing the
/// group files.
///
/// # Errors
///
/// Returns an Error if any of the groups fails to save their given packages.
fn assign_group(&self, to_assign: Vec<(Package, Group)>) -> Result<()> {
let group_package_map = to_hashmap(to_assign);
let section_header = format!("[{}]", self.get_section());
let mut group_package_map: BTreeMap<Group, Packages> = BTreeMap::new();
for (package, group) in to_assign {
group_package_map.entry(group).or_default().insert(package);
}
let section_header = format!("[{}]", self.backend_info().section);
for (group, packages) in group_package_map {
group.save_packages(&section_header, &packages)?;
@@ -92,12 +85,14 @@ pub trait Backend {
///
/// 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());
fn install_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = Command::new(self.backend_info().binary);
cmd.args(backend_info.switches_install);
if noconfirm {
cmd.args(self.get_switches_noconfirm());
cmd.args(backend_info.switches_noconfirm);
}
for p in packages {
@@ -113,9 +108,18 @@ pub trait Backend {
/// # Panics
///
/// This method shall panic when the backend does not support dependent packages.
fn make_dependency(&self, packages: &[Package]) -> Result<()> {
let mut cmd = Command::new(self.get_binary());
cmd.args(self.get_switches_make_dependency());
///
/// # Errors
///
/// Returns an error if the external command fails.
fn make_dependency(&self, packages: &Packages) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = Command::new(backend_info.binary);
if let Some(switches_make_dependency) = backend_info.switches_make_dependency {
cmd.args(switches_make_dependency);
}
for p in packages {
cmd.arg(format!("{p}"));
@@ -126,12 +130,17 @@ pub trait Backend {
/// 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());
/// # Errors
///
/// Returns an error if the external command fails.
fn remove_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = Command::new(backend_info.binary);
cmd.args(backend_info.switches_remove);
if noconfirm {
cmd.args(self.get_switches_noconfirm());
cmd.args(backend_info.switches_noconfirm);
}
for p in packages {
@@ -141,58 +150,18 @@ pub trait Backend {
run_external_command(cmd)
}
/// Get missing packages, sorted alphabetically.
fn get_missing_packages_sorted(&self) -> Result<Vec<Package>> {
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.
///
/// # Errors
///
/// Returns an error if the external command fails.
fn show_package_info(&self, package: &Package) -> Result<()> {
let mut cmd = Command::new(self.get_binary());
cmd.args(self.get_switches_info());
let backend_info = self.backend_info();
let mut cmd = Command::new(backend_info.binary);
cmd.args(backend_info.switches_info);
cmd.arg(format!("{package}"));
run_external_command(cmd)
}
/// Get unmanaged packages, sorted alphabetically.
fn get_unmanaged_packages_sorted(&self) -> Result<Vec<Package>> {
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)
}
/// 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<V>`.
fn to_hashmap<K, V>(to_assign: Vec<(V, K)>) -> HashMap<K, Vec<V>>
where
K: Hash + Eq,
V: Ord,
{
let mut map = HashMap::new();
for (value, key) in to_assign {
let inner: &mut Vec<V> = map.entry(key).or_default();
inner.push(value);
}
for vecs in map.values_mut() {
vecs.sort_unstable();
}
map
}
-56
View File
@@ -1,56 +0,0 @@
/// Used to implement parts of the `Backend` trait that should not change between the actual
/// backends (boilerplate).
macro_rules! impl_backend_constants {
() => {
fn get_binary_default(&self) -> Text {
BINARY
}
fn get_section(&self) -> Text {
SECTION
}
fn get_switches_info(&self) -> Switches {
SWITCHES_INFO
}
fn get_switches_install(&self) -> Switches {
SWITCHES_INSTALL
}
fn get_switches_noconfirm(&self) -> Switches {
SWITCHES_NOCONFIRM
}
fn get_switches_remove(&self) -> Switches {
SWITCHES_REMOVE
}
fn get_switches_make_dependency(&self) -> Switches {
SWITCHES_MAKE_DEPENDENCY
}
fn get_managed_packages(&self) -> &HashSet<Package> {
&self.packages
}
fn load(&mut self, groups: &crate::Groups) {
let own_section_name = self.get_section();
groups
.iter()
.flat_map(|g| &g.sections)
.filter(|section| section.name == own_section_name)
.flat_map(|section| &section.packages)
.for_each(|package| {
self.packages.insert(package.clone());
})
}
fn supports_as_dependency(&self) -> bool {
SUPPORTS_AS_DEPENDENCY
}
};
}
pub(crate) use impl_backend_constants;
+73 -18
View File
@@ -1,23 +1,56 @@
pub mod actual;
pub mod backend_trait;
pub mod macros;
mod root;
pub mod todo_per_backend;
use crate::backend::backend_trait::Switches;
use crate::backend::backend_trait::Text;
use crate::Group;
use crate::Groups;
use crate::Package;
use anyhow::Result;
use backend_trait::Backend;
use std::collections::HashSet;
use std::fmt::Display;
use self::actual::{
fedora::Fedora, flatpak::Flatpak, python::Python, rust::Rust, rustup::Rustup, void::Void,
};
use crate::prelude::*;
use anyhow::{Context, Result};
#[derive(Debug)]
/// A backend with its associated managed packages
pub struct ManagedBackend {
/// All managed packages for this backend, i.e. all packages
/// under the corresponding section in all group files.
pub packages: Packages,
pub any_backend: AnyBackend,
}
impl ManagedBackend {
/// Get unmanaged packages
///
/// # Errors
///
/// Returns an error if the backend fails to get the explicitly installed packages.
pub fn get_unmanaged_packages_sorted(&self) -> Result<Packages> {
let installed = self
.any_backend
.get_explicitly_installed_packages()
.context("could not get explicitly installed packages")?;
let diff = installed.difference(&self.packages).cloned().collect();
Ok(diff)
}
/// Get missing packages
///
/// # Errors
///
/// Returns an error if the backend fails to get the installed packages.
pub fn get_missing_packages_sorted(&self) -> Result<Packages> {
let installed = self
.any_backend
.get_all_installed_packages()
.context("could not get installed packages")?;
let diff = self.packages.difference(&installed).cloned().collect();
Ok(diff)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[enum_dispatch::enum_dispatch(Backend)]
pub enum AnyBackend {
#[cfg(feature = "arch")]
@@ -31,22 +64,44 @@ pub enum AnyBackend {
Rustup(Rustup),
Void(Void),
}
impl AnyBackend {
/// Returns an iterator of every variant of backend.
pub fn iter() -> impl Iterator<Item = Self> {
pub fn all(config: &Config) -> impl Iterator<Item = Self> {
vec![
#[cfg(feature = "arch")]
Self::Arch(actual::arch::Arch::new()),
Self::Arch(actual::arch::Arch::new(config)),
#[cfg(feature = "debian")]
Self::Debian(actual::debian::Debian::new()),
Self::Flatpak(Flatpak::new()),
Self::Flatpak(Flatpak::new(config)),
Self::Fedora(Fedora::new()),
Self::Python(Python::new()),
Self::Python(Python::new(config)),
Self::Rust(Rust::new()),
Self::Rustup(Rustup::new()),
Self::Void(Void::new()),
]
.into_iter()
}
pub fn from_section(section: &str, config: &Config) -> Result<Self> {
match section {
#[cfg(feature = "arch")]
"arch" => Ok(Self::Arch(actual::arch::Arch::new(config))),
#[cfg(feature = "debian")]
"debian" => Ok(Self::Debian(actual::debian::Debian::new())),
"flatpak" => Ok(Self::Flatpak(Flatpak::new(config))),
"fedora" => Ok(Self::Fedora(Fedora::new())),
"python" => Ok(Self::Python(Python::new(config))),
"rust" => Ok(Self::Rust(Rust::new())),
"rustup" => Ok(Self::Rustup(Rustup::new())),
"void" => Ok(Self::Void(Void::new())),
_ => Err(anyhow::anyhow!(
"no matching backend for the section: {section}"
)),
}
}
}
impl Display for AnyBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.backend_info().section)
}
}
@@ -2,8 +2,7 @@ use std::fmt::Write;
use anyhow::{Context, Result};
use super::{AnyBackend, Backend};
use crate::Package;
use crate::prelude::*;
/// A vector of tuples containing a Backends and a vector of unmanaged packages
/// for that backend.
@@ -11,17 +10,17 @@ use crate::Package;
/// This struct is used to store a list of unmanaged packages or missing packages
/// for all backends.
#[derive(Debug)]
pub struct ToDoPerBackend(Vec<(AnyBackend, Vec<Package>)>);
pub struct ToDoPerBackend(Vec<(AnyBackend, Packages)>);
impl ToDoPerBackend {
pub fn new() -> Self {
Self(vec![])
}
pub fn push(&mut self, item: (AnyBackend, Vec<Package>)) {
pub fn push(&mut self, item: (AnyBackend, Packages)) {
self.0.push(item);
}
pub fn iter(&self) -> impl Iterator<Item = &(AnyBackend, Vec<Package>)> {
pub fn iter(&self) -> impl Iterator<Item = &(AnyBackend, Packages)> {
self.0.iter()
}
@@ -37,7 +36,7 @@ impl ToDoPerBackend {
backend
.install_packages(packages, noconfirm)
.with_context(|| format!("installing packages for {}", backend.get_section()))?;
.with_context(|| format!("installing packages for {backend}"))?;
}
Ok(())
}
@@ -50,7 +49,7 @@ impl ToDoPerBackend {
backend
.remove_packages(packages, noconfirm)
.with_context(|| format!("removing packages for {}", backend.get_section()))?;
.with_context(|| format!("removing packages for {backend}"))?;
}
Ok(())
}
@@ -65,7 +64,7 @@ impl ToDoPerBackend {
let mut segment = String::new();
segment.write_str(&format!("[{}]", backend.get_section()))?;
segment.write_str(&format!("[{backend}]"))?;
for package in packages {
segment.write_str(&format!("\n{package}"))?;
}
@@ -95,7 +94,7 @@ impl Default for ToDoPerBackend {
}
impl IntoIterator for ToDoPerBackend {
type Item = (AnyBackend, Vec<Package>);
type Item = (AnyBackend, Packages);
type IntoIter = std::vec::IntoIter<Self::Item>;
+3 -1
View File
@@ -5,6 +5,8 @@ use std::path::Path;
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use crate::prelude::*;
// Update the master README if fields change.
/// Config for the program, as listed in `$XDG_CONFIG_HOME/pacdef/pacdef.toml`.
#[derive(Debug, Serialize, Deserialize)]
@@ -55,7 +57,7 @@ impl Config {
Ok(content) => content,
Err(e) => {
if e.kind() == ErrorKind::NotFound {
bail!(crate::Error::ConfigFileNotFound)
bail!(Error::ConfigFileNotFound)
}
bail!("unexpected error occurred: {e:?}");
}
+43 -65
View File
@@ -8,23 +8,14 @@ use std::process::Command;
use anyhow::{bail, ensure, Context, Result};
use const_format::formatcp;
use crate::backend::backend_trait::Backend;
use crate::backend::todo_per_backend::ToDoPerBackend;
use crate::backend::AnyBackend;
use crate::cli::{
CleanPackageAction, EditGroupAction, ExportGroupAction, GroupAction, GroupArguments,
ImportGroupAction, ListGroupAction, MainArguments, MainSubcommand, NewGroupAction,
PackageAction, PackageArguments, RemoveGroupAction, ReviewPackageAction, SearchPackageAction,
ShowGroupAction, SyncPackageAction, UnmanagedPackageAction, VersionArguments,
};
use crate::cmd::{run_edit_command, run_external_command};
use crate::env::{get_editor, should_print_debug_info};
use crate::grouping::group::groups_to_backend_packages;
use crate::path::{binary_in_path, get_absolutized_file_paths, get_group_dir};
use crate::prelude::*;
use crate::review::review;
use crate::search::search_packages;
use crate::ui::get_user_confirmation;
use crate::Group;
use crate::{Config, Error, Groups};
impl MainArguments {
/// Run the action that was provided by the user as first argument.
@@ -39,7 +30,7 @@ impl MainArguments {
match self.subcommand {
MainSubcommand::Group(group) => group.run(groups),
MainSubcommand::Package(package) => package.run(groups, config),
MainSubcommand::Version(version) => version.run(),
MainSubcommand::Version(version) => version.run(config),
}
}
}
@@ -47,8 +38,8 @@ impl MainArguments {
impl VersionArguments {
/// If the crate was compiled from git, return `pacdef, <version> (<hash>)`.
/// Otherwise return `pacdef, <version>`.
fn run(self) -> Result<()> {
let backends = get_included_backends();
fn run(self, config: &Config) -> Result<()> {
let backends = get_included_backends(config);
let mut result = format!("pacdef, version: {}\n", get_version_string());
result.push_str("supported backends:");
for b in backends {
@@ -221,7 +212,7 @@ impl NewGroupAction {
for new_group in &self.new_groups {
ensure!(
new_group != "." && new_group != "..",
crate::Error::InvalidGroupName(new_group.clone())
Error::InvalidGroupName(new_group.clone())
);
}
@@ -236,10 +227,7 @@ impl NewGroupAction {
.collect();
for file in &paths {
ensure!(
!file.exists(),
crate::Error::GroupAlreadyExists(file.clone())
);
ensure!(!file.exists(), Error::GroupAlreadyExists(file.clone()));
}
for file in &paths {
@@ -284,10 +272,7 @@ impl ShowGroupAction {
}
// return an error if any arg was not found
ensure!(
errors.is_empty(),
crate::Error::MultipleGroupsNotFound(errors)
);
ensure!(errors.is_empty(), Error::MultipleGroupsNotFound(errors));
let show_more_than_one_group = self.show_groups.len() > 1;
@@ -398,50 +383,38 @@ impl UnmanagedPackageAction {
}
fn get_missing_packages(groups: &Groups, config: &Config) -> Result<ToDoPerBackend> {
let backend_packages = groups_to_backend_packages(groups, config)?;
let mut to_install = ToDoPerBackend::new();
for mut backend in AnyBackend::iter() {
for (any_backend, packages) in &backend_packages {
let backend_info = any_backend.backend_info();
if config
.disabled_backends
.contains(&backend.get_section().to_string())
.contains(&backend_info.section.to_string())
{
continue;
}
if !binary_in_path(backend.get_binary())? {
if !binary_in_path(&backend_info.binary)? {
continue;
}
overwrite_values_from_config(&mut backend, config);
backend.load(groups);
let managed_backend = ManagedBackend {
packages: packages.clone(),
any_backend: any_backend.clone(),
};
match backend.get_missing_packages_sorted() {
Ok(diff) => to_install.push((backend, diff)),
Err(error) => show_backend_query_error(&error, &backend),
match managed_backend.get_missing_packages_sorted() {
Ok(diff) => to_install.push((any_backend.clone(), diff)),
Err(error) => show_backend_query_error(&error, any_backend),
};
}
Ok(to_install)
}
fn overwrite_values_from_config(backend: &mut AnyBackend, config: &Config) {
#[cfg(feature = "arch")]
{
if let AnyBackend::Arch(arch) = backend {
arch.binary.clone_from(&config.aur_helper);
arch.aur_rm_args.clone_from(&config.aur_rm_args);
}
}
if let AnyBackend::Flatpak(flatpak) = backend {
flatpak.systemwide = config.flatpak_systemwide;
}
if let AnyBackend::Python(python) = backend {
python.binary.clone_from(&config.pip_binary);
}
}
/// Get a list of unmanaged packages per backend.
///
/// This method loops through all enabled `Backend`s whose binary is in `PATH`.
@@ -450,29 +423,35 @@ fn overwrite_values_from_config(backend: &mut AnyBackend, config: &Config) {
///
/// This function will propagate errors from the individual backends.
fn get_unmanaged_packages(groups: &Groups, config: &Config) -> Result<ToDoPerBackend> {
let mut result = ToDoPerBackend::new();
let backend_packages = groups_to_backend_packages(groups, config)?;
for mut backend in AnyBackend::iter() {
let mut todo_unmanaged = ToDoPerBackend::new();
for (any_backend, packages) in &backend_packages {
let backend_info = any_backend.backend_info();
if config
.disabled_backends
.contains(&backend.get_section().to_string())
.contains(&backend_info.section.to_string())
{
continue;
}
if !binary_in_path(backend.get_binary())? {
if !binary_in_path(&backend_info.binary)? {
continue;
}
overwrite_values_from_config(&mut backend, config);
backend.load(groups);
let managed_backend = ManagedBackend {
packages: packages.clone(),
any_backend: any_backend.clone(),
};
match backend.get_unmanaged_packages_sorted() {
Ok(unmanaged) => result.push((backend, unmanaged)),
Err(error) => show_backend_query_error(&error, &backend),
match managed_backend.get_unmanaged_packages_sorted() {
Ok(unmanaged) => todo_unmanaged.push((any_backend.clone(), unmanaged)),
Err(error) => show_backend_query_error(&error, any_backend),
};
}
Ok(result)
Ok(todo_unmanaged)
}
/// Create the parent directory of the `path` if that directory does not exist.
@@ -553,14 +532,13 @@ fn find_groups_by_name<'a>(names: &[String], groups: &'a Groups) -> Result<Vec<&
/// Show the error chain for an error that has occurred when a backend was queried
/// if the `RUST_BACKTRACE` env variable is set to `1` or `full`.
fn show_backend_query_error(error: &anyhow::Error, backend: &AnyBackend) {
let section = backend.get_section();
if should_print_debug_info() {
log::warn!(
"skipping backend '{section}': {}",
"skipping backend '{backend}': {}",
error.chain().map(|x| x.to_string()).collect::<String>()
);
} else {
log::warn!("skipping backend '{section}': {error}");
log::warn!("skipping backend '{backend}': {error}");
}
}
@@ -578,10 +556,10 @@ pub const fn get_version_string() -> &'static str {
}
/// Get a vector with the names of all backends, sorted alphabetically.
fn get_included_backends() -> Vec<&'static str> {
fn get_included_backends(config: &Config) -> Vec<&'static str> {
let mut result = vec![];
for backend in AnyBackend::iter() {
result.push(backend.get_section());
for backend in AnyBackend::all(config) {
result.push(backend.backend_info().section);
}
result.sort_unstable();
result
+23 -7
View File
@@ -1,4 +1,4 @@
use std::collections::{BTreeSet, HashSet};
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Display;
use std::fs::{create_dir, read_to_string, File};
use std::hash::Hash;
@@ -11,11 +11,27 @@ use walkdir::WalkDir;
use crate::path::get_relative_path;
use super::{Package, Section};
use crate::prelude::*;
/// A set of groups
pub type Groups = BTreeSet<Group>;
pub type BackendPackages = BTreeMap<AnyBackend, Packages>;
pub fn groups_to_backend_packages(groups: &Groups, config: &Config) -> Result<BackendPackages> {
let mut backend_packages = BackendPackages::new();
for group in groups {
for section in &group.sections {
backend_packages
.entry(AnyBackend::from_section(&section.name, config)?)
.or_default()
.extend(section.packages.iter().cloned());
}
}
Ok(backend_packages)
}
/// Representation of a group file.
#[derive(Debug, Clone)]
pub struct Group {
@@ -23,7 +39,7 @@ pub struct Group {
/// base dir).
pub name: String,
/// The sections in the file which in turn hold the packages.
pub sections: HashSet<Section>,
pub sections: Sections,
/// The absolute path of the original file.
pub path: PathBuf,
/// Whether the main program should warn this group being loaded from a symlink.
@@ -143,7 +159,7 @@ impl Group {
let name = extract_group_name(path, group_dir.as_ref());
let mut lines = content.lines().peekable();
let mut sections = HashSet::new();
let mut sections = Sections::new();
while lines.peek().is_some() {
let result = Section::try_from_lines(&mut lines).context("reading section");
@@ -180,7 +196,7 @@ impl Group {
///
/// This function returns an error if the group file cannot be read, or if the
/// file cannot be written to.
pub fn save_packages(&self, section_header: &str, packages: &[Package]) -> Result<()> {
pub fn save_packages(&self, section_header: &str, packages: &Packages) -> Result<()> {
let mut content = read_to_string(&self.path)
.with_context(|| format!("reading existing file contents from {:?}", &self.path))?;
@@ -248,7 +264,7 @@ impl Display for Group {
fn write_packages_to_existing_section(
group_file_content: &mut String,
section_header: &str,
packages: &[Package],
packages: &Packages,
) -> Result<()> {
let idx_of_first_package_line_in_section =
find_first_package_line_in_section(group_file_content, section_header)?;
@@ -290,7 +306,7 @@ fn find_first_package_line_in_section(
fn add_new_section_with_packages(
group_file_content: &mut String,
section_header: &str,
packages: &[Package],
packages: &Packages,
) {
group_file_content.push('\n');
group_file_content.push_str(section_header);
+3 -8
View File
@@ -9,11 +9,6 @@ all groups using [`Group::load`], which in turn will get all packages from all
sections.
*/
mod group;
mod package;
mod section;
pub use group::Group;
pub use group::Groups;
pub use package::Package;
pub use section::Section;
pub mod group;
pub mod package;
pub mod section;
+23 -17
View File
@@ -1,9 +1,12 @@
use std::cmp::Ordering;
use std::collections::BTreeSet;
use std::fmt::{Display, Write};
use std::hash::Hash;
pub type Packages = BTreeSet<Package>;
/// A struct to represent a single package, consisting of a `name`, and
/// optionally a `repo`.
#[derive(Debug, Eq, PartialOrd, Ord, Clone)]
#[derive(Debug, Clone)]
pub struct Package {
/// The name of the package
pub name: String,
@@ -51,7 +54,7 @@ impl Package {
}
/// Try to parse a string (from a line in a group file) and return a package.
/// From the string, any possible comment is removed and whitespace is trimmed.
/// From the string, any possible comment is removed and whitespace is trimmed.package
/// Returns `None` if there is nothing left after trimming.
pub fn try_from<S>(s: S) -> Option<Self>
where
@@ -69,22 +72,25 @@ impl Package {
impl PartialEq for Package {
fn eq(&self, other: &Self) -> bool {
let self_repo = self.repo.as_ref();
let other_repo = other.repo.as_ref();
// iff both packages have repos, they must be identical, otherwise we don't care
let repos_are_identical =
self_repo.map_or(true, |sr| other_repo.map_or(true, |or| sr == or));
let names_are_identical = self.name == other.name;
names_are_identical && repos_are_identical
self.cmp(other).is_eq()
}
}
impl Hash for Package {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
impl Eq for Package {}
impl PartialOrd for Package {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Package {
fn cmp(&self, other: &Self) -> Ordering {
self.name
.cmp(&other.name)
.then(self.repo.as_ref().map_or(Ordering::Equal, |self_repo| {
other
.repo
.as_ref()
.map_or(Ordering::Equal, |other_repo| self_repo.cmp(other_repo))
}))
}
}
+8 -6
View File
@@ -1,27 +1,29 @@
use std::collections::HashSet;
use std::collections::BTreeSet;
use std::fmt::{Display, Write};
use std::hash::Hash;
use std::iter::Peekable;
use anyhow::{ensure, Context, Result};
use super::Package;
use crate::prelude::*;
pub type Sections = BTreeSet<Section>;
#[derive(Debug, Clone)]
pub struct Section {
pub name: String,
pub packages: HashSet<Package>,
pub packages: Packages,
}
impl Section {
pub fn new(name: String, packages: HashSet<Package>) -> Self {
pub fn new(name: String, packages: Packages) -> Self {
Self { name, packages }
}
pub fn try_from_lines<'a>(iter: &mut Peekable<impl Iterator<Item = &'a str>>) -> Result<Self> {
let name = find_next_section_name(iter)?;
let mut packages = HashSet::new();
let mut packages = Packages::new();
while next_line_might_be_package(iter) {
if let Some(package) = Package::try_from(iter.next().expect("we checked this is some"))
@@ -36,7 +38,7 @@ impl Section {
}
}
fn insert_package(package: Package, packages: &mut HashSet<Package>) {
fn insert_package(package: Package, packages: &mut Packages) {
let package_name = package.name.clone();
let newly_inserted = packages.insert(package);
+4 -5
View File
@@ -35,10 +35,9 @@ mod review;
mod search;
mod ui;
#[allow(unused_imports)]
mod prelude;
pub mod path;
pub use crate::config::Config;
pub use crate::errors::Error;
pub use crate::grouping::Group;
pub use crate::grouping::Groups;
pub use crate::grouping::Package;
pub use prelude::{Config, Error, Group};
+46
View File
@@ -0,0 +1,46 @@
#[cfg(feature = "arch")]
pub use crate::backend::actual::arch::Arch;
#[cfg(feature = "debian")]
pub use crate::backend::actual::debian::Debian;
pub use crate::backend::actual::{
fedora::Fedora, flatpak::Flatpak, python::Python, rust::Rust, rustup::Rustup, void::Void,
};
pub use crate::backend::backend_trait::{Backend, BackendInfo, Switches, Text};
pub use crate::backend::todo_per_backend::ToDoPerBackend;
pub use crate::backend::AnyBackend;
pub use crate::backend::ManagedBackend;
pub use crate::cli::CleanPackageAction;
pub use crate::cli::EditGroupAction;
pub use crate::cli::ExportGroupAction;
pub use crate::cli::GroupAction;
pub use crate::cli::GroupArguments;
pub use crate::cli::ImportGroupAction;
pub use crate::cli::ListGroupAction;
pub use crate::cli::MainArguments;
pub use crate::cli::MainSubcommand;
pub use crate::cli::NewGroupAction;
pub use crate::cli::PackageAction;
pub use crate::cli::PackageArguments;
pub use crate::cli::RemoveGroupAction;
pub use crate::cli::ReviewPackageAction;
pub use crate::cli::SearchPackageAction;
pub use crate::cli::ShowGroupAction;
pub use crate::cli::SyncPackageAction;
pub use crate::cli::UnmanagedPackageAction;
pub use crate::cli::VersionArguments;
pub use crate::config::Config;
pub use crate::errors::Error;
pub use crate::grouping::{
group::{Group, Groups},
package::{Package, Packages},
section::{Section, Sections},
};
pub use crate::path::binary_in_path;
pub use crate::path::get_absolutized_file_paths;
pub use crate::path::get_cargo_home;
pub use crate::path::get_config_path;
pub use crate::path::get_config_path_old_version;
pub use crate::path::get_group_dir;
pub use crate::path::get_home_dir;
pub use crate::path::get_pacdef_base_dir;
pub use crate::path::get_relative_path;
+11 -7
View File
@@ -1,4 +1,4 @@
use crate::{backend::AnyBackend, Group, Package};
use crate::prelude::*;
use super::strategy::Strategy;
@@ -48,9 +48,9 @@ impl ReviewsPerBackend {
let mut result = vec![];
for (backend, actions) in self {
let mut to_delete = vec![];
let mut to_delete = Packages::new();
let mut assign_group = vec![];
let mut as_dependency = vec![];
let mut as_dependency = Packages::new();
extract_actions(
actions,
@@ -91,15 +91,19 @@ pub enum ContinueWithReview {
fn extract_actions(
actions: Vec<ReviewAction>,
to_delete: &mut Vec<Package>,
to_delete: &mut Packages,
assign_group: &mut Vec<(Package, Group)>,
as_dependency: &mut Vec<Package>,
as_dependency: &mut Packages,
) {
for action in actions {
match action {
ReviewAction::Delete(package) => to_delete.push(package),
ReviewAction::Delete(package) => {
to_delete.insert(package);
}
ReviewAction::AssignGroup(package, group) => assign_group.push((package, group)),
ReviewAction::AsDependency(package) => as_dependency.push(package),
ReviewAction::AsDependency(package) => {
as_dependency.insert(package);
}
}
}
}
+2 -4
View File
@@ -5,10 +5,8 @@ use std::io::{stdin, stdout, Write};
use anyhow::Result;
use crate::backend::backend_trait::Backend;
use crate::backend::todo_per_backend::ToDoPerBackend;
use crate::prelude::*;
use crate::ui::{get_user_confirmation, read_single_char_from_terminal};
use crate::{Group, Groups, Package};
use self::datastructures::{ContinueWithReview, ReviewAction, ReviewIntention, ReviewsPerBackend};
use self::strategy::Strategy;
@@ -24,7 +22,7 @@ pub fn review(todo_per_backend: ToDoPerBackend, groups: &Groups) -> Result<()> {
'outer: for (backend, packages) in todo_per_backend {
let mut actions = vec![];
for package in packages {
println!("{}: {package}", backend.get_section());
println!("{}: {package}", backend.backend_info().section);
match get_action_for_package(package, groups, &mut actions, &backend)? {
ContinueWithReview::Yes => continue,
ContinueWithReview::No => return Ok(()),
+6 -9
View File
@@ -1,23 +1,20 @@
use anyhow::Result;
use crate::{
backend::{backend_trait::Backend, AnyBackend},
Group, Package,
};
use crate::prelude::*;
#[derive(Debug)]
pub struct Strategy {
backend: AnyBackend,
delete: Vec<Package>,
as_dependency: Vec<Package>,
delete: Packages,
as_dependency: Packages,
assign_group: Vec<(Package, Group)>,
}
impl Strategy {
pub fn new(
backend: AnyBackend,
delete: Vec<Package>,
as_dependency: Vec<Package>,
delete: Packages,
as_dependency: Packages,
assign_group: Vec<(Package, Group)>,
) -> Self {
Self {
@@ -49,7 +46,7 @@ impl Strategy {
return;
}
println!("[{}]", self.backend.get_section());
println!("[{}]", self.backend.backend_info().section);
if !self.delete.is_empty() {
println!("delete:");
+1 -5
View File
@@ -1,14 +1,10 @@
use std::iter::Peekable;
use std::vec::IntoIter;
use crate::prelude::*;
use anyhow::{bail, Result};
use regex::Regex;
use crate::{
grouping::{Group, Package, Section},
Groups,
};
/// Find all packages in all groups whose name match the regex from the
/// command-line arguments. Print the name of the packages per group and
/// section.