major overhaul of Backend from dynamic to static dispatch using an big enum

This commit is contained in:
ripytide
2024-04-22 14:08:53 +01:00
parent 636a7a02ec
commit df5b229d23
36 changed files with 382 additions and 590 deletions
+9 -1
View File
@@ -1,9 +1,17 @@
# Contributing # Contributing
Thank you for considering to contribute to `pacdef`. The recommended workflow is this: ## General Steps
Thank you for considering to contribute to `pacdef`. The recommended workflow is
this:
1. Open a github issue, mention that you would like to fix the issue in a PR. 1. Open a github issue, mention that you would like to fix the issue in a PR.
2. Wait for approval. 2. Wait for approval.
3. Fork the repository and implement your fix / feature. 3. Fork the repository and implement your fix / feature.
4. Make sure your code generates no warnings, and passes `rustfmt` and `clippy`. 4. Make sure your code generates no warnings, and passes `rustfmt` and `clippy`.
5. Open the pull request. 5. Open the pull request.
## Rust-Analyzer Issues
Rust Analyzer may not work unless both the `pacutils` and `apt` packages are
installed. On Arch that is, this may vary on other distros.
Generated
+13 -10
View File
@@ -215,6 +215,18 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "enum_dispatch"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
@@ -395,8 +407,8 @@ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"const_format", "const_format",
"enum_dispatch",
"libc", "libc",
"pacdef_macros",
"path-absolutize", "path-absolutize",
"regex", "regex",
"rstest", "rstest",
@@ -409,15 +421,6 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "pacdef_macros"
version = "1.0.1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "path-absolutize" name = "path-absolutize"
version = "3.1.1" version = "3.1.1"
+1 -2
View File
@@ -12,8 +12,7 @@ categories = ["command-line-utilities"]
rust-version = "1.74" rust-version = "1.74"
[workspace.dependencies] [workspace.dependencies]
pacdef_macros = { path = "crates/pacdef_macros", version = "1.0" } pacdef = { path = "crates/pacdef" }
pacdef = { path = "crates/pacdef", version = "1.6" }
[profile.release] [profile.release]
lto = "off" lto = "off"
-83
View File
@@ -1,83 +0,0 @@
# This is a configuration file for the bacon tool
#
# Bacon repository: https://github.com/Canop/bacon
# Complete help on configuration: https://dystroy.org/bacon/config/
# You can also check bacon's own bacon.toml file
# as an example: https://github.com/Canop/bacon/blob/main/bacon.toml
default_job = "check"
[jobs.check]
command = ["cargo", "check", "--color", "always", "--features", "arch"]
need_stdout = false
[jobs.check-all]
command = [
"cargo",
"check",
"--all-targets",
"--color",
"always",
"--features",
"arch",
]
need_stdout = false
[jobs.clippy]
command = [
"cargo",
"clippy",
"--all-targets",
"--color",
"always",
"--features",
"arch",
]
need_stdout = false
[jobs.test]
command = [
"cargo",
"test",
"--color",
"always",
"--package",
"pacdef",
"--",
"--color",
"always", # see https://github.com/Canop/bacon/issues/124
]
need_stdout = true
[jobs.doc]
command = ["cargo", "doc", "--color", "always", "--no-deps"]
need_stdout = false
# If the doc compiles, then it opens in your browser and bacon switches
# to the previous job
[jobs.doc-open]
command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"]
need_stdout = false
on_success = "back" # so that we don't open the browser at each change
# You can run your application and have the result displayed in bacon,
# *if* it makes sense for this crate. You can run an example the same
# way. Don't forget the `--color always` part or the errors won't be
# properly parsed.
[jobs.run]
command = [
"cargo",
"run",
"--color",
"always",
# put launch parameters for your program behind a `--` separator
]
need_stdout = true
allow_warnings = true
# You may define here keybindings that would be specific to
# a project, for example a shortcut to launch a specific job.
# Shortcuts to internal functions (scrolling, toggling, etc.)
# should go in your personal global prefs.toml file instead.
[keybindings]
# alt-m = "job:my-job"
+1 -2
View File
@@ -19,14 +19,13 @@ regex = { version = "1.10", default-features = false, features = ["std"] }
termios = "0.3" termios = "0.3"
walkdir = "2.5" walkdir = "2.5"
libc = "0.2" libc = "0.2"
enum_dispatch = "0.3"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_yaml = "0.9" serde_yaml = "0.9"
pacdef_macros.workspace = true
# backends # backends
alpm = { version = "3.0", optional = true } alpm = { version = "3.0", optional = true }
rust-apt = { version = "0.7", optional = true } rust-apt = { version = "0.7", optional = true }
+3 -3
View File
@@ -1,11 +1,11 @@
use self::datastructure::Arguments;
mod cli; mod cli;
mod datastructure; pub mod datastructure;
mod parsing; mod parsing;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
pub use datastructure::*;
/// Get and parse the CLI arguments. /// Get and parse the CLI arguments.
#[must_use] #[must_use]
pub fn get() -> Arguments { pub fn get() -> Arguments {
+17 -17
View File
@@ -1,4 +1,6 @@
use super::datastructure::*; use super::datastructure::{
Arguments, Edit, Force, GroupAction, Groups, Noconfirm, OutputDir, PackageAction, Regex,
};
const ARGS_CONSISTENT: &str = "argument declaration and parsing must be consistent"; const ARGS_CONSISTENT: &str = "argument declaration and parsing must be consistent";
@@ -13,30 +15,28 @@ pub(super) fn parse(args: clap::ArgMatches) -> Arguments {
} }
fn parse_group_args(args: &clap::ArgMatches) -> GroupAction { fn parse_group_args(args: &clap::ArgMatches) -> GroupAction {
use GroupAction::*;
match args.subcommand() { match args.subcommand() {
Some(("edit", args)) => Edit(get_groups(args)), Some(("edit", args)) => GroupAction::Edit(get_groups(args)),
Some(("export", args)) => Export(get_groups(args), get_output_dir(args), get_force(args)), Some(("export", args)) => {
Some(("import", args)) => Import(get_groups(args)), GroupAction::Export(get_groups(args), get_output_dir(args), get_force(args))
Some(("list", _)) => List, }
Some(("new", args)) => New(get_groups(args), get_edit(args)), Some(("import", args)) => GroupAction::Import(get_groups(args)),
Some(("remove", args)) => Remove(get_groups(args)), Some(("list", _)) => GroupAction::List,
Some(("show", args)) => Show(get_groups(args)), Some(("new", args)) => GroupAction::New(get_groups(args), get_edit(args)),
Some(("remove", args)) => GroupAction::Remove(get_groups(args)),
Some(("show", args)) => GroupAction::Show(get_groups(args)),
Some(value) => panic!("group subcommand was not matched: {value:?}"), Some(value) => panic!("group subcommand was not matched: {value:?}"),
None => unreachable!("prevented by clap"), None => unreachable!("prevented by clap"),
} }
} }
fn parse_package_args(args: &clap::ArgMatches) -> PackageAction { fn parse_package_args(args: &clap::ArgMatches) -> PackageAction {
use PackageAction::*;
match args.subcommand() { match args.subcommand() {
Some(("clean", args)) => Clean(get_noconfirm(args)), Some(("clean", args)) => PackageAction::Clean(get_noconfirm(args)),
Some(("review", _)) => Review, Some(("review", _)) => PackageAction::Review,
Some(("search", args)) => Search(get_regex(args)), Some(("search", args)) => PackageAction::Search(get_regex(args)),
Some(("sync", args)) => Sync(get_noconfirm(args)), Some(("sync", args)) => PackageAction::Sync(get_noconfirm(args)),
Some(("unmanaged", _)) => Unmanaged, Some(("unmanaged", _)) => PackageAction::Unmanaged,
Some(value) => panic!("package subcommand was not matched: {value:?}"), Some(value) => panic!("package subcommand was not matched: {value:?}"),
None => unreachable!("prevented by clap"), None => unreachable!("prevented by clap"),
} }
+17 -13
View File
@@ -12,9 +12,23 @@ use crate::{Group, Package};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Arch { pub struct Arch {
pub(crate) binary: String, pub binary: String,
pub(crate) aur_rm_args: Vec<String>, pub aur_rm_args: Vec<String>,
pub(crate) packages: HashSet<Package>, pub packages: HashSet<Package>,
}
impl Arch {
pub fn new() -> Self {
Self {
binary: BINARY.to_string(),
aur_rm_args: vec![],
packages: HashSet::new(),
}
}
}
impl Default for Arch {
fn default() -> Self {
Self::new()
}
} }
const BINARY: Text = "pacman"; const BINARY: Text = "pacman";
@@ -117,13 +131,3 @@ fn convert_to_pacdef_packages(packages: HashSet<String>) -> HashSet<Package> {
fn get_db_handle() -> Result<Alpm> { fn get_db_handle() -> Result<Alpm> {
Alpm::new("/", "/var/lib/pacman").context("connecting to DB using expected default values") Alpm::new("/", "/var/lib/pacman").context("connecting to DB using expected default values")
} }
impl Arch {
pub(crate) fn new() -> Self {
Self {
binary: BINARY.to_string(),
aur_rm_args: vec![],
packages: HashSet::new(),
}
}
}
+13 -9
View File
@@ -12,7 +12,19 @@ use crate::{Group, Package};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Debian { pub struct Debian {
pub(crate) packages: HashSet<Package>, pub packages: HashSet<Package>,
}
impl Debian {
pub fn new() -> Self {
Self {
packages: HashSet::new(),
}
}
}
impl Default for Debian {
fn default() -> Self {
Self::new()
}
} }
const BINARY: Text = "apt"; const BINARY: Text = "apt";
@@ -94,11 +106,3 @@ impl Backend for Debian {
run_external_command(cmd) run_external_command(cmd)
} }
} }
impl Debian {
pub(crate) fn new() -> Self {
Self {
packages: HashSet::new(),
}
}
}
+13 -9
View File
@@ -10,7 +10,19 @@ use crate::{Group, Package};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Fedora { pub struct Fedora {
pub(crate) packages: HashSet<Package>, pub packages: HashSet<Package>,
}
impl Fedora {
pub fn new() -> Self {
Self {
packages: HashSet::new(),
}
}
}
impl Default for Fedora {
fn default() -> Self {
Self::new()
}
} }
const BINARY: Text = "dnf"; const BINARY: Text = "dnf";
@@ -123,14 +135,6 @@ impl Backend for Fedora {
} }
} }
impl Fedora {
pub fn new() -> Self {
Self {
packages: HashSet::new(),
}
}
}
fn create_package(package: &str) -> Package { fn create_package(package: &str) -> Package {
if DEFAULT_REPOS.iter().any(|repo| package.contains(repo)) && !package.contains("copr") { if DEFAULT_REPOS.iter().any(|repo| package.contains(repo)) && !package.contains("copr") {
let package = package.split('/').nth(1).expect("Cannot be empty!"); let package = package.split('/').nth(1).expect("Cannot be empty!");
+40 -36
View File
@@ -10,8 +10,46 @@ use crate::{Group, Package};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Flatpak { pub struct Flatpak {
pub(crate) packages: HashSet<Package>, pub packages: HashSet<Package>,
pub(crate) systemwide: bool, pub systemwide: bool,
}
impl Flatpak {
pub fn new() -> Self {
Self {
packages: HashSet::new(),
systemwide: true,
}
}
fn get_switches_runtime(&self) -> Switches {
if self.systemwide {
&[]
} else {
&["--user"]
}
}
fn get_installed_packages(&self, include_implicit: bool) -> Result<HashSet<Package>> {
let mut cmd = Command::new(BINARY);
cmd.args(["list", "--columns=application"]);
if !include_implicit {
cmd.arg("--app");
}
if !self.systemwide {
cmd.arg("--user");
}
let output = String::from_utf8(cmd.output()?.stdout)?;
Ok(output
.lines()
.map(Package::from)
.collect::<HashSet<Package>>())
}
}
impl Default for Flatpak {
fn default() -> Self {
Self::new()
}
} }
const BINARY: Text = "flatpak"; const BINARY: Text = "flatpak";
@@ -84,37 +122,3 @@ impl Backend for Flatpak {
run_external_command(cmd) run_external_command(cmd)
} }
} }
impl Flatpak {
pub(crate) fn new() -> Self {
Self {
packages: HashSet::new(),
systemwide: true,
}
}
fn get_switches_runtime(&self) -> Switches {
if self.systemwide {
&[]
} else {
&["--user"]
}
}
fn get_installed_packages(&self, include_implicit: bool) -> Result<HashSet<Package>> {
let mut cmd = Command::new(BINARY);
cmd.args(["list", "--columns=application"]);
if !include_implicit {
cmd.arg("--app");
}
if !self.systemwide {
cmd.arg("--user");
}
let output = String::from_utf8(cmd.output()?.stdout)?;
Ok(output
.lines()
.map(Package::from)
.collect::<HashSet<Package>>())
}
}
+44 -40
View File
@@ -9,10 +9,52 @@ use crate::backend::backend_trait::{Backend, Switches, Text};
use crate::backend::macros::impl_backend_constants; use crate::backend::macros::impl_backend_constants;
use crate::{Group, Package}; use crate::{Group, Package};
macro_rules! ERROR{
($bin:expr) => {
panic!("Cannot use {} for package management in python. Please use a valid package manager like pip or pipx.", $bin)
};
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Python { pub struct Python {
pub(crate) binary: String, pub binary: String,
pub(crate) packages: HashSet<Package>, pub packages: HashSet<Package>,
}
impl Python {
pub fn new() -> Self {
Self {
binary: BINARY.to_string(),
packages: HashSet::new(),
}
}
fn get_switches_runtime(&self) -> Switches {
match self.get_binary() {
"pip" => &["list", "--format", "json", "--not-required", "--user"],
"pipx" => &["list", "--json"],
_ => ERROR!(self.get_binary()),
}
}
fn get_switches_explicit(&self) -> Switches {
match self.get_binary() {
"pip" => &["list", "--format", "json", "--user"],
"pipx" => &["list", "--json"],
_ => ERROR!(self.get_binary()),
}
}
fn extract_packages(&self, output: Value) -> Result<HashSet<Package>> {
match self.get_binary() {
"pip" => extract_pacdef_packages(output),
"pipx" => extract_pacdef_packages_pipx(output),
_ => ERROR!(self.get_binary()),
}
}
}
impl Default for Python {
fn default() -> Self {
Self::new()
}
} }
const BINARY: Text = "pip"; const BINARY: Text = "pip";
@@ -26,12 +68,6 @@ const SWITCHES_REMOVE: Switches = &["uninstall"];
const SUPPORTS_AS_DEPENDENCY: bool = false; const SUPPORTS_AS_DEPENDENCY: bool = false;
macro_rules! ERROR{
($bin:expr) => {
panic!("Cannot use {} for package management in python. Please use a valid package manager like pip or pipx.", $bin)
};
}
impl Backend for Python { impl Backend for Python {
impl_backend_constants!(); impl_backend_constants!();
@@ -64,38 +100,6 @@ fn run_pip_command(cmd: &mut Command, args: &[&str]) -> Result<Value> {
Ok(val) Ok(val)
} }
impl Python {
pub(crate) fn new() -> Self {
Self {
binary: BINARY.to_string(),
packages: HashSet::new(),
}
}
fn get_switches_runtime(&self) -> Switches {
match self.get_binary() {
"pip" => &["list", "--format", "json", "--not-required", "--user"],
"pipx" => &["list", "--json"],
_ => ERROR!(self.get_binary()),
}
}
fn get_switches_explicit(&self) -> Switches {
match self.get_binary() {
"pip" => &["list", "--format", "json", "--user"],
"pipx" => &["list", "--json"],
_ => ERROR!(self.get_binary()),
}
}
fn extract_packages(&self, output: Value) -> Result<HashSet<Package>> {
match self.get_binary() {
"pip" => extract_pacdef_packages(output),
"pipx" => extract_pacdef_packages_pipx(output),
_ => ERROR!(self.get_binary()),
}
}
}
fn extract_pacdef_packages(value: Value) -> Result<HashSet<Package>> { fn extract_pacdef_packages(value: Value) -> Result<HashSet<Package>> {
let result = value let result = value
.as_array() .as_array()
+13 -9
View File
@@ -12,7 +12,19 @@ use crate::{Group, Package};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Rust { pub struct Rust {
pub(crate) packages: HashSet<Package>, pub packages: HashSet<Package>,
}
impl Rust {
pub fn new() -> Self {
Self {
packages: HashSet::new(),
}
}
}
impl Default for Rust {
fn default() -> Self {
Self::new()
}
} }
const BINARY: Text = "cargo"; const BINARY: Text = "cargo";
@@ -76,14 +88,6 @@ fn extract_packages(json: &Value) -> Result<HashSet<Package>> {
Ok(result) Ok(result)
} }
impl Rust {
pub(crate) fn new() -> Self {
Self {
packages: HashSet::new(),
}
}
}
fn get_crates_file() -> Result<PathBuf> { fn get_crates_file() -> Result<PathBuf> {
let mut result = crate::path::get_cargo_home().context("getting cargo home dir")?; let mut result = crate::path::get_cargo_home().context("getting cargo home dir")?;
result.push(".crates2.json"); result.push(".crates2.json");
+17 -1
View File
@@ -12,9 +12,25 @@ use std::process::Command;
use self::helpers::{ use self::helpers::{
group_components_by_toolchains, install_components, toolchain_of_component_was_already_removed, group_components_by_toolchains, install_components, toolchain_of_component_was_already_removed,
}; };
pub use self::types::Rustup;
use self::types::{Repotype, RustupPackage}; use self::types::{Repotype, RustupPackage};
#[derive(Debug, Clone)]
pub struct Rustup {
pub packages: HashSet<Package>,
}
impl Rustup {
pub fn new() -> Self {
Self {
packages: HashSet::new(),
}
}
}
impl Default for Rustup {
fn default() -> Self {
Self::new()
}
}
const BINARY: Text = "rustup"; const BINARY: Text = "rustup";
const SECTION: Text = "rustup"; const SECTION: Text = "rustup";
@@ -1,13 +1,7 @@
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use std::collections::HashSet;
use crate::{backend::backend_trait::Switches, Package}; use crate::{backend::backend_trait::Switches, Package};
#[derive(Debug, Clone)]
pub struct Rustup {
pub(crate) packages: HashSet<Package>,
}
#[derive(Debug)] #[derive(Debug)]
pub enum Repotype { pub enum Repotype {
Toolchain, Toolchain,
@@ -28,14 +22,6 @@ pub struct RustupPackage {
pub component: Option<String>, pub component: Option<String>,
} }
impl Rustup {
pub fn new() -> Self {
Self {
packages: HashSet::new(),
}
}
}
impl Repotype { impl Repotype {
fn try_from<T>(value: T) -> Result<Self> fn try_from<T>(value: T) -> Result<Self>
where where
+13 -9
View File
@@ -12,7 +12,19 @@ use crate::{Group, Package};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Void { pub struct Void {
pub(crate) packages: HashSet<Package>, pub packages: HashSet<Package>,
}
impl Void {
pub fn new() -> Self {
Self {
packages: HashSet::new(),
}
}
}
impl Default for Void {
fn default() -> Self {
Self::new()
}
} }
const BINARY: Text = "xbps-install"; const BINARY: Text = "xbps-install";
@@ -124,11 +136,3 @@ impl Backend for Void {
run_external_command(cmd) run_external_command(cmd)
} }
} }
impl Void {
pub(crate) fn new() -> Self {
Self {
packages: HashSet::new(),
}
}
}
+5 -9
View File
@@ -1,7 +1,5 @@
use std::any::Any;
use std::cmp::{Eq, Ord}; use std::cmp::{Eq, Ord};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
use std::hash::Hash; use std::hash::Hash;
use std::process::Command; use std::process::Command;
use std::rc::Rc; use std::rc::Rc;
@@ -11,11 +9,12 @@ use anyhow::{Context, Result};
use crate::cmd::run_external_command; use crate::cmd::run_external_command;
use crate::{Group, Package}; use crate::{Group, Package};
pub(in crate::backend) type Switches = &'static [&'static str]; pub type Switches = &'static [&'static str];
pub(in crate::backend) type Text = &'static str; pub type Text = &'static str;
/// The trait of a struct that is used as a backend. /// The trait of a struct that is used as a backend.
pub trait Backend: Debug { #[enum_dispatch::enum_dispatch]
pub trait Backend {
/// Return the actual binary. Iff the backend supports different /// Return the actual binary. Iff the backend supports different
/// binaries, you will need to overwrite this implementation to return /// binaries, you will need to overwrite this implementation to return
/// the binary that was loaded at runtime. See /// the binary that was loaded at runtime. See
@@ -47,7 +46,7 @@ pub trait Backend: Debug {
/// Get CLI switches for the package manager to mark packages as /// Get CLI switches for the package manager to mark packages as
/// dependency. This is not supported by all package managers. See /// dependency. This is not supported by all package managers. See
/// [`Backend::supports_as_dependency`]. /// [`Backend::supports_as_dependency()`].
fn get_switches_make_dependency(&self) -> Switches; fn get_switches_make_dependency(&self) -> Switches;
/// Load all packages from a set of groups. The backend will visit all groups, /// Load all packages from a set of groups. The backend will visit all groups,
@@ -174,9 +173,6 @@ pub trait Backend: Debug {
Ok(diff) Ok(diff)
} }
/// Return a mutable reference to self as `Any`. Required for downcasting.
fn as_any_mut(&mut self) -> &mut dyn Any;
/// Whether the underlying package manager supports dependency packages. /// Whether the underlying package manager supports dependency packages.
fn supports_as_dependency(&self) -> bool; fn supports_as_dependency(&self) -> bool;
} }
-21
View File
@@ -1,21 +0,0 @@
use super::{Backend, Backends};
#[derive(Debug)]
pub struct BackendIter {
pub(crate) next: Option<Backends>,
}
impl Iterator for BackendIter {
type Item = Box<dyn Backend>;
fn next(&mut self) -> Option<Self::Item> {
match &self.next {
None => None,
Some(b) => {
let result = b.get_backend();
self.next = b.next();
Some(result)
}
}
}
}
-4
View File
@@ -47,10 +47,6 @@ macro_rules! impl_backend_constants {
}) })
} }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn supports_as_dependency(&self) -> bool { fn supports_as_dependency(&self) -> bool {
SUPPORTS_AS_DEPENDENCY SUPPORTS_AS_DEPENDENCY
} }
+45 -19
View File
@@ -1,26 +1,52 @@
mod actual; pub mod actual;
mod backend_trait; pub mod backend_trait;
mod iter; pub mod macros;
mod macros;
mod root; mod root;
mod todo_per_backend; pub mod todo_per_backend;
pub use backend_trait::Backend; use crate::backend::backend_trait::Switches;
pub use iter::BackendIter; use crate::backend::backend_trait::Text;
pub use todo_per_backend::ToDoPerBackend; use crate::Group;
use crate::Package;
use anyhow::Result;
use backend_trait::Backend;
use std::collections::HashSet;
use std::rc::Rc;
use pacdef_macros::Register; use self::actual::{
fedora::Fedora, flatpak::Flatpak, python::Python, rust::Rust, rustup::Rustup, void::Void,
};
#[derive(Debug, Register)] #[derive(Debug)]
pub enum Backends { #[enum_dispatch::enum_dispatch(Backend)]
pub enum AnyBackend {
#[cfg(feature = "arch")] #[cfg(feature = "arch")]
Arch, Arch(actual::arch::Arch),
#[cfg(feature = "debian")] #[cfg(feature = "debian")]
Debian, Debian(actual::debian::Debian),
Flatpak, Flatpak(Flatpak),
Fedora, Fedora(Fedora),
Python, Python(Python),
Rust, Rust(Rust),
Rustup, Rustup(Rustup),
Void, Void(Void),
}
impl AnyBackend {
/// Returns an iterator of every variant of backend.
pub fn iter() -> impl Iterator<Item = Self> {
vec![
#[cfg(feature = "arch")]
Self::Arch(actual::arch::Arch::new()),
#[cfg(feature = "debian")]
Self::Debian(actual::debian::Debian::new()),
Self::Flatpak(Flatpak::new()),
Self::Fedora(Fedora::new()),
Self::Python(Python::new()),
Self::Rust(Rust::new()),
Self::Rustup(Rustup::new()),
Self::Void(Void::new()),
]
.into_iter()
}
} }
+29 -17
View File
@@ -2,63 +2,60 @@ use std::fmt::Write;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use super::Backend; use super::{AnyBackend, Backend};
use crate::Package; use crate::Package;
/// A vector of tuples containing a `dyn Backend` 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.
/// ///
/// 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<(Box<dyn Backend>, Vec<Package>)>); pub struct ToDoPerBackend(Vec<(AnyBackend, Vec<Package>)>);
impl ToDoPerBackend { impl ToDoPerBackend {
pub(crate) fn new() -> Self { pub fn new() -> Self {
Self(vec![]) Self(vec![])
} }
pub(crate) fn push(&mut self, item: (Box<dyn Backend>, Vec<Package>)) { pub fn push(&mut self, item: (AnyBackend, Vec<Package>)) {
self.0.push(item); self.0.push(item);
} }
pub(crate) fn into_iter(self) -> impl Iterator<Item = (Box<dyn Backend>, Vec<Package>)> { pub fn iter(&self) -> impl Iterator<Item = &(AnyBackend, Vec<Package>)> {
self.0.into_iter()
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &(Box<dyn Backend>, Vec<Package>)> {
self.0.iter() self.0.iter()
} }
pub(crate) fn nothing_to_do_for_all_backends(&self) -> bool { pub fn nothing_to_do_for_all_backends(&self) -> bool {
self.0.iter().all(|(_, diff)| diff.is_empty()) self.0.iter().all(|(_, diff)| diff.is_empty())
} }
pub(crate) fn install_missing_packages(&self, noconfirm: bool) -> Result<()> { pub fn install_missing_packages(&self, noconfirm: bool) -> Result<()> {
for (backend, packages) in &self.0 { for (backend, packages) in &self.0 {
if packages.is_empty() { if packages.is_empty() {
continue; continue;
} }
Backend::install_packages(&**backend, packages, noconfirm) backend
.install_packages(packages, noconfirm)
.with_context(|| format!("installing packages for {}", backend.get_section()))?; .with_context(|| format!("installing packages for {}", backend.get_section()))?;
} }
Ok(()) Ok(())
} }
pub(crate) fn remove_unmanaged_packages(&self, noconfirm: bool) -> Result<()> { pub fn remove_unmanaged_packages(&self, noconfirm: bool) -> Result<()> {
for (backend, packages) in &self.0 { for (backend, packages) in &self.0 {
if packages.is_empty() { if packages.is_empty() {
continue; continue;
} }
Backend::remove_packages(&**backend, packages, noconfirm) backend
.remove_packages(packages, noconfirm)
.with_context(|| format!("removing packages for {}", backend.get_section()))?; .with_context(|| format!("removing packages for {}", backend.get_section()))?;
} }
Ok(()) Ok(())
} }
pub(crate) fn show(&self) -> Result<()> { pub fn show(&self) -> Result<()> {
let mut parts = vec![]; let mut parts = vec![];
for (backend, packages) in self.iter() { for (backend, packages) in self.iter() {
@@ -91,3 +88,18 @@ impl ToDoPerBackend {
Ok(()) Ok(())
} }
} }
impl Default for ToDoPerBackend {
fn default() -> Self {
Self::new()
}
}
impl IntoIterator for ToDoPerBackend {
type Item = (AnyBackend, Vec<Package>);
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
+43 -51
View File
@@ -7,8 +7,12 @@ use std::path::{Path, PathBuf};
use anyhow::{bail, ensure, Context, Result}; use anyhow::{bail, ensure, Context, Result};
use const_format::formatcp; use const_format::formatcp;
use crate::args::{self, PackageAction}; use crate::args::datastructure::{
use crate::backend::{Backend, Backends, ToDoPerBackend}; Arguments, Edit, Force, GroupAction, Groups, Noconfirm, OutputDir, PackageAction, Regex,
};
use crate::backend::backend_trait::Backend;
use crate::backend::todo_per_backend::ToDoPerBackend;
use crate::backend::AnyBackend;
use crate::cmd::run_edit_command; use crate::cmd::run_edit_command;
use crate::env::should_print_debug_info; use crate::env::should_print_debug_info;
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};
@@ -21,7 +25,7 @@ use crate::{review, Error};
/// Most data that is required during runtime of the program. /// Most data that is required during runtime of the program.
pub struct Pacdef { pub struct Pacdef {
/// The command line arguments. Is an `Option` so that we can take ownership later without cloning. /// The command line arguments. Is an `Option` so that we can take ownership later without cloning.
args: Option<args::Arguments>, args: Option<Arguments>,
/// The config of the program. /// The config of the program.
config: Config, config: Config,
/// The hashset of all groups. /// The hashset of all groups.
@@ -32,7 +36,7 @@ impl Pacdef {
/// Creates a new [`Pacdef`]. `config` should be passed from [`Config::load`], and `args` from /// Creates a new [`Pacdef`]. `config` should be passed from [`Config::load`], and `args` from
/// [`args::get`]. /// [`args::get`].
#[must_use] #[must_use]
pub const fn new(args: args::Arguments, config: Config, groups: HashSet<Group>) -> Self { pub const fn new(args: Arguments, config: Config, groups: HashSet<Group>) -> Self {
Self { Self {
args: Some(args), args: Some(args),
config, config,
@@ -52,47 +56,46 @@ impl Pacdef {
/// # Panics /// # Panics
/// ///
/// This function panics if the `args` field is `None`. /// This function panics if the `args` field is `None`.
#[allow(clippy::unit_arg)]
pub fn run_action_from_arg(mut self) -> Result<()> { pub fn run_action_from_arg(mut self) -> Result<()> {
match self match self
.args .args
.take() .take()
.expect("if there were no args we would not get to here") .expect("if there were no args we would not get to here")
{ {
args::Arguments::Group(group_args) => self.run_group_subcommand(&group_args), Arguments::Group(group_args) => self.run_group_subcommand(&group_args),
args::Arguments::Package(package_args) => self.run_package_subcommand(&package_args), Arguments::Package(package_args) => self.run_package_subcommand(&package_args),
args::Arguments::Version => Ok(self.show_version()), Arguments::Version => {
self.show_version();
Ok(())
}
} }
} }
fn run_group_subcommand(self, args: &args::GroupAction) -> Result<()> { fn run_group_subcommand(self, args: &GroupAction) -> Result<()> {
use args::GroupAction::*;
match args { match args {
Edit(args::Groups(groups)) => self.edit_groups(groups), GroupAction::Edit(Groups(groups)) => self.edit_groups(groups),
Export(args::Groups(groups), args::OutputDir(dir), args::Force(force)) => { GroupAction::Export(Groups(groups), OutputDir(dir), Force(force)) => {
self.export_groups(groups, dir.as_ref(), *force) self.export_groups(groups, dir.as_ref(), *force)
} }
Import(args::Groups(groups)) => self.import_groups(groups), GroupAction::Import(Groups(groups)) => self.import_groups(groups),
List => self.show_groups(), GroupAction::List => self.show_groups(),
New(args::Groups(groups), args::Edit(edit)) => self.new_groups(groups, *edit), GroupAction::New(Groups(groups), Edit(edit)) => self.new_groups(groups, *edit),
Remove(args::Groups(groups)) => self.remove_groups(groups), GroupAction::Remove(Groups(groups)) => self.remove_groups(groups),
Show(args::Groups(groups)) => self.show_group_content(groups), GroupAction::Show(Groups(groups)) => self.show_group_content(groups),
} }
} }
fn run_package_subcommand(mut self, args: &PackageAction) -> Result<()> { fn run_package_subcommand(mut self, args: &PackageAction) -> Result<()> {
use args::PackageAction::*;
match args { match args {
Clean(args::Noconfirm(noconfirm)) => self.clean_packages(*noconfirm), PackageAction::Clean(Noconfirm(noconfirm)) => self.clean_packages(*noconfirm),
Review => review::review(self.get_unmanaged_packages()?, self.groups), PackageAction::Review => review::review(self.get_unmanaged_packages()?, self.groups),
Search(args::Regex(regex)) => { PackageAction::Search(Regex(regex)) => {
self.warn_about_groups_that_arent_symlinks(); self.warn_about_groups_that_arent_symlinks();
search::search_packages(regex, &self.groups) search::search_packages(regex, &self.groups)
} }
Sync(args::Noconfirm(noconfirm)) => self.install_packages(*noconfirm), PackageAction::Sync(Noconfirm(noconfirm)) => self.install_packages(*noconfirm),
Unmanaged => self.show_unmanaged_packages(), PackageAction::Unmanaged => self.show_unmanaged_packages(),
} }
} }
@@ -103,7 +106,7 @@ impl Pacdef {
eprintln!("WARNING: no group files found"); eprintln!("WARNING: no group files found");
} }
for mut backend in Backends::iter() { for mut backend in AnyBackend::iter() {
if self if self
.config .config
.disabled_backends .disabled_backends
@@ -116,39 +119,33 @@ impl Pacdef {
continue; continue;
} }
self.overwrite_values_from_config(&mut *backend); self.overwrite_values_from_config(&mut backend);
backend.load(&self.groups); backend.load(&self.groups);
match backend.get_missing_packages_sorted() { match backend.get_missing_packages_sorted() {
Ok(diff) => to_install.push((backend, diff)), Ok(diff) => to_install.push((backend, diff)),
Err(error) => show_backend_query_error(&error, &*backend), Err(error) => show_backend_query_error(&error, &backend),
}; };
} }
Ok(to_install) Ok(to_install)
} }
fn overwrite_values_from_config(&mut self, backend: &mut dyn Backend) { fn overwrite_values_from_config(&mut self, backend: &mut AnyBackend) {
#[cfg(feature = "arch")] #[cfg(feature = "arch")]
{ {
if let Some(arch) = backend.as_any_mut().downcast_mut::<crate::backend::Arch>() { if let AnyBackend::Arch(arch) = backend {
arch.binary = self.config.aur_helper.clone(); arch.binary.clone_from(&self.config.aur_helper);
arch.aur_rm_args = self.config.aur_rm_args.clone(); arch.aur_rm_args.clone_from(&self.config.aur_rm_args);
} }
} }
if let Some(flatpak) = backend if let AnyBackend::Flatpak(flatpak) = backend {
.as_any_mut()
.downcast_mut::<crate::backend::Flatpak>()
{
flatpak.systemwide = self.config.flatpak_systemwide; flatpak.systemwide = self.config.flatpak_systemwide;
} }
if let Some(python) = backend if let AnyBackend::Python(python) = backend {
.as_any_mut() python.binary.clone_from(&self.config.pip_binary);
.downcast_mut::<crate::backend::Python>()
{
python.binary = self.config.pip_binary.clone();
} }
} }
@@ -204,7 +201,6 @@ impl Pacdef {
Ok(()) Ok(())
} }
#[allow(clippy::unused_self)]
fn show_version(self) { fn show_version(self) {
println!("{}", get_name_and_version()); println!("{}", get_name_and_version());
} }
@@ -237,7 +233,7 @@ impl Pacdef {
let mut result = ToDoPerBackend::new(); let mut result = ToDoPerBackend::new();
for mut backend in Backends::iter() { for mut backend in AnyBackend::iter() {
if self if self
.config .config
.disabled_backends .disabled_backends
@@ -250,12 +246,12 @@ impl Pacdef {
continue; continue;
} }
self.overwrite_values_from_config(&mut *backend); self.overwrite_values_from_config(&mut backend);
backend.load(&self.groups); backend.load(&self.groups);
match backend.get_unmanaged_packages_sorted() { match backend.get_unmanaged_packages_sorted() {
Ok(unmanaged) => result.push((backend, unmanaged)), Ok(unmanaged) => result.push((backend, unmanaged)),
Err(error) => show_backend_query_error(&error, &*backend), Err(error) => show_backend_query_error(&error, &backend),
}; };
} }
Ok(result) Ok(result)
@@ -265,7 +261,6 @@ impl Pacdef {
/// ///
/// This methods cannot return an error. It returns a `Result` to be consistent /// This methods cannot return an error. It returns a `Result` to be consistent
/// with other methods. /// with other methods.
#[allow(clippy::unnecessary_wraps)]
fn show_groups(self) -> Result<()> { fn show_groups(self) -> Result<()> {
self.warn_about_groups_that_arent_symlinks(); self.warn_about_groups_that_arent_symlinks();
@@ -354,7 +349,6 @@ impl Pacdef {
Ok(()) Ok(())
} }
#[allow(clippy::unused_self)]
fn import_groups(&self, file_names: &[String]) -> Result<()> { fn import_groups(&self, file_names: &[String]) -> Result<()> {
let files = get_absolutized_file_paths(file_names)?; let files = get_absolutized_file_paths(file_names)?;
let groups_dir = get_group_dir()?; let groups_dir = get_group_dir()?;
@@ -410,7 +404,6 @@ impl Pacdef {
/// - a group with the same name already exists, /// - a group with the same name already exists,
/// - the editor cannot be run, or /// - the editor cannot be run, or
/// - if we do not have permission to write to the group dir. /// - if we do not have permission to write to the group dir.
#[allow(clippy::unused_self)]
fn new_groups(&self, new_groups: &[String], edit: bool) -> Result<()> { fn new_groups(&self, new_groups: &[String], edit: bool) -> Result<()> {
let group_path = get_group_dir()?; let group_path = get_group_dir()?;
@@ -585,8 +578,7 @@ fn find_groups_by_name<'a>(names: &[String], groups: &'a HashSet<Group>) -> Resu
/// 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`.
#[allow(clippy::option_if_let_else)] fn show_backend_query_error(error: &anyhow::Error, backend: &AnyBackend) {
fn show_backend_query_error(error: &anyhow::Error, backend: &dyn Backend) {
let section = backend.get_section(); let section = backend.get_section();
if should_print_debug_info() { if should_print_debug_info() {
eprintln!("WARNING: skipping backend '{section}':"); eprintln!("WARNING: skipping backend '{section}':");
@@ -628,7 +620,7 @@ 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() -> Vec<&'static str> {
let mut result = vec![]; let mut result = vec![];
for backend in Backends::iter() { for backend in AnyBackend::iter() {
result.push(backend.get_section()); result.push(backend.get_section());
} }
result.sort_unstable(); result.sort_unstable();
+5 -5
View File
@@ -18,13 +18,13 @@ use super::{Package, Section};
pub struct Group { pub struct Group {
/// Name of the group (file name from which it was read, relative to the group /// Name of the group (file name from which it was read, relative to the group
/// base dir). /// base dir).
pub(crate) 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(crate) sections: HashSet<Section>, pub sections: HashSet<Section>,
/// The absolute path of the original file. /// The absolute path of the original file.
pub(crate) 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.
pub(crate) warn_symlink: bool, pub warn_symlink: bool,
} }
impl Group { impl Group {
@@ -177,7 +177,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(crate) fn save_packages(&self, section_header: &str, packages: &[Package]) -> Result<()> { pub fn save_packages(&self, section_header: &str, packages: &[Package]) -> 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))?;
+1
View File
@@ -8,6 +8,7 @@ one (strictly spoken zero) or more [`Package`]s. On start-up `pacdef` will load
all groups using [`Group::load`], which in turn will get all packages from all all groups using [`Group::load`], which in turn will get all packages from all
sections. sections.
*/ */
mod group; mod group;
mod package; mod package;
mod section; mod section;
+6 -5
View File
@@ -5,8 +5,10 @@ use std::hash::Hash;
/// optionally a `repo`. /// optionally a `repo`.
#[derive(Debug, Eq, PartialOrd, Ord, Clone)] #[derive(Debug, Eq, PartialOrd, Ord, Clone)]
pub struct Package { pub struct Package {
pub(crate) name: String, /// The name of the package
pub(crate) repo: Option<String>, pub name: String,
/// Optionally, which repository the package belongs to
pub repo: Option<String>,
} }
fn remove_comment_and_trim_whitespace(s: &str) -> &str { fn remove_comment_and_trim_whitespace(s: &str) -> &str {
@@ -51,7 +53,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.
/// Returns `None` if there is nothing left after trimming. /// Returns `None` if there is nothing left after trimming.
pub(crate) fn try_from<S>(s: S) -> Option<Self> pub fn try_from<S>(s: S) -> Option<Self>
where where
S: AsRef<str>, S: AsRef<str>,
{ {
@@ -116,11 +118,10 @@ mod tests {
assert_eq!(repo, None); assert_eq!(repo, None);
} }
#[allow(clippy::unwrap_used)]
#[test] #[test]
fn from() { fn from() {
let x = "myrepo/somepackage # ".to_string(); let x = "myrepo/somepackage # ".to_string();
let p = Package::try_from(x).unwrap(); let p = Package::try_from(x).expect("this should be a valid package line");
assert_eq!(p.name, "somepackage"); assert_eq!(p.name, "somepackage");
assert_eq!(p.repo, Some("myrepo".to_string())); assert_eq!(p.repo, Some("myrepo".to_string()));
} }
+2 -4
View File
@@ -14,13 +14,11 @@ pub struct Section {
} }
impl Section { impl Section {
pub(crate) fn new(name: String, packages: HashSet<Package>) -> Self { pub fn new(name: String, packages: HashSet<Package>) -> Self {
Self { name, packages } Self { name, packages }
} }
pub(crate) fn try_from_lines<'a>( pub fn try_from_lines<'a>(iter: &mut Peekable<impl Iterator<Item = &'a str>>) -> Result<Self> {
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 = HashSet::new();
+3 -7
View File
@@ -1,6 +1,4 @@
/*! //! This library contains all logic that happens in `pacdef`.
This library contains all logic that happens in `pacdef` under the hood.
*/
#![warn( #![warn(
clippy::as_conversions, clippy::as_conversions,
@@ -23,7 +21,7 @@ This library contains all logic that happens in `pacdef` under the hood.
)] )]
mod args; mod args;
mod backend; pub(crate) mod backend;
mod cmd; mod cmd;
mod config; mod config;
mod core; mod core;
@@ -40,6 +38,4 @@ pub use crate::config::Config;
pub use crate::core::Pacdef; pub use crate::core::Pacdef;
pub use crate::errors::Error; pub use crate::errors::Error;
pub use crate::grouping::Group; pub use crate::grouping::Group;
pub(crate) use crate::grouping::Package; pub use crate::grouping::Package;
extern crate pacdef_macros;
+1 -3
View File
@@ -1,6 +1,4 @@
/*! //! Main program for `pacdef`.
Main program for `pacdef`.
*/
#![warn( #![warn(
clippy::as_conversions, clippy::as_conversions,
+10 -6
View File
@@ -28,7 +28,7 @@ pub fn get_group_dir() -> Result<PathBuf> {
/// # Errors /// # Errors
/// ///
/// This function will return an error if `$XDG_CONFIG_HOME` cannot be determined. /// This function will return an error if `$XDG_CONFIG_HOME` cannot be determined.
pub(crate) fn get_pacdef_base_dir() -> Result<PathBuf> { pub fn get_pacdef_base_dir() -> Result<PathBuf> {
let mut dir = get_xdg_config_home().context("getting XDG_CONFIG_HOME")?; let mut dir = get_xdg_config_home().context("getting XDG_CONFIG_HOME")?;
dir.push("pacdef"); dir.push("pacdef");
Ok(dir) Ok(dir)
@@ -40,7 +40,7 @@ pub(crate) fn get_pacdef_base_dir() -> Result<PathBuf> {
/// ///
/// This function will return an error if neither the `$CARGO_HOME` nor /// This function will return an error if neither the `$CARGO_HOME` nor
/// the `$HOME` environment variables are set. /// the `$HOME` environment variables are set.
pub(crate) fn get_cargo_home() -> Result<PathBuf> { pub fn get_cargo_home() -> Result<PathBuf> {
if let Ok(config) = env::var("CARGO_HOME") { if let Ok(config) = env::var("CARGO_HOME") {
Ok(config.into()) Ok(config.into())
} else { } else {
@@ -72,7 +72,7 @@ fn get_xdg_config_home() -> Result<PathBuf> {
/// # Errors /// # Errors
/// ///
/// This function will return an error if the `$HOME` variable is not set. /// This function will return an error if the `$HOME` variable is not set.
pub(crate) fn get_home_dir() -> Result<PathBuf> { pub fn get_home_dir() -> Result<PathBuf> {
Ok(env::var("HOME").context("getting $HOME variable")?.into()) Ok(env::var("HOME").context("getting $HOME variable")?.into())
} }
@@ -106,7 +106,7 @@ pub fn get_config_path_old_version() -> Result<PathBuf> {
/// # Errors /// # Errors
/// ///
/// This function returns an error if `$PATH` is not set. /// This function returns an error if `$PATH` is not set.
pub(crate) fn binary_in_path(name: &str) -> Result<bool> { pub fn binary_in_path(name: &str) -> Result<bool> {
let paths = env::var_os("PATH").context("getting $PATH")?; let paths = env::var_os("PATH").context("getting $PATH")?;
for dir in env::split_paths(&paths) { for dir in env::split_paths(&paths) {
let full_path = dir.join(name); let full_path = dir.join(name);
@@ -123,7 +123,7 @@ pub(crate) fn binary_in_path(name: &str) -> Result<bool> {
/// ///
/// Panics if at least one element in `base_path` does not match the corresponding /// Panics if at least one element in `base_path` does not match the corresponding
/// element in `full_path`. /// element in `full_path`.
pub(crate) fn get_relative_path<P>(full_path: P, base_path: P) -> PathBuf pub fn get_relative_path<P>(full_path: P, base_path: P) -> PathBuf
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
@@ -138,7 +138,11 @@ where
} }
/// For each file argument, return the absolute path to the file. /// For each file argument, return the absolute path to the file.
pub(crate) fn get_absolutized_file_paths(arg_match: &[String]) -> Result<Vec<PathBuf>> { ///
/// # Errors
///
/// Returns an error if any of the files cannot be absolutized.
pub fn get_absolutized_file_paths(arg_match: &[String]) -> Result<Vec<PathBuf>> {
let mut result = vec![]; let mut result = vec![];
for item in arg_match { for item in arg_match {
+5 -6
View File
@@ -1,7 +1,6 @@
use std::rc::Rc; use std::rc::Rc;
use crate::backend::Backend; use crate::{backend::AnyBackend, Group, Package};
use crate::{Group, Package};
use super::strategy::Strategy; use super::strategy::Strategy;
@@ -26,7 +25,7 @@ pub(super) enum ReviewIntention {
#[derive(Debug)] #[derive(Debug)]
pub(super) struct ReviewsPerBackend { pub(super) struct ReviewsPerBackend {
items: Vec<(Box<dyn Backend>, Vec<ReviewAction>)>, items: Vec<(AnyBackend, Vec<ReviewAction>)>,
} }
impl ReviewsPerBackend { impl ReviewsPerBackend {
@@ -38,7 +37,7 @@ impl ReviewsPerBackend {
self.items.iter().all(|(_, vec)| vec.is_empty()) self.items.iter().all(|(_, vec)| vec.is_empty())
} }
pub(super) fn push(&mut self, value: (Box<dyn Backend>, Vec<ReviewAction>)) { pub(super) fn push(&mut self, value: (AnyBackend, Vec<ReviewAction>)) {
self.items.push(value); self.items.push(value);
} }
@@ -77,9 +76,9 @@ impl ReviewsPerBackend {
} }
impl IntoIterator for ReviewsPerBackend { impl IntoIterator for ReviewsPerBackend {
type Item = (Box<dyn Backend>, Vec<ReviewAction>); type Item = (AnyBackend, Vec<ReviewAction>);
type IntoIter = std::vec::IntoIter<(Box<dyn Backend>, Vec<ReviewAction>)>; type IntoIter = std::vec::IntoIter<(AnyBackend, Vec<ReviewAction>)>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
self.items.into_iter() self.items.into_iter()
+5 -5
View File
@@ -6,7 +6,8 @@ use std::rc::Rc;
use anyhow::Result; use anyhow::Result;
use crate::backend::{Backend, ToDoPerBackend}; use crate::backend::backend_trait::Backend;
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, Package}; use crate::{Group, Package};
@@ -27,11 +28,11 @@ pub fn review(
return Ok(()); return Ok(());
} }
'outer: for (backend, packages) in todo_per_backend.into_iter() { '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.get_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(()),
ContinueWithReview::NoAndApply => { ContinueWithReview::NoAndApply => {
@@ -161,9 +162,8 @@ fn print_enumerated_groups(groups: &[Rc<Group>]) {
} }
} }
#[allow(clippy::as_conversions)] // this cannot introduce errors for any reasonably sized numbers.
fn get_amount_of_digits_for_number(number: usize) -> usize { fn get_amount_of_digits_for_number(number: usize) -> usize {
(number as f64).log10().trunc() as usize + 1 number.to_string().len()
} }
fn ask_group(groups: &[Rc<Group>]) -> Result<Option<Rc<Group>>> { fn ask_group(groups: &[Rc<Group>]) -> Result<Option<Rc<Group>>> {
+6 -4
View File
@@ -2,12 +2,14 @@ use std::rc::Rc;
use anyhow::Result; use anyhow::Result;
use crate::backend::Backend; use crate::{
use crate::{Group, Package}; backend::{backend_trait::Backend, AnyBackend},
Group, Package,
};
#[derive(Debug)] #[derive(Debug)]
pub(super) struct Strategy { pub(super) struct Strategy {
backend: Box<dyn Backend>, backend: AnyBackend,
delete: Vec<Package>, delete: Vec<Package>,
as_dependency: Vec<Package>, as_dependency: Vec<Package>,
assign_group: Vec<(Package, Rc<Group>)>, assign_group: Vec<(Package, Rc<Group>)>,
@@ -15,7 +17,7 @@ pub(super) struct Strategy {
impl Strategy { impl Strategy {
pub(super) fn new( pub(super) fn new(
backend: Box<dyn Backend>, backend: AnyBackend,
delete: Vec<Package>, delete: Vec<Package>,
as_dependency: Vec<Package>, as_dependency: Vec<Package>,
assign_group: Vec<(Package, Rc<Group>)>, assign_group: Vec<(Package, Rc<Group>)>,
+2 -2
View File
@@ -63,8 +63,8 @@ fn print_triples(mut vec: Vec<(&Group, &Section, &Package)>) {
} }
fn save_group_and_section_name(g0: &mut String, g: &Group, s0: &mut String, s: &Section) { fn save_group_and_section_name(g0: &mut String, g: &Group, s0: &mut String, s: &Section) {
*g0 = g.name.clone(); g0.clone_from(&g.name);
*s0 = s.name.clone(); s0.clone_from(&s.name);
} }
fn print_separator_unless_exhausted( fn print_separator_unless_exhausted(
-19
View File
@@ -1,19 +0,0 @@
[package]
name = "pacdef_macros"
description = "procedural macros for pacdef"
version = "1.0.1"
edition.workspace = true
license.workspace = true
repository.workspace = true
readme.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = "2.0"
[lib]
proc-macro = true
-34
View File
@@ -1,34 +0,0 @@
/*!
Procedural macros for `pacdef`.
*/
#![warn(
clippy::as_conversions,
clippy::cognitive_complexity,
clippy::explicit_iter_loop,
clippy::explicit_into_iter_loop,
clippy::map_entry,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::option_if_let_else,
clippy::redundant_pub_crate,
clippy::semicolon_if_nothing_returned,
clippy::unnecessary_wraps,
clippy::unused_self,
clippy::unwrap_used,
clippy::use_debug,
clippy::use_self,
clippy::wildcard_dependencies,
missing_docs
)]
mod register;
use proc_macro::TokenStream;
/// Derive (1) an iterator over the variants, (2) imports for the individual backends, and (3)
/// instantiationn code for each backend.
#[proc_macro_derive(Register)]
pub fn register(input: TokenStream) -> TokenStream {
register::register(input)
}
-111
View File
@@ -1,111 +0,0 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{DeriveInput, __private::TokenStream2};
pub fn register(input: TokenStream) -> TokenStream {
let input = syn::parse::<DeriveInput>(input).expect("I don't know when this could fail");
let name = &input.ident;
let syn::Data::Enum(enum_data) = &input.data else {
panic!("`Register` can only be used on enums");
};
let first_variant = &enum_data.variants[0].ident;
let variant_matches_backend = generate_variant_backend(enum_data);
let variant_matches_next = generate_variant_matches_next(enum_data);
let variant_imports = generate_variant_imports(enum_data);
let expanded = compile_output(
name,
first_variant,
variant_matches_backend,
variant_matches_next,
variant_imports,
);
TokenStream::from(expanded)
}
fn compile_output<T, U, V>(
name: &syn::Ident,
first_variant: &syn::Ident,
variant_backend: T,
variant_next: U,
variant_imports: V,
) -> TokenStream2
where
T: Iterator<Item = TokenStream2>,
U: Iterator<Item = TokenStream2>,
V: Iterator<Item = TokenStream2>,
{
let expanded = quote! {
#(#variant_imports)*
impl #name {
pub fn iter() -> BackendIter {
BackendIter {
next: Some(Self::#first_variant),
}
}
fn get_backend(&self) -> Box<dyn Backend> {
match self {
#(#variant_backend)*
}
}
fn next(&self) -> Option<Self> {
match self {
#(#variant_next)*
}
}
}
};
expanded
}
fn generate_variant_imports(enum_data: &syn::DataEnum) -> impl Iterator<Item = TokenStream2> + '_ {
let variant_imports = enum_data.variants.iter().map(|variant| {
let variant_name = &variant.ident;
let variant_module = proc_macro2::Ident::new(
&variant_name.to_string().to_lowercase(),
proc_macro2::Span::call_site(),
);
quote! {
pub use actual::#variant_module::#variant_name;
}
});
variant_imports
}
fn generate_variant_matches_next(
enum_data: &syn::DataEnum,
) -> impl Iterator<Item = TokenStream2> + '_ {
let variant_matches_next = enum_data.variants.iter().enumerate().map(|(i, variant)| {
let variant_name = &variant.ident;
if i == enum_data.variants.len() - 1 {
quote! {
Self::#variant_name => None,
}
} else {
let next_variant = &enum_data.variants[i + 1].ident;
quote! {
Self::#variant_name => Some(Self::#next_variant),
}
}
});
variant_matches_next
}
fn generate_variant_backend(enum_data: &syn::DataEnum) -> impl Iterator<Item = TokenStream2> + '_ {
let variant_matches_backend = enum_data.variants.iter().map(|variant| {
let variant_name = &variant.ident;
quote! {
Self::#variant_name => Box::new(#variant_name::new()),
}
});
variant_matches_backend
}