refact(backend): static dispatch
major overhaul of Backend from dynamic to static dispatch See #73.
This commit is contained in:
+9
-1
@@ -1,9 +1,17 @@
|
||||
# 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.
|
||||
2. Wait for approval.
|
||||
3. Fork the repository and implement your fix / feature.
|
||||
4. Make sure your code generates no warnings, and passes `rustfmt` and `clippy`.
|
||||
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
@@ -215,6 +215,18 @@ dependencies = [
|
||||
"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]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
@@ -395,8 +407,8 @@ dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"const_format",
|
||||
"enum_dispatch",
|
||||
"libc",
|
||||
"pacdef_macros",
|
||||
"path-absolutize",
|
||||
"regex",
|
||||
"rstest",
|
||||
@@ -409,15 +421,6 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacdef_macros"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "path-absolutize"
|
||||
version = "3.1.1"
|
||||
|
||||
+1
-2
@@ -12,8 +12,7 @@ categories = ["command-line-utilities"]
|
||||
rust-version = "1.74"
|
||||
|
||||
[workspace.dependencies]
|
||||
pacdef_macros = { path = "crates/pacdef_macros", version = "1.0" }
|
||||
pacdef = { path = "crates/pacdef", version = "1.6" }
|
||||
pacdef = { path = "crates/pacdef" }
|
||||
|
||||
[profile.release]
|
||||
lto = "off"
|
||||
|
||||
-83
@@ -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"
|
||||
@@ -19,14 +19,13 @@ regex = { version = "1.10", default-features = false, features = ["std"] }
|
||||
termios = "0.3"
|
||||
walkdir = "2.5"
|
||||
libc = "0.2"
|
||||
enum_dispatch = "0.3"
|
||||
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
|
||||
pacdef_macros.workspace = true
|
||||
|
||||
# backends
|
||||
alpm = { version = "3.0", optional = true }
|
||||
rust-apt = { version = "0.7", optional = true }
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use self::datastructure::Arguments;
|
||||
|
||||
mod cli;
|
||||
mod datastructure;
|
||||
pub mod datastructure;
|
||||
mod parsing;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use datastructure::*;
|
||||
|
||||
/// Get and parse the CLI arguments.
|
||||
#[must_use]
|
||||
pub fn get() -> Arguments {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -13,30 +15,28 @@ pub(super) fn parse(args: clap::ArgMatches) -> Arguments {
|
||||
}
|
||||
|
||||
fn parse_group_args(args: &clap::ArgMatches) -> GroupAction {
|
||||
use GroupAction::*;
|
||||
|
||||
match args.subcommand() {
|
||||
Some(("edit", args)) => Edit(get_groups(args)),
|
||||
Some(("export", args)) => Export(get_groups(args), get_output_dir(args), get_force(args)),
|
||||
Some(("import", args)) => Import(get_groups(args)),
|
||||
Some(("list", _)) => List,
|
||||
Some(("new", args)) => New(get_groups(args), get_edit(args)),
|
||||
Some(("remove", args)) => Remove(get_groups(args)),
|
||||
Some(("show", args)) => Show(get_groups(args)),
|
||||
Some(("edit", args)) => GroupAction::Edit(get_groups(args)),
|
||||
Some(("export", args)) => {
|
||||
GroupAction::Export(get_groups(args), get_output_dir(args), get_force(args))
|
||||
}
|
||||
Some(("import", args)) => GroupAction::Import(get_groups(args)),
|
||||
Some(("list", _)) => GroupAction::List,
|
||||
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:?}"),
|
||||
None => unreachable!("prevented by clap"),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_package_args(args: &clap::ArgMatches) -> PackageAction {
|
||||
use PackageAction::*;
|
||||
|
||||
match args.subcommand() {
|
||||
Some(("clean", args)) => Clean(get_noconfirm(args)),
|
||||
Some(("review", _)) => Review,
|
||||
Some(("search", args)) => Search(get_regex(args)),
|
||||
Some(("sync", args)) => Sync(get_noconfirm(args)),
|
||||
Some(("unmanaged", _)) => Unmanaged,
|
||||
Some(("clean", args)) => PackageAction::Clean(get_noconfirm(args)),
|
||||
Some(("review", _)) => PackageAction::Review,
|
||||
Some(("search", args)) => PackageAction::Search(get_regex(args)),
|
||||
Some(("sync", args)) => PackageAction::Sync(get_noconfirm(args)),
|
||||
Some(("unmanaged", _)) => PackageAction::Unmanaged,
|
||||
Some(value) => panic!("package subcommand was not matched: {value:?}"),
|
||||
None => unreachable!("prevented by clap"),
|
||||
}
|
||||
|
||||
@@ -12,9 +12,23 @@ use crate::{Group, Package};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Arch {
|
||||
pub(crate) binary: String,
|
||||
pub(crate) aur_rm_args: Vec<String>,
|
||||
pub(crate) packages: HashSet<Package>,
|
||||
pub binary: String,
|
||||
pub aur_rm_args: Vec<String>,
|
||||
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";
|
||||
@@ -117,13 +131,3 @@ fn convert_to_pacdef_packages(packages: HashSet<String>) -> HashSet<Package> {
|
||||
fn get_db_handle() -> Result<Alpm> {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,19 @@ use crate::{Group, Package};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
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";
|
||||
@@ -94,11 +106,3 @@ impl Backend for Debian {
|
||||
run_external_command(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debian {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
packages: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,19 @@ use crate::{Group, Package};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
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";
|
||||
@@ -123,14 +135,6 @@ impl Backend for Fedora {
|
||||
}
|
||||
}
|
||||
|
||||
impl Fedora {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
packages: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_package(package: &str) -> Package {
|
||||
if DEFAULT_REPOS.iter().any(|repo| package.contains(repo)) && !package.contains("copr") {
|
||||
let package = package.split('/').nth(1).expect("Cannot be empty!");
|
||||
|
||||
@@ -10,8 +10,46 @@ use crate::{Group, Package};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Flatpak {
|
||||
pub(crate) packages: HashSet<Package>,
|
||||
pub(crate) systemwide: bool,
|
||||
pub packages: HashSet<Package>,
|
||||
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";
|
||||
@@ -84,37 +122,3 @@ impl Backend for Flatpak {
|
||||
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>>())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,52 @@ use crate::backend::backend_trait::{Backend, Switches, Text};
|
||||
use crate::backend::macros::impl_backend_constants;
|
||||
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)]
|
||||
pub struct Python {
|
||||
pub(crate) binary: String,
|
||||
pub(crate) packages: HashSet<Package>,
|
||||
pub binary: String,
|
||||
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";
|
||||
@@ -26,12 +68,6 @@ const SWITCHES_REMOVE: Switches = &["uninstall"];
|
||||
|
||||
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_constants!();
|
||||
|
||||
@@ -64,38 +100,6 @@ fn run_pip_command(cmd: &mut Command, args: &[&str]) -> Result<Value> {
|
||||
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>> {
|
||||
let result = value
|
||||
.as_array()
|
||||
|
||||
@@ -12,7 +12,19 @@ use crate::{Group, Package};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
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";
|
||||
@@ -76,14 +88,6 @@ fn extract_packages(json: &Value) -> Result<HashSet<Package>> {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
impl Rust {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
packages: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_crates_file() -> Result<PathBuf> {
|
||||
let mut result = crate::path::get_cargo_home().context("getting cargo home dir")?;
|
||||
result.push(".crates2.json");
|
||||
|
||||
@@ -12,9 +12,25 @@ use std::process::Command;
|
||||
use self::helpers::{
|
||||
group_components_by_toolchains, install_components, toolchain_of_component_was_already_removed,
|
||||
};
|
||||
pub use self::types::Rustup;
|
||||
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 SECTION: Text = "rustup";
|
||||
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{backend::backend_trait::Switches, Package};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Rustup {
|
||||
pub(crate) packages: HashSet<Package>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Repotype {
|
||||
Toolchain,
|
||||
@@ -28,14 +22,6 @@ pub struct RustupPackage {
|
||||
pub component: Option<String>,
|
||||
}
|
||||
|
||||
impl Rustup {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
packages: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Repotype {
|
||||
fn try_from<T>(value: T) -> Result<Self>
|
||||
where
|
||||
|
||||
@@ -12,7 +12,19 @@ use crate::{Group, Package};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
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";
|
||||
@@ -124,11 +136,3 @@ impl Backend for Void {
|
||||
run_external_command(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
impl Void {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
packages: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use std::any::Any;
|
||||
use std::cmp::{Eq, Ord};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::process::Command;
|
||||
use std::rc::Rc;
|
||||
@@ -11,11 +9,12 @@ use anyhow::{Context, Result};
|
||||
use crate::cmd::run_external_command;
|
||||
use crate::{Group, Package};
|
||||
|
||||
pub(in crate::backend) type Switches = &'static [&'static str];
|
||||
pub(in crate::backend) type Text = &'static str;
|
||||
pub type Switches = &'static [&'static str];
|
||||
pub type Text = &'static str;
|
||||
|
||||
/// 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
|
||||
/// binaries, you will need to overwrite this implementation to return
|
||||
/// 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
|
||||
/// 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;
|
||||
|
||||
/// Load all packages from a set of groups. The backend will visit all groups,
|
||||
@@ -174,9 +173,6 @@ pub trait Backend: Debug {
|
||||
Ok(diff)
|
||||
}
|
||||
|
||||
/// Return a mutable reference to self as `Any`. Required for downcasting.
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
|
||||
/// Whether the underlying package manager supports dependency packages.
|
||||
fn supports_as_dependency(&self) -> bool;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
SUPPORTS_AS_DEPENDENCY
|
||||
}
|
||||
|
||||
@@ -1,26 +1,52 @@
|
||||
mod actual;
|
||||
mod backend_trait;
|
||||
mod iter;
|
||||
mod macros;
|
||||
pub mod actual;
|
||||
pub mod backend_trait;
|
||||
pub mod macros;
|
||||
mod root;
|
||||
mod todo_per_backend;
|
||||
pub mod todo_per_backend;
|
||||
|
||||
pub use backend_trait::Backend;
|
||||
pub use iter::BackendIter;
|
||||
pub use todo_per_backend::ToDoPerBackend;
|
||||
use crate::backend::backend_trait::Switches;
|
||||
use crate::backend::backend_trait::Text;
|
||||
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)]
|
||||
pub enum Backends {
|
||||
#[derive(Debug)]
|
||||
#[enum_dispatch::enum_dispatch(Backend)]
|
||||
pub enum AnyBackend {
|
||||
#[cfg(feature = "arch")]
|
||||
Arch,
|
||||
Arch(actual::arch::Arch),
|
||||
#[cfg(feature = "debian")]
|
||||
Debian,
|
||||
Flatpak,
|
||||
Fedora,
|
||||
Python,
|
||||
Rust,
|
||||
Rustup,
|
||||
Void,
|
||||
Debian(actual::debian::Debian),
|
||||
Flatpak(Flatpak),
|
||||
Fedora(Fedora),
|
||||
Python(Python),
|
||||
Rust(Rust),
|
||||
Rustup(Rustup),
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,63 +2,60 @@ use std::fmt::Write;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use super::Backend;
|
||||
use super::{AnyBackend, Backend};
|
||||
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.
|
||||
///
|
||||
/// This struct is used to store a list of unmanaged packages or missing packages
|
||||
/// for all backends.
|
||||
#[derive(Debug)]
|
||||
pub struct ToDoPerBackend(Vec<(Box<dyn Backend>, Vec<Package>)>);
|
||||
|
||||
pub struct ToDoPerBackend(Vec<(AnyBackend, Vec<Package>)>);
|
||||
impl ToDoPerBackend {
|
||||
pub(crate) fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
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);
|
||||
}
|
||||
|
||||
pub(crate) fn into_iter(self) -> impl Iterator<Item = (Box<dyn Backend>, Vec<Package>)> {
|
||||
self.0.into_iter()
|
||||
}
|
||||
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = &(Box<dyn Backend>, Vec<Package>)> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = &(AnyBackend, Vec<Package>)> {
|
||||
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())
|
||||
}
|
||||
|
||||
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 {
|
||||
if packages.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
Backend::install_packages(&**backend, packages, noconfirm)
|
||||
backend
|
||||
.install_packages(packages, noconfirm)
|
||||
.with_context(|| format!("installing packages for {}", backend.get_section()))?;
|
||||
}
|
||||
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 {
|
||||
if packages.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
Backend::remove_packages(&**backend, packages, noconfirm)
|
||||
backend
|
||||
.remove_packages(packages, noconfirm)
|
||||
.with_context(|| format!("removing packages for {}", backend.get_section()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn show(&self) -> Result<()> {
|
||||
pub fn show(&self) -> Result<()> {
|
||||
let mut parts = vec![];
|
||||
|
||||
for (backend, packages) in self.iter() {
|
||||
@@ -91,3 +88,18 @@ impl ToDoPerBackend {
|
||||
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
@@ -7,8 +7,12 @@ use std::path::{Path, PathBuf};
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
use const_format::formatcp;
|
||||
|
||||
use crate::args::{self, PackageAction};
|
||||
use crate::backend::{Backend, Backends, ToDoPerBackend};
|
||||
use crate::args::datastructure::{
|
||||
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::env::should_print_debug_info;
|
||||
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.
|
||||
pub struct Pacdef {
|
||||
/// 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.
|
||||
config: Config,
|
||||
/// 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
|
||||
/// [`args::get`].
|
||||
#[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 {
|
||||
args: Some(args),
|
||||
config,
|
||||
@@ -52,47 +56,46 @@ impl Pacdef {
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if the `args` field is `None`.
|
||||
#[allow(clippy::unit_arg)]
|
||||
pub fn run_action_from_arg(mut self) -> Result<()> {
|
||||
match self
|
||||
.args
|
||||
.take()
|
||||
.expect("if there were no args we would not get to here")
|
||||
{
|
||||
args::Arguments::Group(group_args) => self.run_group_subcommand(&group_args),
|
||||
args::Arguments::Package(package_args) => self.run_package_subcommand(&package_args),
|
||||
args::Arguments::Version => Ok(self.show_version()),
|
||||
Arguments::Group(group_args) => self.run_group_subcommand(&group_args),
|
||||
Arguments::Package(package_args) => self.run_package_subcommand(&package_args),
|
||||
Arguments::Version => {
|
||||
self.show_version();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_group_subcommand(self, args: &args::GroupAction) -> Result<()> {
|
||||
use args::GroupAction::*;
|
||||
|
||||
fn run_group_subcommand(self, args: &GroupAction) -> Result<()> {
|
||||
match args {
|
||||
Edit(args::Groups(groups)) => self.edit_groups(groups),
|
||||
Export(args::Groups(groups), args::OutputDir(dir), args::Force(force)) => {
|
||||
GroupAction::Edit(Groups(groups)) => self.edit_groups(groups),
|
||||
GroupAction::Export(Groups(groups), OutputDir(dir), Force(force)) => {
|
||||
self.export_groups(groups, dir.as_ref(), *force)
|
||||
}
|
||||
Import(args::Groups(groups)) => self.import_groups(groups),
|
||||
List => self.show_groups(),
|
||||
New(args::Groups(groups), args::Edit(edit)) => self.new_groups(groups, *edit),
|
||||
Remove(args::Groups(groups)) => self.remove_groups(groups),
|
||||
Show(args::Groups(groups)) => self.show_group_content(groups),
|
||||
GroupAction::Import(Groups(groups)) => self.import_groups(groups),
|
||||
GroupAction::List => self.show_groups(),
|
||||
GroupAction::New(Groups(groups), Edit(edit)) => self.new_groups(groups, *edit),
|
||||
GroupAction::Remove(Groups(groups)) => self.remove_groups(groups),
|
||||
GroupAction::Show(Groups(groups)) => self.show_group_content(groups),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_package_subcommand(mut self, args: &PackageAction) -> Result<()> {
|
||||
use args::PackageAction::*;
|
||||
|
||||
match args {
|
||||
Clean(args::Noconfirm(noconfirm)) => self.clean_packages(*noconfirm),
|
||||
Review => review::review(self.get_unmanaged_packages()?, self.groups),
|
||||
Search(args::Regex(regex)) => {
|
||||
PackageAction::Clean(Noconfirm(noconfirm)) => self.clean_packages(*noconfirm),
|
||||
PackageAction::Review => review::review(self.get_unmanaged_packages()?, self.groups),
|
||||
PackageAction::Search(Regex(regex)) => {
|
||||
self.warn_about_groups_that_arent_symlinks();
|
||||
search::search_packages(regex, &self.groups)
|
||||
}
|
||||
Sync(args::Noconfirm(noconfirm)) => self.install_packages(*noconfirm),
|
||||
Unmanaged => self.show_unmanaged_packages(),
|
||||
PackageAction::Sync(Noconfirm(noconfirm)) => self.install_packages(*noconfirm),
|
||||
PackageAction::Unmanaged => self.show_unmanaged_packages(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +106,7 @@ impl Pacdef {
|
||||
eprintln!("WARNING: no group files found");
|
||||
}
|
||||
|
||||
for mut backend in Backends::iter() {
|
||||
for mut backend in AnyBackend::iter() {
|
||||
if self
|
||||
.config
|
||||
.disabled_backends
|
||||
@@ -116,39 +119,33 @@ impl Pacdef {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.overwrite_values_from_config(&mut *backend);
|
||||
self.overwrite_values_from_config(&mut backend);
|
||||
backend.load(&self.groups);
|
||||
|
||||
match backend.get_missing_packages_sorted() {
|
||||
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)
|
||||
}
|
||||
|
||||
fn overwrite_values_from_config(&mut self, backend: &mut dyn Backend) {
|
||||
fn overwrite_values_from_config(&mut self, backend: &mut AnyBackend) {
|
||||
#[cfg(feature = "arch")]
|
||||
{
|
||||
if let Some(arch) = backend.as_any_mut().downcast_mut::<crate::backend::Arch>() {
|
||||
arch.binary = self.config.aur_helper.clone();
|
||||
arch.aur_rm_args = self.config.aur_rm_args.clone();
|
||||
if let AnyBackend::Arch(arch) = backend {
|
||||
arch.binary.clone_from(&self.config.aur_helper);
|
||||
arch.aur_rm_args.clone_from(&self.config.aur_rm_args);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(flatpak) = backend
|
||||
.as_any_mut()
|
||||
.downcast_mut::<crate::backend::Flatpak>()
|
||||
{
|
||||
if let AnyBackend::Flatpak(flatpak) = backend {
|
||||
flatpak.systemwide = self.config.flatpak_systemwide;
|
||||
}
|
||||
|
||||
if let Some(python) = backend
|
||||
.as_any_mut()
|
||||
.downcast_mut::<crate::backend::Python>()
|
||||
{
|
||||
python.binary = self.config.pip_binary.clone();
|
||||
if let AnyBackend::Python(python) = backend {
|
||||
python.binary.clone_from(&self.config.pip_binary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +201,6 @@ impl Pacdef {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_self)]
|
||||
fn show_version(self) {
|
||||
println!("{}", get_name_and_version());
|
||||
}
|
||||
@@ -237,7 +233,7 @@ impl Pacdef {
|
||||
|
||||
let mut result = ToDoPerBackend::new();
|
||||
|
||||
for mut backend in Backends::iter() {
|
||||
for mut backend in AnyBackend::iter() {
|
||||
if self
|
||||
.config
|
||||
.disabled_backends
|
||||
@@ -250,12 +246,12 @@ impl Pacdef {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.overwrite_values_from_config(&mut *backend);
|
||||
self.overwrite_values_from_config(&mut backend);
|
||||
backend.load(&self.groups);
|
||||
|
||||
match backend.get_unmanaged_packages_sorted() {
|
||||
Ok(unmanaged) => result.push((backend, unmanaged)),
|
||||
Err(error) => show_backend_query_error(&error, &*backend),
|
||||
Err(error) => show_backend_query_error(&error, &backend),
|
||||
};
|
||||
}
|
||||
Ok(result)
|
||||
@@ -265,7 +261,6 @@ impl Pacdef {
|
||||
///
|
||||
/// This methods cannot return an error. It returns a `Result` to be consistent
|
||||
/// with other methods.
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn show_groups(self) -> Result<()> {
|
||||
self.warn_about_groups_that_arent_symlinks();
|
||||
|
||||
@@ -354,7 +349,6 @@ impl Pacdef {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_self)]
|
||||
fn import_groups(&self, file_names: &[String]) -> Result<()> {
|
||||
let files = get_absolutized_file_paths(file_names)?;
|
||||
let groups_dir = get_group_dir()?;
|
||||
@@ -410,7 +404,6 @@ impl Pacdef {
|
||||
/// - a group with the same name already exists,
|
||||
/// - the editor cannot be run, or
|
||||
/// - 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<()> {
|
||||
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
|
||||
/// 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: &dyn Backend) {
|
||||
fn show_backend_query_error(error: &anyhow::Error, backend: &AnyBackend) {
|
||||
let section = backend.get_section();
|
||||
if should_print_debug_info() {
|
||||
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.
|
||||
fn get_included_backends() -> Vec<&'static str> {
|
||||
let mut result = vec![];
|
||||
for backend in Backends::iter() {
|
||||
for backend in AnyBackend::iter() {
|
||||
result.push(backend.get_section());
|
||||
}
|
||||
result.sort_unstable();
|
||||
|
||||
@@ -18,13 +18,13 @@ use super::{Package, Section};
|
||||
pub struct Group {
|
||||
/// Name of the group (file name from which it was read, relative to the group
|
||||
/// base dir).
|
||||
pub(crate) name: String,
|
||||
pub name: String,
|
||||
/// 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.
|
||||
pub(crate) path: PathBuf,
|
||||
pub path: PathBuf,
|
||||
/// Whether the main program should warn this group being loaded from a symlink.
|
||||
pub(crate) warn_symlink: bool,
|
||||
pub warn_symlink: bool,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
@@ -177,7 +177,7 @@ impl Group {
|
||||
///
|
||||
/// This function returns an error if the group file cannot be read, or if the
|
||||
/// 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)
|
||||
.with_context(|| format!("reading existing file contents from {:?}", &self.path))?;
|
||||
|
||||
|
||||
@@ -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
|
||||
sections.
|
||||
*/
|
||||
|
||||
mod group;
|
||||
mod package;
|
||||
mod section;
|
||||
|
||||
@@ -5,8 +5,10 @@ use std::hash::Hash;
|
||||
/// optionally a `repo`.
|
||||
#[derive(Debug, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct Package {
|
||||
pub(crate) name: String,
|
||||
pub(crate) repo: Option<String>,
|
||||
/// The name of the package
|
||||
pub name: String,
|
||||
/// Optionally, which repository the package belongs to
|
||||
pub repo: Option<String>,
|
||||
}
|
||||
|
||||
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.
|
||||
/// From the string, any possible comment is removed and whitespace is trimmed.
|
||||
/// 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
|
||||
S: AsRef<str>,
|
||||
{
|
||||
@@ -116,11 +118,10 @@ mod tests {
|
||||
assert_eq!(repo, None);
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[test]
|
||||
fn from() {
|
||||
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.repo, Some("myrepo".to_string()));
|
||||
}
|
||||
|
||||
@@ -14,13 +14,11 @@ pub struct 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 }
|
||||
}
|
||||
|
||||
pub(crate) 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 mut packages = HashSet::new();
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/*!
|
||||
This library contains all logic that happens in `pacdef` under the hood.
|
||||
*/
|
||||
//! This library contains all logic that happens in `pacdef`.
|
||||
|
||||
#![warn(
|
||||
clippy::as_conversions,
|
||||
@@ -23,7 +21,7 @@ This library contains all logic that happens in `pacdef` under the hood.
|
||||
)]
|
||||
|
||||
mod args;
|
||||
mod backend;
|
||||
pub(crate) mod backend;
|
||||
mod cmd;
|
||||
mod config;
|
||||
mod core;
|
||||
@@ -40,6 +38,4 @@ pub use crate::config::Config;
|
||||
pub use crate::core::Pacdef;
|
||||
pub use crate::errors::Error;
|
||||
pub use crate::grouping::Group;
|
||||
pub(crate) use crate::grouping::Package;
|
||||
|
||||
extern crate pacdef_macros;
|
||||
pub use crate::grouping::Package;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/*!
|
||||
Main program for `pacdef`.
|
||||
*/
|
||||
//! Main program for `pacdef`.
|
||||
|
||||
#![warn(
|
||||
clippy::as_conversions,
|
||||
|
||||
@@ -28,7 +28,7 @@ pub fn get_group_dir() -> Result<PathBuf> {
|
||||
/// # Errors
|
||||
///
|
||||
/// 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")?;
|
||||
dir.push("pacdef");
|
||||
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
|
||||
/// 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") {
|
||||
Ok(config.into())
|
||||
} else {
|
||||
@@ -72,7 +72,7 @@ fn get_xdg_config_home() -> Result<PathBuf> {
|
||||
/// # Errors
|
||||
///
|
||||
/// 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())
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ pub fn get_config_path_old_version() -> Result<PathBuf> {
|
||||
/// # Errors
|
||||
///
|
||||
/// 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")?;
|
||||
for dir in env::split_paths(&paths) {
|
||||
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
|
||||
/// 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
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
@@ -138,7 +138,11 @@ where
|
||||
}
|
||||
|
||||
/// 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![];
|
||||
|
||||
for item in arg_match {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::backend::Backend;
|
||||
use crate::{Group, Package};
|
||||
use crate::{backend::AnyBackend, Group, Package};
|
||||
|
||||
use super::strategy::Strategy;
|
||||
|
||||
@@ -26,7 +25,7 @@ pub(super) enum ReviewIntention {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ReviewsPerBackend {
|
||||
items: Vec<(Box<dyn Backend>, Vec<ReviewAction>)>,
|
||||
items: Vec<(AnyBackend, Vec<ReviewAction>)>,
|
||||
}
|
||||
|
||||
impl ReviewsPerBackend {
|
||||
@@ -38,7 +37,7 @@ impl ReviewsPerBackend {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -77,9 +76,9 @@ impl 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 {
|
||||
self.items.into_iter()
|
||||
|
||||
@@ -6,7 +6,8 @@ use std::rc::Rc;
|
||||
|
||||
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::{Group, Package};
|
||||
|
||||
@@ -27,11 +28,11 @@ pub fn review(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
'outer: for (backend, packages) in todo_per_backend.into_iter() {
|
||||
'outer: for (backend, packages) in todo_per_backend {
|
||||
let mut actions = vec![];
|
||||
for package in packages {
|
||||
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::No => return Ok(()),
|
||||
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 {
|
||||
(number as f64).log10().trunc() as usize + 1
|
||||
number.to_string().len()
|
||||
}
|
||||
|
||||
fn ask_group(groups: &[Rc<Group>]) -> Result<Option<Rc<Group>>> {
|
||||
|
||||
@@ -2,12 +2,14 @@ use std::rc::Rc;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::backend::Backend;
|
||||
use crate::{Group, Package};
|
||||
use crate::{
|
||||
backend::{backend_trait::Backend, AnyBackend},
|
||||
Group, Package,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct Strategy {
|
||||
backend: Box<dyn Backend>,
|
||||
backend: AnyBackend,
|
||||
delete: Vec<Package>,
|
||||
as_dependency: Vec<Package>,
|
||||
assign_group: Vec<(Package, Rc<Group>)>,
|
||||
@@ -15,7 +17,7 @@ pub(super) struct Strategy {
|
||||
|
||||
impl Strategy {
|
||||
pub(super) fn new(
|
||||
backend: Box<dyn Backend>,
|
||||
backend: AnyBackend,
|
||||
delete: Vec<Package>,
|
||||
as_dependency: Vec<Package>,
|
||||
assign_group: Vec<(Package, Rc<Group>)>,
|
||||
|
||||
@@ -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) {
|
||||
*g0 = g.name.clone();
|
||||
*s0 = s.name.clone();
|
||||
g0.clone_from(&g.name);
|
||||
s0.clone_from(&s.name);
|
||||
}
|
||||
|
||||
fn print_separator_unless_exhausted(
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user