Backend trait refactors

- 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`
This commit is contained in:
ripytide
2024-04-24 17:03:42 +01:00
parent 15b50d8cb0
commit 17e42ca435
26 changed files with 524 additions and 560 deletions
+26 -37
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);
@@ -67,12 +52,14 @@ 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: &[Package], 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 {
@@ -84,13 +71,15 @@ 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: &[Package], 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()
} }
+29 -34
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,36 +19,35 @@ 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()));
} }
@@ -75,12 +66,14 @@ 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: &[Package], 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 {
@@ -92,11 +85,13 @@ 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: &[Package], 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 {
+36 -38
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)?;
@@ -83,12 +75,14 @@ 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: &[Package], 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 {
@@ -107,12 +101,14 @@ 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: &[Package], 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,8 +119,10 @@ 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)
+35 -43
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: &[Package], 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 {
@@ -92,17 +80,19 @@ impl Backend for Flatpak {
} }
fn make_dependency(&self, _: &[Package]) -> Result<()> { fn make_dependency(&self, _: &[Package]) -> 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: &[Package], 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}"));
+28 -44
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: &[Package]) -> 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")?
+22 -29
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, _: &[Package]) -> 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)
} }
+27 -35
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,13 +62,13 @@ 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, _: &[Package]) -> 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: &[Package], _: bool) -> Result<()> {
@@ -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 {
+35 -30
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)?;
@@ -87,11 +80,13 @@ 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: &[Package], 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 {
@@ -102,11 +97,13 @@ impl Backend for Void {
} }
fn remove_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> { fn remove_packages(&self, packages: &[Package], 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 {
@@ -117,8 +114,14 @@ impl Backend for Void {
} }
fn make_dependency(&self, packages: &[Package]) -> Result<()> { fn make_dependency(&self, packages: &[Package]) -> 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)
+68 -81
View File
@@ -1,68 +1,54 @@
use std::cmp::{Eq, Ord}; use std::cmp::{Eq, Ord};
use std::collections::{HashMap, HashSet}; use std::collections::HashMap;
use std::hash::Hash; 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 +56,17 @@ 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 group_package_map = to_hashmap(to_assign);
let section_header = format!("[{}]", self.get_section()); 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)?;
@@ -93,11 +83,13 @@ 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: &[Package], 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 +105,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.
///
/// # Errors
///
/// Returns an error if the external command fails.
fn make_dependency(&self, packages: &[Package]) -> Result<()> { fn make_dependency(&self, packages: &[Package]) -> Result<()> {
let mut cmd = Command::new(self.get_binary()); let backend_info = self.backend_info();
cmd.args(self.get_switches_make_dependency());
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 +127,17 @@ pub trait Backend {
/// Remove the specified packages. /// Remove the specified packages.
/// ///
/// # Errors
///
/// Returns an error if the external command fails.
fn remove_packages(&self, packages: &[Package], noconfirm: bool) -> Result<()> { fn remove_packages(&self, packages: &[Package], 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);
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,39 +147,20 @@ 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 /// For a vector of tuples containing a `V` and `K`, where a `K` may occur more than
-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;
+70 -18
View File
@@ -1,23 +1,53 @@
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, sorted alphabetically.
///
/// # Errors
///
/// Returns an error if the backend fails to get the explicitly installed packages.
pub fn get_unmanaged_packages_sorted(&self) -> Result<Vec<Package>> {
let installed = self
.any_backend
.get_explicitly_installed_packages()
.context("could not get explicitly installed packages")?;
let mut diff: Vec<_> = installed.difference(&self.packages).cloned().collect();
diff.sort_unstable();
Ok(diff)
}
/// Get missing packages, sorted alphabetically.
///
/// # Errors
///
/// Returns an error if the backend fails to get the installed packages.
pub fn get_missing_packages_sorted(&self) -> Result<Vec<Package>> {
let installed = self
.any_backend
.get_all_installed_packages()
.context("could not get installed packages")?;
let mut diff: Vec<_> = self.packages.difference(&installed).cloned().collect();
diff.sort_unstable();
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 +61,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.
@@ -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}"))?;
} }
+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:?}");
} }
+48 -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,40 @@ 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(diff) => todo_unmanaged.push((any_backend.clone(), diff)),
Err(error) => show_backend_query_error(&error, &backend), Err(error) => show_backend_query_error(&error, any_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. /// Create the parent directory of the `path` if that directory does not exist.
@@ -553,14 +537,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 +561,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
+20 -4
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");
+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;
+3
View File
@@ -1,6 +1,9 @@
use std::collections::BTreeSet;
use std::fmt::{Display, Write}; use std::fmt::{Display, Write};
use std::hash::Hash; 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, Eq, PartialOrd, Ord, Clone)]
+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);
+5 -8
View File
@@ -16,12 +16,10 @@
clippy::unwrap_used, clippy::unwrap_used,
clippy::use_debug, clippy::use_debug,
clippy::use_self, clippy::use_self,
clippy::wildcard_dependencies, clippy::wildcard_dependencies
missing_docs
)] )]
pub(crate) mod backend; pub(crate) mod backend;
#[allow(missing_docs)]
pub mod cli; pub mod cli;
mod cmd; mod cmd;
@@ -35,10 +33,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;
+1 -2
View File
@@ -10,8 +10,7 @@
clippy::unwrap_used, clippy::unwrap_used,
clippy::use_debug, clippy::use_debug,
clippy::use_self, clippy::use_self,
clippy::wildcard_dependencies, clippy::wildcard_dependencies
missing_docs
)] )]
use std::path::Path; use std::path::Path;
+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;
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::{backend::AnyBackend, Group, Package}; use crate::prelude::*;
use super::strategy::Strategy; use super::strategy::Strategy;
+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(()),
+2 -5
View File
@@ -1,9 +1,6 @@
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 {
@@ -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.