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
|
# 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
@@ -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
@@ -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
@@ -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"
|
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 }
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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!");
|
||||||
|
|||||||
@@ -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>>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
fn supports_as_dependency(&self) -> bool {
|
||||||
SUPPORTS_AS_DEPENDENCY
|
SUPPORTS_AS_DEPENDENCY
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
|
|||||||
@@ -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))?;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,6 +1,4 @@
|
|||||||
/*!
|
//! Main program for `pacdef`.
|
||||||
Main program for `pacdef`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#![warn(
|
#![warn(
|
||||||
clippy::as_conversions,
|
clippy::as_conversions,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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>>> {
|
||||||
|
|||||||
@@ -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>)>,
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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