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 alpm::PackageReason::Explicit;
use anyhow::{Context, Result}; 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::cmd::run_external_command;
use crate::Package; use crate::prelude::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Arch { pub struct Arch {
pub binary: String, pub binary: String,
pub aur_rm_args: Vec<String>, pub aur_rm_args: Vec<String>,
pub packages: HashSet<Package>,
} }
impl Arch { impl Arch {
pub fn new() -> Self { pub fn new(config: &Config) -> Self {
Self { Self {
binary: BINARY.to_string(), binary: config.aur_helper.clone(),
aur_rm_args: vec![], aur_rm_args: config.aur_rm_args.clone(),
packages: HashSet::new(),
} }
} }
} }
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 for Arch {
impl_backend_constants!(); fn backend_info(&self) -> BackendInfo {
BackendInfo {
fn get_binary(&self) -> Text { binary: self.binary.clone(),
let r#box = self.binary.clone().into_boxed_str(); section: "arch",
Box::leak(r#box) 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() let alpm_packages = get_all_installed_packages_from_alpm()
.context("getting all installed packages from alpm")?; .context("getting all installed packages from alpm")?;
@@ -58,7 +43,7 @@ impl Backend for Arch {
Ok(result) 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() let alpm_packages = get_explicitly_installed_packages_from_alpm()
.context("getting all installed packages from alpm")?; .context("getting all installed packages from alpm")?;
let result = convert_to_pacdef_packages(alpm_packages); let result = convert_to_pacdef_packages(alpm_packages);
@@ -66,13 +51,15 @@ impl Backend for Arch {
} }
/// Install the specified packages. /// 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); let mut cmd = Command::new(&self.binary);
cmd.args(self.get_switches_install()); cmd.args(backend_info.switches_install);
if noconfirm { if noconfirm {
cmd.args(self.get_switches_noconfirm()); cmd.args(backend_info.switches_noconfirm);
} }
for p in packages { for p in packages {
@@ -83,14 +70,16 @@ impl Backend for Arch {
} }
/// Remove the specified packages. /// 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); 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); cmd.args(&self.aur_rm_args);
if noconfirm { if noconfirm {
cmd.args(self.get_switches_noconfirm()); cmd.args(backend_info.switches_noconfirm);
} }
for p in packages { for p in packages {
@@ -124,7 +113,7 @@ fn get_explicitly_installed_packages_from_alpm() -> Result<HashSet<String>> {
Ok(result) 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() packages.into_iter().map(Package::from).collect()
} }
+32 -37
View File
@@ -1,24 +1,16 @@
use std::collections::HashSet;
use anyhow::Result; use anyhow::Result;
use rust_apt::cache::PackageSort; use rust_apt::cache::PackageSort;
use rust_apt::new_cache; 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::backend::root::build_base_command_with_privileges;
use crate::cmd::run_external_command; use crate::cmd::run_external_command;
use crate::Package; use crate::prelude::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Debian { pub struct Debian {}
pub packages: HashSet<Package>,
}
impl Debian { impl Debian {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {}
packages: HashSet::new(),
}
} }
} }
impl Default for Debian { 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 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 cache = new_cache!()?;
let sort = PackageSort::default().installed(); let sort = PackageSort::default().installed();
let mut result = HashSet::new(); let mut result = Packages::new();
for pkg in cache.packages(&sort)? { for pkg in cache.packages(&sort)? {
result.insert(Package::from(pkg.name().to_string())); result.insert(Package::from(pkg.name().to_string()));
} }
Ok(result) Ok(result)
} }
fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>> { fn get_explicitly_installed_packages(&self) -> Result<Packages> {
let cache = new_cache!()?; let cache = new_cache!()?;
let sort = PackageSort::default().installed().manually_installed(); let sort = PackageSort::default().installed().manually_installed();
let mut result = HashSet::new(); let mut result = Packages::new();
for pkg in cache.packages(&sort)? { for pkg in cache.packages(&sort)? {
result.insert(Package::from(pkg.name().to_string())); result.insert(Package::from(pkg.name().to_string()));
} }
Ok(result) 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"); let mut cmd = build_base_command_with_privileges("apt-mark");
cmd.arg("auto"); cmd.arg("auto");
for p in packages { for p in packages {
@@ -74,13 +65,15 @@ impl Backend for Debian {
} }
/// Install the specified packages. /// Install the specified packages.
fn install_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> { fn install_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let mut cmd = build_base_command_with_privileges(self.get_binary()); 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 { if noconfirm {
cmd.args(self.get_switches_noconfirm()); cmd.args(backend_info.switches_noconfirm);
} }
for p in packages { for p in packages {
@@ -91,12 +84,14 @@ impl Backend for Debian {
} }
/// Remove the specified packages. /// Remove the specified packages.
fn remove_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> { fn remove_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let mut cmd = build_base_command_with_privileges(self.get_binary()); let backend_info = self.backend_info();
cmd.args(self.get_switches_remove());
let mut cmd = build_base_command_with_privileges(&backend_info.binary);
cmd.args(backend_info.switches_remove);
if noconfirm { if noconfirm {
cmd.args(self.get_switches_noconfirm()); cmd.args(backend_info.switches_noconfirm);
} }
for p in packages { for p in packages {
+39 -41
View File
@@ -1,22 +1,15 @@
use std::collections::HashSet;
use std::process::Command; use std::process::Command;
use anyhow::Result; 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::cmd::run_external_command;
use crate::Package; use crate::prelude::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Fedora { pub struct Fedora {}
pub packages: HashSet<Package>,
}
impl Fedora { impl Fedora {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {}
packages: HashSet::new(),
}
} }
} }
impl Default for Fedora { impl Default for Fedora {
@@ -25,16 +18,9 @@ impl Default for Fedora {
} }
} }
const BINARY: Text = "dnf"; /// These repositories are ignored when storing the packages
const SECTION: Text = "fedora"; /// as these are present by default on any sane fedora system
const DEFAULT_REPOS: [&str; 5] = ["koji", "fedora", "updates", "anaconda", "@"];
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 switches are responsible for /// These switches are responsible for
/// getting the packages explicitly installed by the user /// getting the packages explicitly installed by the user
@@ -54,15 +40,21 @@ const SWITCHES_FETCH_GLOBAL: Switches = &[
"%{from_repo}/%{name}", "%{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 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>> { fn get_all_installed_packages(&self) -> Result<Packages> {
let mut cmd = Command::new(self.get_binary()); let mut cmd = Command::new(self.backend_info().binary);
cmd.args(SWITCHES_FETCH_GLOBAL); cmd.args(SWITCHES_FETCH_GLOBAL);
let output = String::from_utf8(cmd.output()?.stdout)?; let output = String::from_utf8(cmd.output()?.stdout)?;
@@ -71,8 +63,8 @@ impl Backend for Fedora {
Ok(packages) Ok(packages)
} }
fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>> { fn get_explicitly_installed_packages(&self) -> Result<Packages> {
let mut cmd = Command::new(self.get_binary()); let mut cmd = Command::new(self.backend_info().binary);
cmd.args(SWITCHES_FETCH_USER); cmd.args(SWITCHES_FETCH_USER);
let output = String::from_utf8(cmd.output()?.stdout)?; let output = String::from_utf8(cmd.output()?.stdout)?;
@@ -82,13 +74,15 @@ impl Backend for Fedora {
} }
/// Install the specified packages. /// 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"); let mut cmd = Command::new("sudo");
cmd.arg(self.get_binary()); cmd.arg(backend_info.binary);
cmd.args(self.get_switches_install()); cmd.args(backend_info.switches_install);
if noconfirm { if noconfirm {
cmd.args(self.get_switches_noconfirm()); cmd.args(backend_info.switches_noconfirm);
} }
for p in packages { for p in packages {
@@ -106,13 +100,15 @@ impl Backend for Fedora {
} }
/// Show information from package manager for package. /// 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"); let mut cmd = Command::new("sudo");
cmd.arg(self.get_binary()); cmd.arg(backend_info.binary);
cmd.args(self.get_switches_remove()); cmd.args(backend_info.switches_remove);
if noconfirm { if noconfirm {
cmd.args(self.get_switches_noconfirm()); cmd.args(backend_info.switches_noconfirm);
} }
for p in packages { for p in packages {
@@ -123,14 +119,16 @@ impl Backend for Fedora {
} }
fn show_package_info(&self, package: &Package) -> Result<()> { fn show_package_info(&self, package: &Package) -> Result<()> {
let mut cmd = Command::new(self.get_binary()); let backend_info = self.backend_info();
cmd.args(self.get_switches_info());
let mut cmd = Command::new(backend_info.binary);
cmd.args(backend_info.switches_info);
cmd.arg(&package.name); cmd.arg(&package.name);
run_external_command(cmd) run_external_command(cmd)
} }
fn make_dependency(&self, _: &[Package]) -> Result<()> { fn make_dependency(&self, _: &Packages) -> Result<()> {
panic!("Not supported by the package manager!") panic!("Not supported by the package manager!")
} }
} }
+38 -46
View File
@@ -1,23 +1,18 @@
use std::collections::HashSet;
use std::process::Command; use std::process::Command;
use anyhow::Result; 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::cmd::run_external_command;
use crate::Package; use crate::prelude::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Flatpak { pub struct Flatpak {
pub packages: HashSet<Package>,
pub systemwide: bool, pub systemwide: bool,
} }
impl Flatpak { impl Flatpak {
pub fn new() -> Self { pub fn new(config: &Config) -> Self {
Self { Self {
packages: HashSet::new(), systemwide: config.flatpak_systemwide,
systemwide: true,
} }
} }
@@ -29,8 +24,8 @@ impl Flatpak {
} }
} }
fn get_installed_packages(&self, include_implicit: bool) -> Result<HashSet<Package>> { fn get_installed_packages(&self, include_implicit: bool) -> Result<Packages> {
let mut cmd = Command::new(BINARY); let mut cmd = Command::new(self.backend_info().binary);
cmd.args(["list", "--columns=application"]); cmd.args(["list", "--columns=application"]);
if !include_implicit { if !include_implicit {
cmd.arg("--app"); cmd.arg("--app");
@@ -40,48 +35,41 @@ impl Flatpak {
} }
let output = String::from_utf8(cmd.output()?.stdout)?; let output = String::from_utf8(cmd.output()?.stdout)?;
Ok(output Ok(output.lines().map(Package::from).collect::<Packages>())
.lines()
.map(Package::from)
.collect::<HashSet<Package>>())
} }
} }
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 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) 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) self.get_installed_packages(false)
} }
/// Install the specified packages. /// Install the specified packages.
fn install_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> { fn install_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let mut cmd = Command::new(self.get_binary()); let backend_info = self.backend_info();
cmd.args(self.get_switches_install());
let mut cmd = Command::new(backend_info.binary);
cmd.args(backend_info.switches_install);
cmd.args(self.get_switches_runtime()); cmd.args(self.get_switches_runtime());
if noconfirm { if noconfirm {
cmd.args(self.get_switches_noconfirm()); cmd.args(backend_info.switches_noconfirm);
} }
for p in packages { for p in packages {
@@ -91,18 +79,20 @@ impl Backend for Flatpak {
run_external_command(cmd) run_external_command(cmd)
} }
fn make_dependency(&self, _: &[Package]) -> Result<()> { fn make_dependency(&self, _: &Packages) -> Result<()> {
panic!("not supported by {}", BINARY) panic!("not supported by {}", self.backend_info().binary)
} }
/// Remove the specified packages. /// Remove the specified packages.
fn remove_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> { fn remove_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let mut cmd = Command::new(self.get_binary()); let backend_info = self.backend_info();
cmd.args(self.get_switches_remove());
let mut cmd = Command::new(backend_info.binary);
cmd.args(backend_info.switches_remove);
cmd.args(self.get_switches_runtime()); cmd.args(self.get_switches_runtime());
if noconfirm { if noconfirm {
cmd.args(self.get_switches_noconfirm()); cmd.args(backend_info.switches_noconfirm);
} }
for p in packages { for p in packages {
@@ -114,8 +104,10 @@ impl Backend for Flatpak {
/// Show information from package manager for package. /// Show information from package manager for package.
fn show_package_info(&self, package: &Package) -> Result<()> { fn show_package_info(&self, package: &Package) -> Result<()> {
let mut cmd = Command::new(self.get_binary()); let backend_info = self.backend_info();
cmd.args(self.get_switches_info());
let mut cmd = Command::new(backend_info.binary);
cmd.args(backend_info.switches_info);
cmd.args(self.get_switches_runtime()); cmd.args(self.get_switches_runtime());
cmd.arg(format!("{package}")); cmd.arg(format!("{package}"));
+29 -45
View File
@@ -1,13 +1,10 @@
use std::collections::HashSet;
use std::process::Command; use std::process::Command;
use anyhow::Context; use anyhow::Context;
use anyhow::Result; use anyhow::Result;
use serde_json::Value; use serde_json::Value;
use crate::backend::backend_trait::{Backend, Switches, Text}; use crate::prelude::*;
use crate::backend::macros::impl_backend_constants;
use crate::Package;
macro_rules! ERROR{ macro_rules! ERROR{
($bin:expr) => { ($bin:expr) => {
@@ -15,81 +12,68 @@ macro_rules! ERROR{
}; };
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Python { pub struct Python {
pub binary: String, pub binary: String,
pub packages: HashSet<Package>,
} }
impl Python { impl Python {
pub fn new() -> Self { pub fn new(config: &Config) -> Self {
Self { Self {
binary: BINARY.to_string(), binary: config.pip_binary.to_string(),
packages: HashSet::new(),
} }
} }
fn get_switches_runtime(&self) -> Switches { fn get_switches_runtime(&self) -> Switches {
match self.get_binary() { match self.backend_info().binary.as_str() {
"pip" => &["list", "--format", "json", "--not-required", "--user"], "pip" => &["list", "--format", "json", "--not-required", "--user"],
"pipx" => &["list", "--json"], "pipx" => &["list", "--json"],
_ => ERROR!(self.get_binary()), _ => ERROR!(self.backend_info().binary),
} }
} }
fn get_switches_explicit(&self) -> Switches { fn get_switches_explicit(&self) -> Switches {
match self.get_binary() { match self.backend_info().binary.as_str() {
"pip" => &["list", "--format", "json", "--user"], "pip" => &["list", "--format", "json", "--user"],
"pipx" => &["list", "--json"], "pipx" => &["list", "--json"],
_ => ERROR!(self.get_binary()), _ => ERROR!(self.backend_info().binary),
} }
} }
fn extract_packages(&self, output: Value) -> Result<HashSet<Package>> { fn extract_packages(&self, output: Value) -> Result<Packages> {
match self.get_binary() { match self.backend_info().binary.as_str() {
"pip" => extract_pacdef_packages(output), "pip" => extract_pacdef_packages(output),
"pipx" => extract_pacdef_packages_pipx(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 for Python {
impl_backend_constants!(); fn backend_info(&self) -> BackendInfo {
BackendInfo {
fn get_binary(&self) -> Text { binary: self.binary.clone(),
let r#box = self.binary.clone().into_boxed_str(); section: "python",
Box::leak(r#box) switches_info: &["show"],
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 mut cmd = Command::new(self.get_binary()); let mut cmd = Command::new(self.backend_info().binary);
let output = run_pip_command(&mut cmd, self.get_switches_runtime())?; let output = run_pip_command(&mut cmd, self.get_switches_runtime())?;
self.extract_packages(output) self.extract_packages(output)
} }
fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>> { fn get_explicitly_installed_packages(&self) -> Result<Packages> {
let mut cmd = Command::new(self.get_binary()); let mut cmd = Command::new(self.backend_info().binary);
let output = run_pip_command(&mut cmd, self.get_switches_explicit())?; let output = run_pip_command(&mut cmd, self.get_switches_explicit())?;
self.extract_packages(output) self.extract_packages(output)
} }
fn make_dependency(&self, _packages: &[Package]) -> Result<()> { fn make_dependency(&self, _packages: &Packages) -> Result<()> {
panic!("not supported by {}", BINARY) panic!("not supported by {}", self.binary)
} }
} }
@@ -100,7 +84,7 @@ fn run_pip_command(cmd: &mut Command, args: &[&str]) -> Result<Value> {
Ok(val) Ok(val)
} }
fn extract_pacdef_packages(value: Value) -> Result<HashSet<Package>> { fn extract_pacdef_packages(value: Value) -> Result<Packages> {
let result = value let result = value
.as_array() .as_array()
.context("getting inner json array")? .context("getting inner json array")?
@@ -111,7 +95,7 @@ fn extract_pacdef_packages(value: Value) -> Result<HashSet<Package>> {
Ok(result) 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"] let result = value["venvs"]
.as_object() .as_object()
.context("getting inner json 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::fs::read_to_string;
use std::io::ErrorKind::NotFound; use std::io::ErrorKind::NotFound;
use std::path::PathBuf; use std::path::PathBuf;
@@ -6,19 +5,13 @@ use std::path::PathBuf;
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use serde_json::Value; use serde_json::Value;
use crate::backend::backend_trait::{Backend, Switches, Text}; use crate::prelude::*;
use crate::backend::macros::impl_backend_constants;
use crate::Package;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Rust { pub struct Rust {}
pub packages: HashSet<Package>,
}
impl Rust { impl Rust {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {}
packages: HashSet::new(),
}
} }
} }
impl Default for Rust { 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 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 file = get_crates_file().context("getting path to crates file")?;
let content = match read_to_string(file) { let content = match read_to_string(file) {
Ok(string) => string, Ok(string) => string,
Err(err) if err.kind() == NotFound => { Err(err) if err.kind() == NotFound => {
log::warn!("no crates file found for cargo. Assuming no crates installed yet."); 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), Err(err) => bail!(err),
}; };
@@ -58,18 +50,18 @@ impl Backend for Rust {
extract_packages(&json).context("extracting packages from crates file") 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() self.get_all_installed_packages()
.context("getting all installed packages") .context("getting all installed packages")
} }
fn make_dependency(&self, _: &[Package]) -> Result<()> { fn make_dependency(&self, _: &Packages) -> Result<()> {
panic!("not supported by {}", BINARY) panic!("not supported by {}", self.backend_info().binary)
} }
} }
fn extract_packages(json: &Value) -> Result<HashSet<Package>> { fn extract_packages(json: &Value) -> Result<Packages> {
let result: HashSet<_> = json let result: Packages = json
.get("installs") .get("installs")
.context("get 'installs' field from json")? .context("get 'installs' field from json")?
.as_object() .as_object()
@@ -83,6 +75,7 @@ fn extract_packages(json: &Value) -> Result<HashSet<Package>> {
}) })
.map(|name| Package::try_from(name).expect("name is valid")) .map(|name| Package::try_from(name).expect("name is valid"))
.collect(); .collect();
Ok(result) Ok(result)
} }
+30 -38
View File
@@ -1,12 +1,9 @@
mod helpers; mod helpers;
mod types; 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::cmd::run_external_command;
use crate::Package; use crate::prelude::*;
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use std::collections::HashSet;
use std::process::Command; use std::process::Command;
use self::helpers::{ use self::helpers::{
@@ -14,15 +11,11 @@ use self::helpers::{
}; };
use self::types::{Repotype, RustupPackage}; use self::types::{Repotype, RustupPackage};
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Rustup { pub struct Rustup {}
pub packages: HashSet<Package>,
}
impl Rustup { impl Rustup {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {}
packages: HashSet::new(),
}
} }
} }
impl Default for Rustup { 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 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 let toolchains_vec = self
.run_toolchain_command(Repotype::Toolchain.get_info_switches()) .run_toolchain_command(Repotype::Toolchain.get_info_switches())
.context("Getting installed toolchains")?; .context("Getting installed toolchains")?;
let toolchains: HashSet<Package> = toolchains_vec let toolchains: Packages = toolchains_vec
.iter() .iter()
.map(|name| ["toolchain", name].join("/").into()) .map(|name| ["toolchain", name].join("/").into())
.collect(); .collect();
let components: HashSet<Package> = self let components: Packages = self
.run_component_command(Repotype::Component.get_info_switches(), &toolchains_vec) .run_component_command(Repotype::Component.get_info_switches(), &toolchains_vec)
.context("Getting installed components")? .context("Getting installed components")?
.iter() .iter()
.map(|name| ["component", name].join("/").into()) .map(|name| ["component", name].join("/").into())
.collect(); .collect();
let mut packages = HashSet::new(); let mut packages = Packages::new();
packages.extend(toolchains); packages.extend(toolchains);
packages.extend(components); packages.extend(components);
@@ -70,16 +62,16 @@ impl Backend for Rustup {
Ok(packages) Ok(packages)
} }
fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>> { fn get_explicitly_installed_packages(&self) -> Result<Packages> {
self.get_all_installed_packages() self.get_all_installed_packages()
.context("Getting all installed packages") .context("Getting all installed packages")
} }
fn make_dependency(&self, _: &[Package]) -> Result<()> { fn make_dependency(&self, _: &Packages) -> Result<()> {
panic!("Not supported by {}", self.get_binary()) 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 packages = RustupPackage::from_pacdef_packages(packages)?;
let (toolchains, components) = let (toolchains, components) =
@@ -91,7 +83,7 @@ impl Backend for Rustup {
Ok(()) 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 rustup_packages = RustupPackage::from_pacdef_packages(packages)?;
let (toolchains, components) = let (toolchains, components) =
@@ -110,7 +102,7 @@ impl Rustup {
let mut val = Vec::new(); let mut val = Vec::new();
for toolchain in toolchains { 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); cmd.args(args).arg(toolchain);
let output = String::from_utf8(cmd.output()?.stdout)?; let output = String::from_utf8(cmd.output()?.stdout)?;
@@ -124,7 +116,7 @@ impl Rustup {
} }
fn run_toolchain_command(&self, args: &[&str]) -> Result<Vec<String>> { 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); cmd.args(args);
let output = String::from_utf8(cmd.output()?.stdout)?; let output = String::from_utf8(cmd.output()?.stdout)?;
@@ -146,7 +138,7 @@ impl Rustup {
if toolchains.is_empty() { if toolchains.is_empty() {
return Ok(()); 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()); cmd.args(Repotype::Toolchain.get_install_switches());
for toolchain in toolchains { for toolchain in toolchains {
@@ -166,7 +158,7 @@ impl Rustup {
let components_by_toolchain = group_components_by_toolchains(components); let components_by_toolchain = group_components_by_toolchains(components);
for components_for_one_toolchain in components_by_toolchain { 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()); cmd.args(Repotype::Component.get_install_switches());
let the_toolchain = &components_for_one_toolchain let the_toolchain = &components_for_one_toolchain
@@ -195,7 +187,7 @@ impl Rustup {
fn remove_toolchains(&self, toolchains: Vec<RustupPackage>) -> Result<Vec<String>> { fn remove_toolchains(&self, toolchains: Vec<RustupPackage>) -> Result<Vec<String>> {
let mut removed_toolchains = vec![]; let mut removed_toolchains = vec![];
if !toolchains.is_empty() { 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()); cmd.args(Repotype::Toolchain.get_remove_switches());
for toolchain_package in &toolchains { for toolchain_package in &toolchains {
@@ -216,7 +208,7 @@ impl Rustup {
removed_toolchains: Vec<String>, removed_toolchains: Vec<String>,
) -> Result<()> { ) -> Result<()> {
for component_package in components { 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()); cmd.args(Repotype::Component.get_remove_switches());
if toolchain_of_component_was_already_removed(&removed_toolchains, &component_package) { if toolchain_of_component_was_already_removed(&removed_toolchains, &component_package) {
@@ -1,6 +1,6 @@
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use crate::{backend::backend_trait::Switches, Package}; use crate::prelude::*;
#[derive(Debug)] #[derive(Debug)]
pub enum Repotype { pub enum Repotype {
@@ -95,7 +95,7 @@ impl RustupPackage {
(toolchains, components) (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![]; let mut result = vec![];
for package in packages { for package in packages {
+38 -33
View File
@@ -1,24 +1,17 @@
use std::collections::HashSet;
use std::process::Command; use std::process::Command;
use anyhow::Result; use anyhow::Result;
use regex::Regex; 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::backend::root::build_base_command_with_privileges;
use crate::cmd::run_external_command; use crate::cmd::run_external_command;
use crate::Package; use crate::prelude::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Void { pub struct Void {}
pub packages: HashSet<Package>,
}
impl Void { impl Void {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {}
packages: HashSet::new(),
}
} }
} }
impl Default for Void { impl Default for Void {
@@ -27,25 +20,25 @@ impl Default for Void {
} }
} }
const BINARY: Text = "xbps-install";
const INSTALL_BINARY: Text = "xbps-install"; const INSTALL_BINARY: Text = "xbps-install";
const REMOVE_BINARY: Text = "xbps-remove"; const REMOVE_BINARY: Text = "xbps-remove";
const QUERY_BINARY: Text = "xbps-query"; const QUERY_BINARY: Text = "xbps-query";
const PKGDB_BINARY: Text = "xbps-pkgdb"; 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 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 // Removes the package status and description from output
let re_str_1 = r"^ii |^uu |^hr |^\?\? | .*"; let re_str_1 = r"^ii |^uu |^hr |^\?\? | .*";
// Removes the package version from output // Removes the package version from output
@@ -67,7 +60,7 @@ impl Backend for Void {
Ok(packages) 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 // Removes the package version from output
let re_str = r"-[^-]*$"; let re_str = r"-[^-]*$";
let re = Regex::new(re_str)?; let re = Regex::new(re_str)?;
@@ -86,12 +79,14 @@ impl Backend for Void {
} }
/// Install the specified packages. /// 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); let mut cmd = build_base_command_with_privileges(INSTALL_BINARY);
cmd.args(self.get_switches_install()); cmd.args(backend_info.switches_install);
if noconfirm { if noconfirm {
cmd.args(self.get_switches_noconfirm()); cmd.args(backend_info.switches_noconfirm);
} }
for p in packages { for p in packages {
@@ -101,12 +96,14 @@ impl Backend for Void {
run_external_command(cmd) 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); let mut cmd = build_base_command_with_privileges(REMOVE_BINARY);
cmd.args(self.get_switches_remove()); cmd.args(backend_info.switches_remove);
if noconfirm { if noconfirm {
cmd.args(self.get_switches_noconfirm()); cmd.args(backend_info.switches_noconfirm);
} }
for p in packages { for p in packages {
@@ -116,9 +113,15 @@ impl Backend for Void {
run_external_command(cmd) 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); 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 { for p in packages {
cmd.arg(format!("{p}")); cmd.arg(format!("{p}"));
@@ -129,8 +132,10 @@ impl Backend for Void {
/// Show information from package manager for package. /// Show information from package manager for package.
fn show_package_info(&self, package: &Package) -> Result<()> { fn show_package_info(&self, package: &Package) -> Result<()> {
let backend_info = self.backend_info();
let mut cmd = Command::new(QUERY_BINARY); let mut cmd = Command::new(QUERY_BINARY);
cmd.args(self.get_switches_info()); cmd.args(backend_info.switches_info);
cmd.arg(format!("{package}")); cmd.arg(format!("{package}"));
run_external_command(cmd) run_external_command(cmd)
+77 -108
View File
@@ -1,68 +1,52 @@
use std::cmp::{Eq, Ord}; use std::collections::BTreeMap;
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use std::process::Command; use std::process::Command;
use anyhow::{Context, Result}; use anyhow::Result;
use crate::cmd::run_external_command; use crate::cmd::run_external_command;
use crate::{Group, Groups, Package}; use crate::prelude::*;
pub type Switches = &'static [&'static str]; pub type Switches = &'static [&'static str];
pub type Text = &'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. /// The trait of a struct that is used as a backend.
#[enum_dispatch::enum_dispatch] #[enum_dispatch::enum_dispatch]
pub trait Backend { pub trait Backend {
/// Return the actual binary. Iff the backend supports different /// Return the [`BackendInfo`] associated with this backend.
/// binaries, you will need to overwrite this implementation to return fn backend_info(&self) -> BackendInfo;
/// the binary that was loaded at runtime. See
/// [`Backend::get_binary_default()`]. fn supports_as_dependency(&self) -> bool {
fn get_binary(&self) -> Text { self.backend_info().switches_make_dependency.is_some()
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: &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. /// Get all packages that are installed in the system.
/// ///
/// # Errors /// # Errors
/// ///
/// This function shall return an error if the installed packages cannot be /// This function shall return an error if the installed packages cannot be
/// determined. /// 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. /// 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 /// This function shall return an error if the explicitly installed packages
/// cannot be determined. /// 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 /// Assign each of the packages to an individual group by editing the
/// group files. /// 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<()> { fn assign_group(&self, to_assign: Vec<(Package, Group)>) -> Result<()> {
let group_package_map = to_hashmap(to_assign); let mut group_package_map: BTreeMap<Group, Packages> = BTreeMap::new();
let section_header = format!("[{}]", self.get_section());
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 { for (group, packages) in group_package_map {
group.save_packages(&section_header, &packages)?; 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 /// This function will return an error if the package manager cannot be run or it
/// returns an error. /// returns an error.
fn install_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> { fn install_packages(&self, packages: &Packages, noconfirm: bool) -> Result<()> {
let mut cmd = Command::new(self.get_binary()); let backend_info = self.backend_info();
cmd.args(self.get_switches_install());
let mut cmd = Command::new(self.backend_info().binary);
cmd.args(backend_info.switches_install);
if noconfirm { if noconfirm {
cmd.args(self.get_switches_noconfirm()); cmd.args(backend_info.switches_noconfirm);
} }
for p in packages { for p in packages {
@@ -113,9 +108,18 @@ pub trait Backend {
/// # Panics /// # Panics
/// ///
/// This method shall panic when the backend does not support dependent packages. /// 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()); /// # Errors
cmd.args(self.get_switches_make_dependency()); ///
/// 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 { for p in packages {
cmd.arg(format!("{p}")); cmd.arg(format!("{p}"));
@@ -126,12 +130,17 @@ pub trait Backend {
/// Remove the specified packages. /// Remove the specified packages.
/// ///
fn remove_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> { /// # Errors
let mut cmd = Command::new(self.get_binary()); ///
cmd.args(self.get_switches_remove()); /// 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 { if noconfirm {
cmd.args(self.get_switches_noconfirm()); cmd.args(backend_info.switches_noconfirm);
} }
for p in packages { for p in packages {
@@ -141,58 +150,18 @@ pub trait Backend {
run_external_command(cmd) 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. /// Show information from package manager for package.
///
/// # Errors
///
/// Returns an error if the external command fails.
fn show_package_info(&self, package: &Package) -> Result<()> { fn show_package_info(&self, package: &Package) -> Result<()> {
let mut cmd = Command::new(self.get_binary()); let backend_info = self.backend_info();
cmd.args(self.get_switches_info());
let mut cmd = Command::new(backend_info.binary);
cmd.args(backend_info.switches_info);
cmd.arg(format!("{package}")); cmd.arg(format!("{package}"));
run_external_command(cmd) 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 actual;
pub mod backend_trait; pub mod backend_trait;
pub mod macros;
mod root; mod root;
pub mod todo_per_backend; pub mod todo_per_backend;
use crate::backend::backend_trait::Switches; use std::fmt::Display;
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 self::actual::{ use crate::prelude::*;
fedora::Fedora, flatpak::Flatpak, python::Python, rust::Rust, rustup::Rustup, void::Void, 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)] #[enum_dispatch::enum_dispatch(Backend)]
pub enum AnyBackend { pub enum AnyBackend {
#[cfg(feature = "arch")] #[cfg(feature = "arch")]
@@ -31,22 +64,44 @@ pub enum AnyBackend {
Rustup(Rustup), Rustup(Rustup),
Void(Void), Void(Void),
} }
impl AnyBackend { impl AnyBackend {
/// Returns an iterator of every variant of backend. /// 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![ vec![
#[cfg(feature = "arch")] #[cfg(feature = "arch")]
Self::Arch(actual::arch::Arch::new()), Self::Arch(actual::arch::Arch::new(config)),
#[cfg(feature = "debian")] #[cfg(feature = "debian")]
Self::Debian(actual::debian::Debian::new()), Self::Debian(actual::debian::Debian::new()),
Self::Flatpak(Flatpak::new()), Self::Flatpak(Flatpak::new(config)),
Self::Fedora(Fedora::new()), Self::Fedora(Fedora::new()),
Self::Python(Python::new()), Self::Python(Python::new(config)),
Self::Rust(Rust::new()), Self::Rust(Rust::new()),
Self::Rustup(Rustup::new()), Self::Rustup(Rustup::new()),
Self::Void(Void::new()), Self::Void(Void::new()),
] ]
.into_iter() .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 anyhow::{Context, Result};
use super::{AnyBackend, Backend}; use crate::prelude::*;
use crate::Package;
/// A vector of tuples containing a Backends and a vector of unmanaged packages /// A vector of tuples containing a Backends and a vector of unmanaged packages
/// for that backend. /// for that backend.
@@ -11,17 +10,17 @@ use crate::Package;
/// This struct is used to store a list of unmanaged packages or missing packages /// This struct is used to store a list of unmanaged packages or missing packages
/// for all backends. /// for all backends.
#[derive(Debug)] #[derive(Debug)]
pub struct ToDoPerBackend(Vec<(AnyBackend, Vec<Package>)>); pub struct ToDoPerBackend(Vec<(AnyBackend, Packages)>);
impl ToDoPerBackend { impl ToDoPerBackend {
pub fn new() -> Self { pub fn new() -> Self {
Self(vec![]) Self(vec![])
} }
pub fn push(&mut self, item: (AnyBackend, Vec<Package>)) { pub fn push(&mut self, item: (AnyBackend, Packages)) {
self.0.push(item); 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() self.0.iter()
} }
@@ -37,7 +36,7 @@ impl ToDoPerBackend {
backend backend
.install_packages(packages, noconfirm) .install_packages(packages, noconfirm)
.with_context(|| format!("installing packages for {}", backend.get_section()))?; .with_context(|| format!("installing packages for {backend}"))?;
} }
Ok(()) Ok(())
} }
@@ -50,7 +49,7 @@ impl ToDoPerBackend {
backend backend
.remove_packages(packages, noconfirm) .remove_packages(packages, noconfirm)
.with_context(|| format!("removing packages for {}", backend.get_section()))?; .with_context(|| format!("removing packages for {backend}"))?;
} }
Ok(()) Ok(())
} }
@@ -65,7 +64,7 @@ impl ToDoPerBackend {
let mut segment = String::new(); let mut segment = String::new();
segment.write_str(&format!("[{}]", backend.get_section()))?; segment.write_str(&format!("[{backend}]"))?;
for package in packages { for package in packages {
segment.write_str(&format!("\n{package}"))?; segment.write_str(&format!("\n{package}"))?;
} }
@@ -95,7 +94,7 @@ impl Default for ToDoPerBackend {
} }
impl IntoIterator for ToDoPerBackend { impl IntoIterator for ToDoPerBackend {
type Item = (AnyBackend, Vec<Package>); type Item = (AnyBackend, Packages);
type IntoIter = std::vec::IntoIter<Self::Item>; 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 anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::prelude::*;
// Update the master README if fields change. // Update the master README if fields change.
/// Config for the program, as listed in `$XDG_CONFIG_HOME/pacdef/pacdef.toml`. /// Config for the program, as listed in `$XDG_CONFIG_HOME/pacdef/pacdef.toml`.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@@ -55,7 +57,7 @@ impl Config {
Ok(content) => content, Ok(content) => content,
Err(e) => { Err(e) => {
if e.kind() == ErrorKind::NotFound { if e.kind() == ErrorKind::NotFound {
bail!(crate::Error::ConfigFileNotFound) bail!(Error::ConfigFileNotFound)
} }
bail!("unexpected error occurred: {e:?}"); bail!("unexpected error occurred: {e:?}");
} }
+43 -65
View File
@@ -8,23 +8,14 @@ use std::process::Command;
use anyhow::{bail, ensure, Context, Result}; use anyhow::{bail, ensure, Context, Result};
use const_format::formatcp; 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::cmd::{run_edit_command, run_external_command};
use crate::env::{get_editor, should_print_debug_info}; 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::path::{binary_in_path, get_absolutized_file_paths, get_group_dir};
use crate::prelude::*;
use crate::review::review; use crate::review::review;
use crate::search::search_packages; use crate::search::search_packages;
use crate::ui::get_user_confirmation; use crate::ui::get_user_confirmation;
use crate::Group;
use crate::{Config, Error, Groups};
impl MainArguments { impl MainArguments {
/// Run the action that was provided by the user as first argument. /// Run the action that was provided by the user as first argument.
@@ -39,7 +30,7 @@ impl MainArguments {
match self.subcommand { match self.subcommand {
MainSubcommand::Group(group) => group.run(groups), MainSubcommand::Group(group) => group.run(groups),
MainSubcommand::Package(package) => package.run(groups, config), 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 { impl VersionArguments {
/// If the crate was compiled from git, return `pacdef, <version> (<hash>)`. /// If the crate was compiled from git, return `pacdef, <version> (<hash>)`.
/// Otherwise return `pacdef, <version>`. /// Otherwise return `pacdef, <version>`.
fn run(self) -> Result<()> { fn run(self, config: &Config) -> Result<()> {
let backends = get_included_backends(); let backends = get_included_backends(config);
let mut result = format!("pacdef, version: {}\n", get_version_string()); let mut result = format!("pacdef, version: {}\n", get_version_string());
result.push_str("supported backends:"); result.push_str("supported backends:");
for b in backends { for b in backends {
@@ -221,7 +212,7 @@ impl NewGroupAction {
for new_group in &self.new_groups { for new_group in &self.new_groups {
ensure!( ensure!(
new_group != "." && new_group != "..", new_group != "." && new_group != "..",
crate::Error::InvalidGroupName(new_group.clone()) Error::InvalidGroupName(new_group.clone())
); );
} }
@@ -236,10 +227,7 @@ impl NewGroupAction {
.collect(); .collect();
for file in &paths { for file in &paths {
ensure!( ensure!(!file.exists(), Error::GroupAlreadyExists(file.clone()));
!file.exists(),
crate::Error::GroupAlreadyExists(file.clone())
);
} }
for file in &paths { for file in &paths {
@@ -284,10 +272,7 @@ impl ShowGroupAction {
} }
// return an error if any arg was not found // return an error if any arg was not found
ensure!( ensure!(errors.is_empty(), Error::MultipleGroupsNotFound(errors));
errors.is_empty(),
crate::Error::MultipleGroupsNotFound(errors)
);
let show_more_than_one_group = self.show_groups.len() > 1; 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> { 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(); 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 if config
.disabled_backends .disabled_backends
.contains(&backend.get_section().to_string()) .contains(&backend_info.section.to_string())
{ {
continue; continue;
} }
if !binary_in_path(backend.get_binary())? { if !binary_in_path(&backend_info.binary)? {
continue; continue;
} }
overwrite_values_from_config(&mut backend, config); let managed_backend = ManagedBackend {
backend.load(groups); packages: packages.clone(),
any_backend: any_backend.clone(),
};
match backend.get_missing_packages_sorted() { match managed_backend.get_missing_packages_sorted() {
Ok(diff) => to_install.push((backend, diff)), Ok(diff) => to_install.push((any_backend.clone(), diff)),
Err(error) => show_backend_query_error(&error, &backend), Err(error) => show_backend_query_error(&error, any_backend),
}; };
} }
Ok(to_install) 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. /// Get a list of unmanaged packages per backend.
/// ///
/// This method loops through all enabled `Backend`s whose binary is in `PATH`. /// 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. /// This function will propagate errors from the individual backends.
fn get_unmanaged_packages(groups: &Groups, config: &Config) -> Result<ToDoPerBackend> { 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 if config
.disabled_backends .disabled_backends
.contains(&backend.get_section().to_string()) .contains(&backend_info.section.to_string())
{ {
continue; continue;
} }
if !binary_in_path(backend.get_binary())? { if !binary_in_path(&backend_info.binary)? {
continue; continue;
} }
overwrite_values_from_config(&mut backend, config); let managed_backend = ManagedBackend {
backend.load(groups); packages: packages.clone(),
any_backend: any_backend.clone(),
};
match backend.get_unmanaged_packages_sorted() { match managed_backend.get_unmanaged_packages_sorted() {
Ok(unmanaged) => result.push((backend, unmanaged)), Ok(unmanaged) => todo_unmanaged.push((any_backend.clone(), unmanaged)),
Err(error) => show_backend_query_error(&error, &backend), 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. /// 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 /// 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`. /// if the `RUST_BACKTRACE` env variable is set to `1` or `full`.
fn show_backend_query_error(error: &anyhow::Error, backend: &AnyBackend) { fn show_backend_query_error(error: &anyhow::Error, backend: &AnyBackend) {
let section = backend.get_section();
if should_print_debug_info() { if should_print_debug_info() {
log::warn!( log::warn!(
"skipping backend '{section}': {}", "skipping backend '{backend}': {}",
error.chain().map(|x| x.to_string()).collect::<String>() error.chain().map(|x| x.to_string()).collect::<String>()
); );
} else { } 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. /// 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![]; let mut result = vec![];
for backend in AnyBackend::iter() { for backend in AnyBackend::all(config) {
result.push(backend.get_section()); result.push(backend.backend_info().section);
} }
result.sort_unstable(); result.sort_unstable();
result 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::fmt::Display;
use std::fs::{create_dir, read_to_string, File}; use std::fs::{create_dir, read_to_string, File};
use std::hash::Hash; use std::hash::Hash;
@@ -11,11 +11,27 @@ use walkdir::WalkDir;
use crate::path::get_relative_path; use crate::path::get_relative_path;
use super::{Package, Section}; use crate::prelude::*;
/// A set of groups /// A set of groups
pub type Groups = BTreeSet<Group>; 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. /// Representation of a group file.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Group { pub struct Group {
@@ -23,7 +39,7 @@ pub struct Group {
/// base dir). /// base dir).
pub name: String, pub name: String,
/// The sections in the file which in turn hold the packages. /// 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. /// The absolute path of the original file.
pub path: PathBuf, pub path: PathBuf,
/// Whether the main program should warn this group being loaded from a symlink. /// 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 name = extract_group_name(path, group_dir.as_ref());
let mut lines = content.lines().peekable(); let mut lines = content.lines().peekable();
let mut sections = HashSet::new(); let mut sections = Sections::new();
while lines.peek().is_some() { while lines.peek().is_some() {
let result = Section::try_from_lines(&mut lines).context("reading section"); 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 /// This function returns an error if the group file cannot be read, or if the
/// file cannot be written to. /// 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) let mut content = read_to_string(&self.path)
.with_context(|| format!("reading existing file contents from {:?}", &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( fn write_packages_to_existing_section(
group_file_content: &mut String, group_file_content: &mut String,
section_header: &str, section_header: &str,
packages: &[Package], packages: &Packages,
) -> Result<()> { ) -> Result<()> {
let idx_of_first_package_line_in_section = let idx_of_first_package_line_in_section =
find_first_package_line_in_section(group_file_content, section_header)?; 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( fn add_new_section_with_packages(
group_file_content: &mut String, group_file_content: &mut String,
section_header: &str, section_header: &str,
packages: &[Package], packages: &Packages,
) { ) {
group_file_content.push('\n'); group_file_content.push('\n');
group_file_content.push_str(section_header); 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. sections.
*/ */
mod group; pub mod group;
mod package; pub mod package;
mod section; pub mod section;
pub use group::Group;
pub use group::Groups;
pub use package::Package;
pub use section::Section;
+23 -17
View File
@@ -1,9 +1,12 @@
use std::cmp::Ordering;
use std::collections::BTreeSet;
use std::fmt::{Display, Write}; 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 /// A struct to represent a single package, consisting of a `name`, and
/// optionally a `repo`. /// optionally a `repo`.
#[derive(Debug, Eq, PartialOrd, Ord, Clone)] #[derive(Debug, Clone)]
pub struct Package { pub struct Package {
/// The name of the package /// The name of the package
pub name: String, 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. /// 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. /// Returns `None` if there is nothing left after trimming.
pub fn try_from<S>(s: S) -> Option<Self> pub fn try_from<S>(s: S) -> Option<Self>
where where
@@ -69,22 +72,25 @@ impl Package {
impl PartialEq for Package { impl PartialEq for Package {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
let self_repo = self.repo.as_ref(); self.cmp(other).is_eq()
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
} }
} }
impl Eq for Package {}
impl Hash for Package { impl PartialOrd for Package {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.name.hash(state); 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::fmt::{Display, Write};
use std::hash::Hash; use std::hash::Hash;
use std::iter::Peekable; use std::iter::Peekable;
use anyhow::{ensure, Context, Result}; use anyhow::{ensure, Context, Result};
use super::Package; use crate::prelude::*;
pub type Sections = BTreeSet<Section>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Section { pub struct Section {
pub name: String, pub name: String,
pub packages: HashSet<Package>, pub packages: Packages,
} }
impl Section { impl Section {
pub fn new(name: String, packages: HashSet<Package>) -> Self { pub fn new(name: String, packages: Packages) -> Self {
Self { name, packages } Self { name, packages }
} }
pub fn try_from_lines<'a>(iter: &mut Peekable<impl Iterator<Item = &'a str>>) -> Result<Self> { pub fn try_from_lines<'a>(iter: &mut Peekable<impl Iterator<Item = &'a str>>) -> Result<Self> {
let name = find_next_section_name(iter)?; let name = find_next_section_name(iter)?;
let mut packages = HashSet::new(); let mut packages = Packages::new();
while next_line_might_be_package(iter) { while next_line_might_be_package(iter) {
if let Some(package) = Package::try_from(iter.next().expect("we checked this is some")) 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 package_name = package.name.clone();
let newly_inserted = packages.insert(package); let newly_inserted = packages.insert(package);
+4 -5
View File
@@ -35,10 +35,9 @@ mod review;
mod search; mod search;
mod ui; mod ui;
#[allow(unused_imports)]
mod prelude;
pub mod path; pub mod path;
pub use crate::config::Config; pub use prelude::{Config, Error, Group};
pub use crate::errors::Error;
pub use crate::grouping::Group;
pub use crate::grouping::Groups;
pub use crate::grouping::Package;
+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; use super::strategy::Strategy;
@@ -48,9 +48,9 @@ impl ReviewsPerBackend {
let mut result = vec![]; let mut result = vec![];
for (backend, actions) in self { for (backend, actions) in self {
let mut to_delete = vec![]; let mut to_delete = Packages::new();
let mut assign_group = vec![]; let mut assign_group = vec![];
let mut as_dependency = vec![]; let mut as_dependency = Packages::new();
extract_actions( extract_actions(
actions, actions,
@@ -91,15 +91,19 @@ pub enum ContinueWithReview {
fn extract_actions( fn extract_actions(
actions: Vec<ReviewAction>, actions: Vec<ReviewAction>,
to_delete: &mut Vec<Package>, to_delete: &mut Packages,
assign_group: &mut Vec<(Package, Group)>, assign_group: &mut Vec<(Package, Group)>,
as_dependency: &mut Vec<Package>, as_dependency: &mut Packages,
) { ) {
for action in actions { for action in actions {
match action { 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::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 anyhow::Result;
use crate::backend::backend_trait::Backend; use crate::prelude::*;
use crate::backend::todo_per_backend::ToDoPerBackend;
use crate::ui::{get_user_confirmation, read_single_char_from_terminal}; 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::datastructures::{ContinueWithReview, ReviewAction, ReviewIntention, ReviewsPerBackend};
use self::strategy::Strategy; 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 { 'outer: for (backend, packages) in todo_per_backend {
let mut actions = vec![]; let mut actions = vec![];
for package in packages { 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)? { match get_action_for_package(package, groups, &mut actions, &backend)? {
ContinueWithReview::Yes => continue, ContinueWithReview::Yes => continue,
ContinueWithReview::No => return Ok(()), ContinueWithReview::No => return Ok(()),
+6 -9
View File
@@ -1,23 +1,20 @@
use anyhow::Result; use anyhow::Result;
use crate::{ use crate::prelude::*;
backend::{backend_trait::Backend, AnyBackend},
Group, Package,
};
#[derive(Debug)] #[derive(Debug)]
pub struct Strategy { pub struct Strategy {
backend: AnyBackend, backend: AnyBackend,
delete: Vec<Package>, delete: Packages,
as_dependency: Vec<Package>, as_dependency: Packages,
assign_group: Vec<(Package, Group)>, assign_group: Vec<(Package, Group)>,
} }
impl Strategy { impl Strategy {
pub fn new( pub fn new(
backend: AnyBackend, backend: AnyBackend,
delete: Vec<Package>, delete: Packages,
as_dependency: Vec<Package>, as_dependency: Packages,
assign_group: Vec<(Package, Group)>, assign_group: Vec<(Package, Group)>,
) -> Self { ) -> Self {
Self { Self {
@@ -49,7 +46,7 @@ impl Strategy {
return; return;
} }
println!("[{}]", self.backend.get_section()); println!("[{}]", self.backend.backend_info().section);
if !self.delete.is_empty() { if !self.delete.is_empty() {
println!("delete:"); println!("delete:");
+1 -5
View File
@@ -1,14 +1,10 @@
use std::iter::Peekable; use std::iter::Peekable;
use std::vec::IntoIter; use std::vec::IntoIter;
use crate::prelude::*;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use regex::Regex; use regex::Regex;
use crate::{
grouping::{Group, Package, Section},
Groups,
};
/// Find all packages in all groups whose name match the regex from the /// 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 /// command-line arguments. Print the name of the packages per group and
/// section. /// section.