declarative cli and core: refactor
This commit is contained in:
Generated
+19
-186
@@ -84,12 +84,6 @@ version = "1.0.82"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
|
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "autocfg"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -102,12 +96,6 @@ version = "1.0.92"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41"
|
checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.4"
|
version = "4.5.4"
|
||||||
@@ -115,6 +103,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -129,6 +118,18 @@ dependencies = [
|
|||||||
"strsim",
|
"strsim",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -243,113 +244,18 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures"
|
|
||||||
version = "0.3.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
|
|
||||||
dependencies = [
|
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"futures-executor",
|
|
||||||
"futures-io",
|
|
||||||
"futures-sink",
|
|
||||||
"futures-task",
|
|
||||||
"futures-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-channel"
|
|
||||||
version = "0.3.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-core"
|
|
||||||
version = "0.3.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-executor"
|
|
||||||
version = "0.3.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"futures-task",
|
|
||||||
"futures-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-io"
|
|
||||||
version = "0.3.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-macro"
|
|
||||||
version = "0.3.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-sink"
|
|
||||||
version = "0.3.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-task"
|
|
||||||
version = "0.3.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-timer"
|
|
||||||
version = "3.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-util"
|
|
||||||
version = "0.3.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
|
||||||
dependencies = [
|
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"futures-io",
|
|
||||||
"futures-macro",
|
|
||||||
"futures-sink",
|
|
||||||
"futures-task",
|
|
||||||
"memchr",
|
|
||||||
"pin-project-lite",
|
|
||||||
"pin-utils",
|
|
||||||
"slab",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "glob"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.3"
|
version = "0.14.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.2.6"
|
version = "2.2.6"
|
||||||
@@ -411,10 +317,8 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"path-absolutize",
|
"path-absolutize",
|
||||||
"regex",
|
"regex",
|
||||||
"rstest",
|
|
||||||
"rust-apt",
|
"rust-apt",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"termios",
|
"termios",
|
||||||
@@ -439,18 +343,6 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-project-lite"
|
|
||||||
version = "0.2.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-utils"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
@@ -504,41 +396,6 @@ version = "0.8.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "relative-path"
|
|
||||||
version = "1.9.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rstest"
|
|
||||||
version = "0.19.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9d5316d2a1479eeef1ea21e7f9ddc67c191d497abc8fc3ba2467857abbb68330"
|
|
||||||
dependencies = [
|
|
||||||
"futures",
|
|
||||||
"futures-timer",
|
|
||||||
"rstest_macros",
|
|
||||||
"rustc_version",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rstest_macros"
|
|
||||||
version = "0.19.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "04a9df72cc1f67020b0d63ad9bfe4a323e459ea7eb68e03bd9824db49f9a4c25"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"glob",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"regex",
|
|
||||||
"relative-path",
|
|
||||||
"rustc_version",
|
|
||||||
"syn",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-apt"
|
name = "rust-apt"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -550,15 +407,6 @@ dependencies = [
|
|||||||
"terminal_size",
|
"terminal_size",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc_version"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
|
||||||
dependencies = [
|
|
||||||
"semver",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.32"
|
version = "0.38.32"
|
||||||
@@ -593,12 +441,6 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152"
|
checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "semver"
|
|
||||||
version = "1.0.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.197"
|
version = "1.0.197"
|
||||||
@@ -643,15 +485,6 @@ dependencies = [
|
|||||||
"unsafe-libyaml",
|
"unsafe-libyaml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "slab"
|
|
||||||
version = "0.4.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ categories.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
clap = "4.5"
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
const_format = { version = "0.2", default-features = false }
|
const_format = { version = "0.2", default-features = false }
|
||||||
path-absolutize = "3.1"
|
path-absolutize = "3.1"
|
||||||
regex = { version = "1.10", default-features = false, features = ["std"] }
|
regex = { version = "1.10", default-features = false, features = ["std"] }
|
||||||
@@ -21,8 +21,7 @@ walkdir = "2.5"
|
|||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
enum_dispatch = "0.3"
|
enum_dispatch = "0.3"
|
||||||
|
|
||||||
serde = "1.0"
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_derive = "1.0"
|
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
|
|
||||||
@@ -30,9 +29,6 @@ serde_yaml = "0.9"
|
|||||||
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 }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rstest = "0.19"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
arch = ["dep:alpm"]
|
arch = ["dep:alpm"]
|
||||||
|
|||||||
@@ -1,159 +0,0 @@
|
|||||||
use crate::core::get_version_string;
|
|
||||||
use clap::{Arg, ArgAction, Command};
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub(super) fn build_cli() -> Command {
|
|
||||||
let package_cmd = get_package_cmd();
|
|
||||||
let group_cmd = get_group_cmd();
|
|
||||||
let version_cmd = Command::new("version").about("show version info");
|
|
||||||
|
|
||||||
Command::new("pacdef")
|
|
||||||
.about("multi-backend declarative package manager for Linux")
|
|
||||||
.version(get_version_string())
|
|
||||||
.arg_required_else_help(true)
|
|
||||||
.subcommand_required(true)
|
|
||||||
.subcommands([group_cmd, package_cmd, version_cmd])
|
|
||||||
.subcommand_value_name("subcommand")
|
|
||||||
.disable_help_subcommand(true)
|
|
||||||
.disable_version_flag(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build the `pacdef group` subcommand.
|
|
||||||
fn get_group_cmd() -> Command {
|
|
||||||
let edit = Command::new("edit")
|
|
||||||
.about("edit one or more existing group")
|
|
||||||
.arg_required_else_help(true)
|
|
||||||
.arg(
|
|
||||||
Arg::new("groups")
|
|
||||||
.num_args(1..)
|
|
||||||
.required(true)
|
|
||||||
.help("a previously imported group"),
|
|
||||||
)
|
|
||||||
.visible_alias("ed");
|
|
||||||
|
|
||||||
let export = Command::new("export")
|
|
||||||
.about("export one or more group files")
|
|
||||||
.arg_required_else_help(true)
|
|
||||||
.arg(
|
|
||||||
Arg::new("force")
|
|
||||||
.short('f')
|
|
||||||
.long("force")
|
|
||||||
.action(clap::ArgAction::SetTrue)
|
|
||||||
.help("overwrite output files if they exist"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("output_dir")
|
|
||||||
.short('o')
|
|
||||||
.long("output")
|
|
||||||
.help("(optional) the directory under which to save the group"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("groups")
|
|
||||||
.num_args(1..)
|
|
||||||
.required(true)
|
|
||||||
.help("the file to export as group"),
|
|
||||||
)
|
|
||||||
.visible_alias("ex");
|
|
||||||
|
|
||||||
let import = Command::new("import")
|
|
||||||
.about("import one or more group files")
|
|
||||||
.arg_required_else_help(true)
|
|
||||||
.arg(
|
|
||||||
Arg::new("groups")
|
|
||||||
.num_args(1..)
|
|
||||||
.required(true)
|
|
||||||
.help("the file to import as group"),
|
|
||||||
)
|
|
||||||
.visible_alias("i");
|
|
||||||
|
|
||||||
let list = Command::new("list")
|
|
||||||
.about("list names of imported groups")
|
|
||||||
.visible_alias("l");
|
|
||||||
|
|
||||||
let new = Command::new("new")
|
|
||||||
.about("create new group files")
|
|
||||||
.arg_required_else_help(true)
|
|
||||||
.arg(
|
|
||||||
Arg::new("edit")
|
|
||||||
.short('e')
|
|
||||||
.long("edit")
|
|
||||||
.help("edit the new group files after creation")
|
|
||||||
.action(clap::ArgAction::SetTrue),
|
|
||||||
)
|
|
||||||
.arg(Arg::new("groups").num_args(1..).required(true))
|
|
||||||
.visible_alias("n");
|
|
||||||
|
|
||||||
let remove = Command::new("remove")
|
|
||||||
.about("remove one or more previously imported groups")
|
|
||||||
.arg_required_else_help(true)
|
|
||||||
.arg(
|
|
||||||
Arg::new("groups")
|
|
||||||
.num_args(1..)
|
|
||||||
.required(true)
|
|
||||||
.help("a previously imported group that will be removed"),
|
|
||||||
)
|
|
||||||
.visible_alias("r");
|
|
||||||
|
|
||||||
let show = Command::new("show")
|
|
||||||
.about("show packages under an imported group")
|
|
||||||
.arg_required_else_help(true)
|
|
||||||
.arg(
|
|
||||||
Arg::new("groups")
|
|
||||||
.num_args(1..)
|
|
||||||
.required(true)
|
|
||||||
.help("group file(s) to show"),
|
|
||||||
)
|
|
||||||
.visible_alias("s");
|
|
||||||
|
|
||||||
Command::new("group")
|
|
||||||
.arg_required_else_help(true)
|
|
||||||
.about("manage groups")
|
|
||||||
.visible_alias("g")
|
|
||||||
.subcommand_required(true)
|
|
||||||
.subcommands([edit, export, import, list, new, remove, show])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build the `pacdef package` subcommand.
|
|
||||||
fn get_package_cmd() -> Command {
|
|
||||||
let sync = Command::new("sync")
|
|
||||||
.about("install packages from all imported groups")
|
|
||||||
.visible_alias("sy")
|
|
||||||
.arg(build_noconfirm_arg());
|
|
||||||
|
|
||||||
let clean = Command::new("clean")
|
|
||||||
.about("remove unmanaged packages")
|
|
||||||
.visible_alias("c")
|
|
||||||
.arg(build_noconfirm_arg());
|
|
||||||
|
|
||||||
let unmanaged = Command::new("unmanaged")
|
|
||||||
.about("show explicitly installed packages not managed by pacdef")
|
|
||||||
.visible_alias("u");
|
|
||||||
|
|
||||||
let review = Command::new("review")
|
|
||||||
.about("review unmanaged packages")
|
|
||||||
.visible_alias("r");
|
|
||||||
|
|
||||||
let search = Command::new("search")
|
|
||||||
.visible_alias("se")
|
|
||||||
.about("search for packages which match a provided regex")
|
|
||||||
.arg_required_else_help(true)
|
|
||||||
.arg(
|
|
||||||
Arg::new("regex")
|
|
||||||
.required(true)
|
|
||||||
.help("the regular expression the package must match"),
|
|
||||||
);
|
|
||||||
|
|
||||||
Command::new("package")
|
|
||||||
.arg_required_else_help(true)
|
|
||||||
.about("manage packages")
|
|
||||||
.visible_alias("p")
|
|
||||||
.subcommand_required(true)
|
|
||||||
.subcommands([clean, review, search, sync, unmanaged])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_noconfirm_arg() -> Arg {
|
|
||||||
Arg::new("noconfirm")
|
|
||||||
.long("noconfirm")
|
|
||||||
.help("do not ask for any confirmation")
|
|
||||||
.action(ArgAction::SetTrue)
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Arguments {
|
|
||||||
Group(GroupAction),
|
|
||||||
Package(PackageAction),
|
|
||||||
Version,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum GroupAction {
|
|
||||||
Edit(Groups),
|
|
||||||
Export(Groups, OutputDir, Force),
|
|
||||||
Import(Groups),
|
|
||||||
List,
|
|
||||||
New(Groups, Edit),
|
|
||||||
Remove(Groups),
|
|
||||||
Show(Groups),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct Files(pub Vec<String>);
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct Groups(pub Vec<String>);
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum PackageAction {
|
|
||||||
Clean(Noconfirm),
|
|
||||||
Review,
|
|
||||||
Search(Regex),
|
|
||||||
Sync(Noconfirm),
|
|
||||||
Unmanaged,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct Regex(pub String);
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct Edit(pub bool);
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct Noconfirm(pub bool);
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct Force(pub bool);
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct OutputDir(pub Option<String>);
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
use self::datastructure::Arguments;
|
|
||||||
|
|
||||||
mod cli;
|
|
||||||
pub mod datastructure;
|
|
||||||
mod parsing;
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
/// Get and parse the CLI arguments.
|
|
||||||
#[must_use]
|
|
||||||
pub fn get() -> Arguments {
|
|
||||||
let args = cli::build_cli().get_matches();
|
|
||||||
parsing::parse(args)
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
use super::datastructure::{
|
|
||||||
Arguments, Edit, Force, GroupAction, Groups, Noconfirm, OutputDir, PackageAction, Regex,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ARGS_CONSISTENT: &str = "argument declaration and parsing must be consistent";
|
|
||||||
|
|
||||||
pub(super) fn parse(args: clap::ArgMatches) -> Arguments {
|
|
||||||
match args.subcommand() {
|
|
||||||
Some(("group", args)) => Arguments::Group(parse_group_args(args)),
|
|
||||||
Some(("package", args)) => Arguments::Package(parse_package_args(args)),
|
|
||||||
Some(("version", _)) => Arguments::Version,
|
|
||||||
Some(value) => panic!("main subcommand was not matched: {value:?}"),
|
|
||||||
None => unreachable!("prevented by clap"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_group_args(args: &clap::ArgMatches) -> GroupAction {
|
|
||||||
match args.subcommand() {
|
|
||||||
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 {
|
|
||||||
match args.subcommand() {
|
|
||||||
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"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_one_arg<T>(args: &clap::ArgMatches, id: &str) -> T
|
|
||||||
where
|
|
||||||
T: std::any::Any + Clone + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
args.get_one::<T>(id).expect(ARGS_CONSISTENT).to_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_regex(args: &clap::ArgMatches) -> Regex {
|
|
||||||
Regex(get_one_arg(args, "regex"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_noconfirm(args: &clap::ArgMatches) -> Noconfirm {
|
|
||||||
Noconfirm(get_one_arg(args, "noconfirm"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_edit(args: &clap::ArgMatches) -> Edit {
|
|
||||||
Edit(get_one_arg(args, "edit"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_groups(args: &clap::ArgMatches) -> Groups {
|
|
||||||
Groups(
|
|
||||||
args.get_many::<String>("groups")
|
|
||||||
.expect(ARGS_CONSISTENT)
|
|
||||||
.cloned()
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_output_dir(args: &clap::ArgMatches) -> OutputDir {
|
|
||||||
OutputDir(args.get_one::<String>("output_dir").cloned())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_force(args: &clap::ArgMatches) -> Force {
|
|
||||||
Force(get_one_arg(args, "force"))
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
use super::cli::build_cli;
|
|
||||||
use super::datastructure::*;
|
|
||||||
use super::parsing::parse;
|
|
||||||
|
|
||||||
use rstest::rstest;
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case(vec!["pacdef", "group", "list"],
|
|
||||||
Arguments::Group(GroupAction::List))]
|
|
||||||
#[case(vec!["pacdef", "g", "l"],
|
|
||||||
Arguments::Group(GroupAction::List))]
|
|
||||||
#[case(vec!["pacdef", "version"],
|
|
||||||
Arguments::Version)]
|
|
||||||
#[case(vec!["pacdef", "group", "export", "-o", "/tmp", "--force", "base"],
|
|
||||||
Arguments::Group(GroupAction::Export(Groups(vec!["base".to_string()]), OutputDir(Some("/tmp".to_string())), Force(true))))]
|
|
||||||
#[case(vec!["pacdef", "package", "sync"],
|
|
||||||
Arguments::Package(PackageAction::Sync(Noconfirm(false))))]
|
|
||||||
#[case(vec!["pacdef", "package", "sync", "--noconfirm"],
|
|
||||||
Arguments::Package(PackageAction::Sync(Noconfirm(true))))]
|
|
||||||
#[case(vec!["pacdef", "p", "se", "myregex"],
|
|
||||||
Arguments::Package(PackageAction::Search(Regex("myregex".to_string()))))]
|
|
||||||
fn arg_parsing(#[case] input: Vec<&str>, #[case] expected: Arguments) {
|
|
||||||
let args = build_cli().get_matches_from(input);
|
|
||||||
let parsed = parse(args);
|
|
||||||
|
|
||||||
assert_eq!(parsed, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[should_panic]
|
|
||||||
#[case(vec!["pacdef", "package", "search"], "regex missing")]
|
|
||||||
#[should_panic]
|
|
||||||
#[case(vec!["pacdef", "p", "s"], "can be either 'sync' or 'search'")]
|
|
||||||
#[should_panic]
|
|
||||||
#[case(vec!["pacdef", "group", "edit"], "group missing")]
|
|
||||||
fn arg_parsing_invalid(#[case] input: Vec<&str>, #[case] err_msg: &str) {
|
|
||||||
build_cli().try_get_matches_from(input).expect(err_msg);
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@ use anyhow::{Context, Result};
|
|||||||
use crate::backend::backend_trait::{Backend, Switches, Text};
|
use crate::backend::backend_trait::{Backend, Switches, Text};
|
||||||
use crate::backend::macros::impl_backend_constants;
|
use crate::backend::macros::impl_backend_constants;
|
||||||
use crate::cmd::run_external_command;
|
use crate::cmd::run_external_command;
|
||||||
use crate::{Group, Package};
|
use crate::Package;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Arch {
|
pub struct Arch {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::backend::backend_trait::{Backend, Switches, Text};
|
|||||||
use crate::backend::macros::impl_backend_constants;
|
use crate::backend::macros::impl_backend_constants;
|
||||||
use crate::backend::root::build_base_command_with_privileges;
|
use crate::backend::root::build_base_command_with_privileges;
|
||||||
use crate::cmd::run_external_command;
|
use crate::cmd::run_external_command;
|
||||||
use crate::{Group, Package};
|
use crate::Package;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Debian {
|
pub struct Debian {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use anyhow::Result;
|
|||||||
use crate::backend::backend_trait::{Backend, Switches, Text};
|
use crate::backend::backend_trait::{Backend, Switches, Text};
|
||||||
use crate::backend::macros::impl_backend_constants;
|
use crate::backend::macros::impl_backend_constants;
|
||||||
use crate::cmd::run_external_command;
|
use crate::cmd::run_external_command;
|
||||||
use crate::{Group, Package};
|
use crate::Package;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Fedora {
|
pub struct Fedora {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use anyhow::Result;
|
|||||||
use crate::backend::backend_trait::{Backend, Switches, Text};
|
use crate::backend::backend_trait::{Backend, Switches, Text};
|
||||||
use crate::backend::macros::impl_backend_constants;
|
use crate::backend::macros::impl_backend_constants;
|
||||||
use crate::cmd::run_external_command;
|
use crate::cmd::run_external_command;
|
||||||
use crate::{Group, Package};
|
use crate::Package;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Flatpak {
|
pub struct Flatpak {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use serde_json::Value;
|
|||||||
|
|
||||||
use crate::backend::backend_trait::{Backend, Switches, Text};
|
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::Package;
|
||||||
|
|
||||||
macro_rules! ERROR{
|
macro_rules! ERROR{
|
||||||
($bin:expr) => {
|
($bin:expr) => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use serde_json::Value;
|
|||||||
|
|
||||||
use crate::backend::backend_trait::{Backend, Switches, Text};
|
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::Package;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Rust {
|
pub struct Rust {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ mod types;
|
|||||||
use crate::backend::backend_trait::{Backend, Switches, Text};
|
use crate::backend::backend_trait::{Backend, Switches, Text};
|
||||||
use crate::backend::macros::impl_backend_constants;
|
use crate::backend::macros::impl_backend_constants;
|
||||||
use crate::cmd::run_external_command;
|
use crate::cmd::run_external_command;
|
||||||
use crate::{Group, Package};
|
use crate::Package;
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::backend::backend_trait::{Backend, Switches, Text};
|
|||||||
use crate::backend::macros::impl_backend_constants;
|
use crate::backend::macros::impl_backend_constants;
|
||||||
use crate::backend::root::build_base_command_with_privileges;
|
use crate::backend::root::build_base_command_with_privileges;
|
||||||
use crate::cmd::run_external_command;
|
use crate::cmd::run_external_command;
|
||||||
use crate::{Group, Package};
|
use crate::Package;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Void {
|
pub struct Void {
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ use std::cmp::{Eq, Ord};
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
use crate::cmd::run_external_command;
|
use crate::cmd::run_external_command;
|
||||||
use crate::{Group, Package};
|
use crate::{Group, Groups, Package};
|
||||||
|
|
||||||
pub type Switches = &'static [&'static str];
|
pub type Switches = &'static [&'static str];
|
||||||
pub type Text = &'static str;
|
pub type Text = &'static str;
|
||||||
@@ -51,7 +50,7 @@ pub trait Backend {
|
|||||||
|
|
||||||
/// 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,
|
||||||
/// find its own section, and clone all packages into its own struct.
|
/// find its own section, and clone all packages into its own struct.
|
||||||
fn load(&mut self, groups: &HashSet<Group>);
|
fn load(&mut self, groups: &Groups);
|
||||||
|
|
||||||
/// Get all managed packages for this backend, i.e. all packages
|
/// Get all managed packages for this backend, i.e. all packages
|
||||||
/// under the corresponding section in all group files.
|
/// under the corresponding section in all group files.
|
||||||
@@ -75,7 +74,7 @@ pub trait Backend {
|
|||||||
|
|
||||||
/// Assign each of the packages to an individual group by editing the
|
/// Assign each of the packages to an individual group by editing the
|
||||||
/// group files.
|
/// group files.
|
||||||
fn assign_group(&self, to_assign: Vec<(Package, Rc<Group>)>) -> Result<()> {
|
fn assign_group(&self, to_assign: Vec<(Package, Group)>) -> Result<()> {
|
||||||
let group_package_map = to_hashmap(to_assign);
|
let group_package_map = to_hashmap(to_assign);
|
||||||
let section_header = format!("[{}]", self.get_section());
|
let section_header = format!("[{}]", self.get_section());
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ macro_rules! impl_backend_constants {
|
|||||||
&self.packages
|
&self.packages
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(&mut self, groups: &HashSet<Group>) {
|
fn load(&mut self, groups: &crate::Groups) {
|
||||||
let own_section_name = self.get_section();
|
let own_section_name = self.get_section();
|
||||||
|
|
||||||
groups
|
groups
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ pub mod todo_per_backend;
|
|||||||
use crate::backend::backend_trait::Switches;
|
use crate::backend::backend_trait::Switches;
|
||||||
use crate::backend::backend_trait::Text;
|
use crate::backend::backend_trait::Text;
|
||||||
use crate::Group;
|
use crate::Group;
|
||||||
|
use crate::Groups;
|
||||||
use crate::Package;
|
use crate::Package;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use backend_trait::Backend;
|
use backend_trait::Backend;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use self::actual::{
|
use self::actual::{
|
||||||
fedora::Fedora, flatpak::Flatpak, python::Python, rust::Rust, rustup::Rustup, void::Void,
|
fedora::Fedora, flatpak::Flatpak, python::Python, rust::Rust, rustup::Rustup, void::Void,
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
//! The clap declarative command line interface
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use clap::{Args, Parser, Subcommand};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(
|
||||||
|
version,
|
||||||
|
author,
|
||||||
|
arg_required_else_help(true),
|
||||||
|
subcommand_required(true),
|
||||||
|
disable_help_subcommand(true),
|
||||||
|
disable_version_flag(true)
|
||||||
|
)]
|
||||||
|
/// multi-backend declarative package manager for Linux
|
||||||
|
pub struct MainArguments {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub subcommand: MainSubcommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum MainSubcommand {
|
||||||
|
Group(GroupArguments),
|
||||||
|
Package(PackageArguments),
|
||||||
|
Version(VersionArguments),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(
|
||||||
|
arg_required_else_help(true),
|
||||||
|
visible_alias("g"),
|
||||||
|
subcommand_required(true)
|
||||||
|
)]
|
||||||
|
/// manage groups
|
||||||
|
pub struct GroupArguments {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub group_action: GroupAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum GroupAction {
|
||||||
|
Edit(EditGroupAction),
|
||||||
|
Export(ExportGroupAction),
|
||||||
|
Import(ImportGroupAction),
|
||||||
|
List(ListGroupAction),
|
||||||
|
New(NewGroupAction),
|
||||||
|
Remove(RemoveGroupAction),
|
||||||
|
Show(ShowGroupAction),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(arg_required_else_help(true), visible_alias("ed"))]
|
||||||
|
/// edit one or more existing group
|
||||||
|
pub struct EditGroupAction {
|
||||||
|
#[arg(required(true), num_args(1..))]
|
||||||
|
/// a previously imported group
|
||||||
|
pub edit_groups: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(arg_required_else_help(true), visible_alias("ex"))]
|
||||||
|
/// export one or more group files
|
||||||
|
pub struct ExportGroupAction {
|
||||||
|
#[arg(required(true), num_args(1..))]
|
||||||
|
/// the file to export as group
|
||||||
|
pub export_groups: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(short, long)]
|
||||||
|
/// (optional) the directory under which to save the group
|
||||||
|
pub output_dir: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[arg(short, long)]
|
||||||
|
/// overwrite output files if they exist
|
||||||
|
pub force: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(arg_required_else_help(true), visible_alias("i"))]
|
||||||
|
/// import one or more group files
|
||||||
|
pub struct ImportGroupAction {
|
||||||
|
#[arg(required(true), num_args(1..))]
|
||||||
|
/// the file to import as group
|
||||||
|
pub import_groups: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(visible_alias("l"))]
|
||||||
|
/// list names of imported groups
|
||||||
|
pub struct ListGroupAction {}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(arg_required_else_help(true), visible_alias("n"))]
|
||||||
|
/// create new group files
|
||||||
|
pub struct NewGroupAction {
|
||||||
|
#[arg(required(true), num_args(1..))]
|
||||||
|
/// the groups to create
|
||||||
|
pub new_groups: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(short, long)]
|
||||||
|
/// edit the new group files after creation
|
||||||
|
pub edit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(arg_required_else_help(true), visible_alias("r"))]
|
||||||
|
/// remove one or more previously imported groups
|
||||||
|
pub struct RemoveGroupAction {
|
||||||
|
#[arg(required(true), num_args(1..))]
|
||||||
|
/// a previously imported group that will be removed
|
||||||
|
pub remove_groups: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(arg_required_else_help(true), visible_alias("s"))]
|
||||||
|
/// show packages under an imported group
|
||||||
|
pub struct ShowGroupAction {
|
||||||
|
#[arg(required(true), num_args(1..))]
|
||||||
|
/// group file(s) to show
|
||||||
|
pub show_groups: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(
|
||||||
|
arg_required_else_help(true),
|
||||||
|
subcommand_required(true),
|
||||||
|
visible_alias("p")
|
||||||
|
)]
|
||||||
|
/// manage packages
|
||||||
|
pub struct PackageArguments {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub package_action: PackageAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum PackageAction {
|
||||||
|
Clean(CleanPackageAction),
|
||||||
|
Review(ReviewPackageAction),
|
||||||
|
Search(SearchPackageAction),
|
||||||
|
Sync(SyncPackageAction),
|
||||||
|
Unmanaged(UnmanagedPackageAction),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(visible_alias("c"))]
|
||||||
|
/// remove unmanaged packages
|
||||||
|
pub struct CleanPackageAction {
|
||||||
|
#[arg(long)]
|
||||||
|
/// do not ask for any confirmation
|
||||||
|
pub no_confirm: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(visible_alias("r"))]
|
||||||
|
/// review unmanaged packages
|
||||||
|
pub struct ReviewPackageAction {}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(arg_required_else_help(true), visible_alias("se"))]
|
||||||
|
/// search for packages which match a provided regex
|
||||||
|
pub struct SearchPackageAction {
|
||||||
|
#[arg(required(true))]
|
||||||
|
/// the regular expression the package must match
|
||||||
|
pub regex: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(visible_alias("sy"))]
|
||||||
|
/// install packages from all imported groups
|
||||||
|
pub struct SyncPackageAction {
|
||||||
|
#[arg(long)]
|
||||||
|
/// do not ask for any confirmation
|
||||||
|
pub no_confirm: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(visible_alias("u"))]
|
||||||
|
/// show explicitly installed packages not managed by pacdef
|
||||||
|
pub struct UnmanagedPackageAction {}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
pub struct VersionArguments {}
|
||||||
@@ -3,7 +3,7 @@ use std::io::{ErrorKind, Write};
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
// Update the master README if fields change.
|
// Update the master README if fields change.
|
||||||
/// Config for the program, as listed in `$XDG_CONFIG_HOME/pacdef/pacdef.yaml`.
|
/// Config for the program, as listed in `$XDG_CONFIG_HOME/pacdef/pacdef.yaml`.
|
||||||
|
|||||||
+355
-371
@@ -1,49 +1,32 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashMap;
|
||||||
use std::env::current_dir;
|
use std::env::current_dir;
|
||||||
use std::fs::{copy, create_dir_all, remove_file, rename, File};
|
use std::fs::{copy, create_dir_all, remove_file, rename, File};
|
||||||
use std::os::unix::fs::symlink;
|
use std::os::unix::fs::symlink;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
use anyhow::{bail, ensure, Context, Result};
|
use anyhow::{bail, ensure, Context, Result};
|
||||||
use const_format::formatcp;
|
use const_format::formatcp;
|
||||||
|
|
||||||
use crate::args::datastructure::{
|
|
||||||
Arguments, Edit, Force, GroupAction, Groups, Noconfirm, OutputDir, PackageAction, Regex,
|
|
||||||
};
|
|
||||||
use crate::backend::backend_trait::Backend;
|
use crate::backend::backend_trait::Backend;
|
||||||
use crate::backend::todo_per_backend::ToDoPerBackend;
|
use crate::backend::todo_per_backend::ToDoPerBackend;
|
||||||
use crate::backend::AnyBackend;
|
use crate::backend::AnyBackend;
|
||||||
use crate::cmd::run_edit_command;
|
use crate::cli::{
|
||||||
use crate::env::should_print_debug_info;
|
CleanPackageAction, EditGroupAction, ExportGroupAction, GroupAction, GroupArguments,
|
||||||
|
ImportGroupAction, ListGroupAction, MainArguments, MainSubcommand, NewGroupAction,
|
||||||
|
PackageAction, PackageArguments, RemoveGroupAction, ReviewPackageAction, SearchPackageAction,
|
||||||
|
ShowGroupAction, SyncPackageAction, UnmanagedPackageAction, VersionArguments,
|
||||||
|
};
|
||||||
|
use crate::cmd::{run_edit_command, run_external_command};
|
||||||
|
use crate::env::{get_editor, 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};
|
||||||
use crate::search;
|
use crate::review::review;
|
||||||
|
use crate::search::search_packages;
|
||||||
use crate::ui::get_user_confirmation;
|
use crate::ui::get_user_confirmation;
|
||||||
use crate::Config;
|
|
||||||
use crate::Group;
|
use crate::Group;
|
||||||
use crate::{review, Error};
|
use crate::{Config, Error, Groups};
|
||||||
|
|
||||||
/// 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<Arguments>,
|
|
||||||
/// The config of the program.
|
|
||||||
config: Config,
|
|
||||||
/// The hashset of all groups.
|
|
||||||
groups: HashSet<Group>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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: Arguments, config: Config, groups: HashSet<Group>) -> Self {
|
|
||||||
Self {
|
|
||||||
args: Some(args),
|
|
||||||
config,
|
|
||||||
groups,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
impl MainArguments {
|
||||||
/// Run the action that was provided by the user as first argument.
|
/// Run the action that was provided by the user as first argument.
|
||||||
///
|
///
|
||||||
/// For convenience sake, all called functions take a `&self` argument, even if
|
/// For convenience sake, all called functions take a `&self` argument, even if
|
||||||
@@ -52,305 +35,132 @@ impl Pacdef {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This function propagates errors from the underlying functions.
|
/// This function propagates errors from the underlying functions.
|
||||||
///
|
pub fn run(self, groups: &Groups, config: &Config) -> Result<()> {
|
||||||
/// # Panics
|
match self.subcommand {
|
||||||
///
|
MainSubcommand::Group(group) => group.run(groups),
|
||||||
/// This function panics if the `args` field is `None`.
|
MainSubcommand::Package(package) => package.run(groups, config),
|
||||||
pub fn run_action_from_arg(mut self) -> Result<()> {
|
MainSubcommand::Version(version) => version.run(),
|
||||||
match self
|
}
|
||||||
.args
|
}
|
||||||
.take()
|
}
|
||||||
.expect("if there were no args we would not get to here")
|
|
||||||
{
|
impl VersionArguments {
|
||||||
Arguments::Group(group_args) => self.run_group_subcommand(&group_args),
|
/// If the crate was compiled from git, return `pacdef, <version> (<hash>)`.
|
||||||
Arguments::Package(package_args) => self.run_package_subcommand(&package_args),
|
/// Otherwise return `pacdef, <version>`.
|
||||||
Arguments::Version => {
|
fn run(self) -> Result<()> {
|
||||||
self.show_version();
|
let backends = get_included_backends();
|
||||||
|
let mut result = format!("pacdef, version: {}\n", get_version_string());
|
||||||
|
result.push_str("supported backends:");
|
||||||
|
for b in backends {
|
||||||
|
result.push_str("\n ");
|
||||||
|
result.push_str(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}", result);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn run_group_subcommand(self, args: &GroupAction) -> Result<()> {
|
impl GroupArguments {
|
||||||
match args {
|
fn run(self, groups: &Groups) -> Result<()> {
|
||||||
GroupAction::Edit(Groups(groups)) => self.edit_groups(groups),
|
match self.group_action {
|
||||||
GroupAction::Export(Groups(groups), OutputDir(dir), Force(force)) => {
|
GroupAction::Edit(edit) => edit.run(groups),
|
||||||
self.export_groups(groups, dir.as_ref(), *force)
|
GroupAction::Export(export) => export.run(groups),
|
||||||
}
|
GroupAction::Import(import) => import.run(),
|
||||||
GroupAction::Import(Groups(groups)) => self.import_groups(groups),
|
GroupAction::List(list) => list.run(groups),
|
||||||
GroupAction::List => self.show_groups(),
|
GroupAction::New(new) => new.run(),
|
||||||
GroupAction::New(Groups(groups), Edit(edit)) => self.new_groups(groups, *edit),
|
GroupAction::Remove(remove) => remove.run(groups),
|
||||||
GroupAction::Remove(Groups(groups)) => self.remove_groups(groups),
|
GroupAction::Show(show) => show.run(groups),
|
||||||
GroupAction::Show(Groups(groups)) => self.show_group_content(groups),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn run_package_subcommand(mut self, args: &PackageAction) -> Result<()> {
|
impl EditGroupAction {
|
||||||
match args {
|
fn run(self, groups: &Groups) -> Result<()> {
|
||||||
PackageAction::Clean(Noconfirm(noconfirm)) => self.clean_packages(*noconfirm),
|
if groups.is_empty() {
|
||||||
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)
|
|
||||||
}
|
|
||||||
PackageAction::Sync(Noconfirm(noconfirm)) => self.install_packages(*noconfirm),
|
|
||||||
PackageAction::Unmanaged => self.show_unmanaged_packages(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_missing_packages(&mut self) -> Result<ToDoPerBackend> {
|
|
||||||
let mut to_install = ToDoPerBackend::new();
|
|
||||||
|
|
||||||
if self.groups.is_empty() {
|
|
||||||
eprintln!("WARNING: no group files found");
|
eprintln!("WARNING: no group files found");
|
||||||
}
|
}
|
||||||
|
|
||||||
for mut backend in AnyBackend::iter() {
|
let group_files: Vec<_> = find_groups_by_name(&self.edit_groups, groups)
|
||||||
if self
|
|
||||||
.config
|
|
||||||
.disabled_backends
|
|
||||||
.contains(&backend.get_section().to_string())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !binary_in_path(backend.get_binary())? {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(to_install)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn overwrite_values_from_config(&mut self, backend: &mut AnyBackend) {
|
|
||||||
#[cfg(feature = "arch")]
|
|
||||||
{
|
|
||||||
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 AnyBackend::Flatpak(flatpak) = backend {
|
|
||||||
flatpak.systemwide = self.config.flatpak_systemwide;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let AnyBackend::Python(python) = backend {
|
|
||||||
python.binary.clone_from(&self.config.pip_binary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn install_packages(&mut self, noconfirm: bool) -> Result<()> {
|
|
||||||
self.warn_about_groups_that_arent_symlinks();
|
|
||||||
|
|
||||||
let to_install = self.get_missing_packages()?;
|
|
||||||
|
|
||||||
if to_install.nothing_to_do_for_all_backends() {
|
|
||||||
println!("nothing to do");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("Would install the following packages:\n");
|
|
||||||
to_install.show().context("printing things to do")?;
|
|
||||||
|
|
||||||
println!();
|
|
||||||
if noconfirm {
|
|
||||||
println!("proceeding without confirmation");
|
|
||||||
} else if !get_user_confirmation()? {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
to_install.install_missing_packages(noconfirm)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn warn_about_groups_that_arent_symlinks(&self) {
|
|
||||||
for group in &self.groups {
|
|
||||||
if group.warn_symlink {
|
|
||||||
eprintln!(
|
|
||||||
"WARNING: group file {} is not a symlink",
|
|
||||||
group.path.to_string_lossy()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn edit_groups(&self, groups: &[String]) -> Result<()> {
|
|
||||||
self.warn_about_groups_that_arent_symlinks();
|
|
||||||
|
|
||||||
if self.groups.is_empty() {
|
|
||||||
eprintln!("WARNING: no group files found");
|
|
||||||
}
|
|
||||||
|
|
||||||
let group_files: Vec<_> = find_groups_by_name(groups, &self.groups)
|
|
||||||
.context("getting group files for args")?
|
.context("getting group files for args")?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|g| g.path.as_path())
|
.map(|g| g.path.as_path())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
run_edit_command(&group_files).context("running editor")?;
|
let mut cmd = Command::new(get_editor().context("getting suitable editor")?);
|
||||||
|
cmd.current_dir(
|
||||||
|
group_files[0]
|
||||||
|
.parent()
|
||||||
|
.context("getting parent dir of first file argument")?,
|
||||||
|
);
|
||||||
|
for group_file in group_files {
|
||||||
|
cmd.arg(group_file.to_string_lossy().to_string());
|
||||||
|
}
|
||||||
|
run_external_command(cmd)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn show_version(self) {
|
impl ExportGroupAction {
|
||||||
println!("{}", get_name_and_version());
|
/// Export pacdef groups by moving a group file to an output dir. The path of the
|
||||||
}
|
/// group file relative to the group base dir will be replicated under the output
|
||||||
|
/// directory.
|
||||||
fn show_unmanaged_packages(mut self) -> Result<()> {
|
|
||||||
let unmanaged_per_backend = &self.get_unmanaged_packages()?;
|
|
||||||
|
|
||||||
if unmanaged_per_backend.nothing_to_do_for_all_backends() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
unmanaged_per_backend
|
|
||||||
.show()
|
|
||||||
.context("printing things to do")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a list of unmanaged packages per backend.
|
|
||||||
///
|
///
|
||||||
/// This method loops through all enabled `Backend`s whose binary is in `PATH`.
|
/// By default, the output dir is the current working directory. `output_dir` may be
|
||||||
|
/// specified to the path of another directory, in which case `output_dir` must
|
||||||
|
/// exist.
|
||||||
|
///
|
||||||
|
/// If `force` is `true`, the output file will be overwritten if it exists.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This function will propagate errors from the individual backends.
|
/// This function will return an error if
|
||||||
fn get_unmanaged_packages(&mut self) -> Result<ToDoPerBackend> {
|
/// - the group file is a symlink (in which case exporting makes no sense),
|
||||||
self.warn_about_groups_that_arent_symlinks();
|
/// - the output file exists and `force` is not `true`, or
|
||||||
|
/// - the user does not have permission to write to the output dir.
|
||||||
if self.groups.is_empty() {
|
|
||||||
eprintln!("WARNING: no group files found");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut result = ToDoPerBackend::new();
|
|
||||||
|
|
||||||
for mut backend in AnyBackend::iter() {
|
|
||||||
if self
|
|
||||||
.config
|
|
||||||
.disabled_backends
|
|
||||||
.contains(&backend.get_section().to_string())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !binary_in_path(backend.get_binary())? {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print the alphabetically sorted names of all groups to stdout.
|
|
||||||
///
|
///
|
||||||
/// This methods cannot return an error. It returns a `Result` to be consistent
|
/// # Limitations
|
||||||
/// with other methods.
|
///
|
||||||
fn show_groups(self) -> Result<()> {
|
/// At the moment we cannot export nested group dirs. The user would have to
|
||||||
self.warn_about_groups_that_arent_symlinks();
|
/// export every group file individually, or use a shell glob.
|
||||||
|
fn run(self, groups: &Groups) -> Result<()> {
|
||||||
if self.groups.is_empty() {
|
let groups = find_groups_by_name(&self.export_groups, groups)?;
|
||||||
eprintln!("WARNING: no group files found");
|
let output_dir = match self.output_dir {
|
||||||
}
|
Some(p) => p,
|
||||||
|
None => current_dir().context("no output dir specified, getting current directory")?,
|
||||||
let mut vec: Vec<_> = self.groups.iter().collect();
|
|
||||||
vec.sort_unstable();
|
|
||||||
for g in vec {
|
|
||||||
println!("{}", g.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clean_packages(&mut self, noconfirm: bool) -> Result<()> {
|
|
||||||
let to_remove = self.get_unmanaged_packages()?;
|
|
||||||
|
|
||||||
if to_remove.nothing_to_do_for_all_backends() {
|
|
||||||
println!("nothing to do");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("Would remove the following packages:\n");
|
|
||||||
to_remove.show().context("printing things to do")?;
|
|
||||||
|
|
||||||
println!();
|
|
||||||
if noconfirm {
|
|
||||||
println!("proceeding without confirmation");
|
|
||||||
} else if !get_user_confirmation()? {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
to_remove.remove_unmanaged_packages(noconfirm)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_group_content(&self, args: &[String]) -> Result<()> {
|
|
||||||
self.warn_about_groups_that_arent_symlinks();
|
|
||||||
|
|
||||||
if self.groups.is_empty() {
|
|
||||||
eprintln!("WARNING: no group files found");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut errors = vec![];
|
|
||||||
let mut groups = vec![];
|
|
||||||
|
|
||||||
// make sure all args exist before doing anything
|
|
||||||
for arg_group in args {
|
|
||||||
let possible_group = self.groups.iter().find(|g| g.name == **arg_group);
|
|
||||||
|
|
||||||
let Some(group) = possible_group else {
|
|
||||||
errors.push((*arg_group).clone());
|
|
||||||
continue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
groups.push(group);
|
|
||||||
}
|
|
||||||
|
|
||||||
// return an error if any arg was not found
|
|
||||||
ensure!(
|
ensure!(
|
||||||
errors.is_empty(),
|
output_dir.exists() && output_dir.is_dir(),
|
||||||
crate::Error::MultipleGroupsNotFound(errors)
|
"output must be a directory and exist"
|
||||||
);
|
);
|
||||||
|
|
||||||
let show_more_than_one_group = args.len() > 1;
|
for group in &groups {
|
||||||
|
ensure!(!&group.path.is_symlink(), "cannot export symlinks");
|
||||||
|
|
||||||
let mut iter = groups.into_iter().peekable();
|
let mut exported_path = output_dir.clone();
|
||||||
|
exported_path.push(PathBuf::from(&group.name));
|
||||||
|
|
||||||
while let Some(group) = iter.next() {
|
ensure!(
|
||||||
if show_more_than_one_group {
|
!self.force && !exported_path.exists(),
|
||||||
let name = &group.name;
|
"{exported_path:?} already exists"
|
||||||
println!("{name}");
|
);
|
||||||
for _ in 0..name.len() {
|
|
||||||
print!("-");
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{group}");
|
create_parent(&exported_path)
|
||||||
if iter.peek().is_some() {
|
.with_context(|| format!("creating parent dir of {exported_path:?}"))?;
|
||||||
println!();
|
move_file(&group.path, &exported_path).context("moving file")?;
|
||||||
}
|
symlink(&exported_path, &group.path).context("creating symlink to exported file")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn import_groups(&self, file_names: &[String]) -> Result<()> {
|
impl ImportGroupAction {
|
||||||
let files = get_absolutized_file_paths(file_names)?;
|
fn run(self) -> Result<()> {
|
||||||
|
let files = get_absolutized_file_paths(&self.import_groups)?;
|
||||||
let groups_dir = get_group_dir()?;
|
let groups_dir = get_group_dir()?;
|
||||||
|
|
||||||
for target in files {
|
for target in files {
|
||||||
@@ -377,21 +187,29 @@ impl Pacdef {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn remove_groups(&self, groups: &[String]) -> Result<()> {
|
impl ListGroupAction {
|
||||||
if self.groups.is_empty() {
|
/// Print the alphabetically sorted names of all groups to stdout.
|
||||||
|
///
|
||||||
|
/// This methods cannot return an error. It returns a `Result` to be consistent
|
||||||
|
/// with other methods.
|
||||||
|
fn run(self, groups: &Groups) -> Result<()> {
|
||||||
|
if groups.is_empty() {
|
||||||
eprintln!("WARNING: no group files found");
|
eprintln!("WARNING: no group files found");
|
||||||
}
|
}
|
||||||
|
|
||||||
let found = find_groups_by_name(groups, &self.groups)?;
|
let mut vec: Vec<_> = groups.iter().collect();
|
||||||
|
vec.sort_unstable();
|
||||||
for group in found {
|
for g in vec {
|
||||||
remove_file(&group.path)?;
|
println!("{}", g.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewGroupAction {
|
||||||
/// Create empty group files.
|
/// Create empty group files.
|
||||||
///
|
///
|
||||||
/// If `edit` is `true`, the editor will be run to edit the files after they are
|
/// If `edit` is `true`, the editor will be run to edit the files after they are
|
||||||
@@ -404,18 +222,19 @@ 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.
|
||||||
fn new_groups(&self, new_groups: &[String], edit: bool) -> Result<()> {
|
fn run(&self) -> Result<()> {
|
||||||
let group_path = get_group_dir()?;
|
let group_path = get_group_dir()?;
|
||||||
|
|
||||||
// prevent group names that resolve to directories
|
// prevent group names that resolve to directories
|
||||||
for name in new_groups {
|
for new_group in &self.new_groups {
|
||||||
ensure!(
|
ensure!(
|
||||||
*name != "." && *name != "..",
|
new_group != "." && new_group != "..",
|
||||||
crate::Error::InvalidGroupName(name.clone())
|
crate::Error::InvalidGroupName(new_group.clone())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let paths: Vec<_> = new_groups
|
let paths: Vec<_> = self
|
||||||
|
.new_groups
|
||||||
.iter()
|
.iter()
|
||||||
.map(|name| {
|
.map(|name| {
|
||||||
let mut base = group_path.clone();
|
let mut base = group_path.clone();
|
||||||
@@ -435,72 +254,251 @@ impl Pacdef {
|
|||||||
File::create(file)?;
|
File::create(file)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if edit {
|
if self.edit {
|
||||||
run_edit_command(&paths).context("running editor")?;
|
run_edit_command(&paths).context("running editor")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Export pacdef groups by moving a group file to an output dir. The path of the
|
impl RemoveGroupAction {
|
||||||
/// group file relative to the group base dir will be replicated under the output
|
fn run(self, groups: &Groups) -> Result<()> {
|
||||||
/// directory.
|
if groups.is_empty() {
|
||||||
///
|
eprintln!("WARNING: no group files found");
|
||||||
/// By default, the output dir is the current working directory. `output_dir` may be
|
}
|
||||||
/// specified to the path of another directory, in which case `output_dir` must
|
|
||||||
/// exist.
|
|
||||||
///
|
|
||||||
/// If `force` is `true`, the output file will be overwritten if it exists.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function will return an error if
|
|
||||||
/// - the group file is a symlink (in which case exporting makes no sense),
|
|
||||||
/// - the output file exists and `force` is not `true`, or
|
|
||||||
/// - the user does not have permission to write to the output dir.
|
|
||||||
///
|
|
||||||
/// # Limitations
|
|
||||||
///
|
|
||||||
/// At the moment we cannot export nested group dirs. The user would have to
|
|
||||||
/// export every group file individually, or use a shell glob.
|
|
||||||
fn export_groups(
|
|
||||||
&self,
|
|
||||||
names: &[String],
|
|
||||||
output_dir: Option<&String>,
|
|
||||||
force: bool,
|
|
||||||
) -> Result<()> {
|
|
||||||
let groups = find_groups_by_name(names, &self.groups)?;
|
|
||||||
let output_dir = match output_dir.map(PathBuf::from) {
|
|
||||||
Some(p) => p,
|
|
||||||
None => current_dir().context("no output dir specified, getting current directory")?,
|
|
||||||
};
|
|
||||||
|
|
||||||
ensure!(
|
let found = find_groups_by_name(&self.remove_groups, groups)?;
|
||||||
output_dir.exists() && output_dir.is_dir(),
|
|
||||||
"output must be a directory and exist"
|
|
||||||
);
|
|
||||||
|
|
||||||
for group in &groups {
|
for group in found {
|
||||||
ensure!(!&group.path.is_symlink(), "cannot export symlinks");
|
remove_file(&group.path)?;
|
||||||
|
|
||||||
let mut exported_path = output_dir.clone();
|
|
||||||
exported_path.push(PathBuf::from(&group.name));
|
|
||||||
|
|
||||||
ensure!(
|
|
||||||
!force && !exported_path.exists(),
|
|
||||||
"{exported_path:?} already exists"
|
|
||||||
);
|
|
||||||
|
|
||||||
create_parent(&exported_path)
|
|
||||||
.with_context(|| format!("creating parent dir of {exported_path:?}"))?;
|
|
||||||
move_file(&group.path, &exported_path).context("moving file")?;
|
|
||||||
symlink(&exported_path, &group.path).context("creating symlink to exported file")?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ShowGroupAction {
|
||||||
|
fn run(self, groups: &Groups) -> Result<()> {
|
||||||
|
if groups.is_empty() {
|
||||||
|
eprintln!("WARNING: no group files found");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut errors = vec![];
|
||||||
|
let mut found_groups = vec![];
|
||||||
|
|
||||||
|
// make sure all args exist before doing anything
|
||||||
|
for show_group in &self.show_groups {
|
||||||
|
let possible_group = groups.iter().find(|group| group.name == *show_group);
|
||||||
|
|
||||||
|
let Some(group) = possible_group else {
|
||||||
|
errors.push(show_group.to_string());
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
found_groups.push(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return an error if any arg was not found
|
||||||
|
ensure!(
|
||||||
|
errors.is_empty(),
|
||||||
|
crate::Error::MultipleGroupsNotFound(errors)
|
||||||
|
);
|
||||||
|
|
||||||
|
let show_more_than_one_group = self.show_groups.len() > 1;
|
||||||
|
|
||||||
|
let mut iter = found_groups.into_iter().peekable();
|
||||||
|
|
||||||
|
while let Some(group) = iter.next() {
|
||||||
|
if show_more_than_one_group {
|
||||||
|
let name = &group.name;
|
||||||
|
println!("{name}");
|
||||||
|
for _ in 0..name.len() {
|
||||||
|
print!("-");
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{group}");
|
||||||
|
if iter.peek().is_some() {
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageArguments {
|
||||||
|
fn run(self, groups: &Groups, config: &Config) -> Result<()> {
|
||||||
|
match self.package_action {
|
||||||
|
PackageAction::Clean(clean) => clean.run(groups, config),
|
||||||
|
PackageAction::Review(review) => review.run(groups, config),
|
||||||
|
PackageAction::Search(search) => search.run(groups),
|
||||||
|
PackageAction::Sync(sync) => sync.run(groups, config),
|
||||||
|
PackageAction::Unmanaged(unmanaged) => unmanaged.run(groups, config),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CleanPackageAction {
|
||||||
|
fn run(self, groups: &Groups, config: &Config) -> Result<()> {
|
||||||
|
let to_remove = get_unmanaged_packages(groups, config)?;
|
||||||
|
|
||||||
|
if to_remove.nothing_to_do_for_all_backends() {
|
||||||
|
println!("nothing to do");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Would remove the following packages:\n");
|
||||||
|
to_remove.show().context("printing things to do")?;
|
||||||
|
|
||||||
|
println!();
|
||||||
|
if self.no_confirm {
|
||||||
|
println!("proceeding without confirmation");
|
||||||
|
} else if !get_user_confirmation()? {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
to_remove.remove_unmanaged_packages(self.no_confirm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReviewPackageAction {
|
||||||
|
fn run(self, groups: &Groups, config: &Config) -> Result<()> {
|
||||||
|
review(get_unmanaged_packages(groups, config)?, groups)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SearchPackageAction {
|
||||||
|
fn run(self, groups: &Groups) -> Result<()> {
|
||||||
|
search_packages(&self.regex, groups)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyncPackageAction {
|
||||||
|
fn run(self, groups: &Groups, config: &Config) -> Result<()> {
|
||||||
|
let to_install = get_missing_packages(groups, config)?;
|
||||||
|
|
||||||
|
if to_install.nothing_to_do_for_all_backends() {
|
||||||
|
println!("nothing to do");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Would install the following packages:\n");
|
||||||
|
to_install.show().context("printing things to do")?;
|
||||||
|
|
||||||
|
println!();
|
||||||
|
if self.no_confirm {
|
||||||
|
println!("proceeding without confirmation");
|
||||||
|
} else if !get_user_confirmation()? {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
to_install.install_missing_packages(self.no_confirm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnmanagedPackageAction {
|
||||||
|
fn run(self, groups: &Groups, config: &Config) -> Result<()> {
|
||||||
|
let unmanaged_per_backend = &get_unmanaged_packages(groups, config)?;
|
||||||
|
|
||||||
|
if unmanaged_per_backend.nothing_to_do_for_all_backends() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
unmanaged_per_backend
|
||||||
|
.show()
|
||||||
|
.context("printing things to do")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_missing_packages(groups: &Groups, config: &Config) -> Result<ToDoPerBackend> {
|
||||||
|
let mut to_install = ToDoPerBackend::new();
|
||||||
|
|
||||||
|
if groups.is_empty() {
|
||||||
|
eprintln!("WARNING: no group files found");
|
||||||
|
}
|
||||||
|
|
||||||
|
for mut backend in AnyBackend::iter() {
|
||||||
|
if config
|
||||||
|
.disabled_backends
|
||||||
|
.contains(&backend.get_section().to_string())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !binary_in_path(backend.get_binary())? {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
overwrite_values_from_config(&mut backend, config);
|
||||||
|
backend.load(groups);
|
||||||
|
|
||||||
|
match backend.get_missing_packages_sorted() {
|
||||||
|
Ok(diff) => to_install.push((backend, diff)),
|
||||||
|
Err(error) => show_backend_query_error(&error, &backend),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(to_install)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overwrite_values_from_config(backend: &mut AnyBackend, config: &Config) {
|
||||||
|
#[cfg(feature = "arch")]
|
||||||
|
{
|
||||||
|
if let AnyBackend::Arch(arch) = backend {
|
||||||
|
arch.binary.clone_from(&config.aur_helper);
|
||||||
|
arch.aur_rm_args.clone_from(&config.aur_rm_args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let AnyBackend::Flatpak(flatpak) = backend {
|
||||||
|
flatpak.systemwide = config.flatpak_systemwide;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let AnyBackend::Python(python) = backend {
|
||||||
|
python.binary.clone_from(&config.pip_binary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a list of unmanaged packages per backend.
|
||||||
|
///
|
||||||
|
/// This method loops through all enabled `Backend`s whose binary is in `PATH`.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will propagate errors from the individual backends.
|
||||||
|
fn get_unmanaged_packages(groups: &Groups, config: &Config) -> Result<ToDoPerBackend> {
|
||||||
|
if groups.is_empty() {
|
||||||
|
eprintln!("WARNING: no group files found");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = ToDoPerBackend::new();
|
||||||
|
|
||||||
|
for mut backend in AnyBackend::iter() {
|
||||||
|
if config
|
||||||
|
.disabled_backends
|
||||||
|
.contains(&backend.get_section().to_string())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !binary_in_path(backend.get_binary())? {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
overwrite_values_from_config(&mut backend, config);
|
||||||
|
backend.load(groups);
|
||||||
|
|
||||||
|
match backend.get_unmanaged_packages_sorted() {
|
||||||
|
Ok(unmanaged) => result.push((backend, unmanaged)),
|
||||||
|
Err(error) => show_backend_query_error(&error, &backend),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
/// Create the parent directory of the `path` if that directory does not exist.
|
/// Create the parent directory of the `path` if that directory does not exist.
|
||||||
///
|
///
|
||||||
/// Do nothing otherwise.
|
/// Do nothing otherwise.
|
||||||
@@ -558,7 +556,7 @@ where
|
|||||||
///
|
///
|
||||||
/// This function will return an error if any of the file names do not match one
|
/// This function will return an error if any of the file names do not match one
|
||||||
/// of group names.
|
/// of group names.
|
||||||
fn find_groups_by_name<'a>(names: &[String], groups: &'a HashSet<Group>) -> Result<Vec<&'a Group>> {
|
fn find_groups_by_name<'a>(names: &[String], groups: &'a Groups) -> Result<Vec<&'a Group>> {
|
||||||
let name_group_map: HashMap<&str, &Group> =
|
let name_group_map: HashMap<&str, &Group> =
|
||||||
groups.iter().map(|g| (g.name.as_str(), g)).collect();
|
groups.iter().map(|g| (g.name.as_str(), g)).collect();
|
||||||
|
|
||||||
@@ -590,20 +588,6 @@ fn show_backend_query_error(error: &anyhow::Error, backend: &AnyBackend) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the crate was compiled from git, return `pacdef, <version> (<hash>)`.
|
|
||||||
/// Otherwise return `pacdef, <version>`.
|
|
||||||
fn get_name_and_version() -> String {
|
|
||||||
let backends = get_included_backends();
|
|
||||||
let mut result = format!("pacdef, version: {}\n", get_version_string());
|
|
||||||
result.push_str("supported backends:");
|
|
||||||
for b in backends {
|
|
||||||
result.push_str("\n ");
|
|
||||||
result.push_str(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If the crate was compiled from git, return `<version> (<hash>)`. Otherwise
|
/// If the crate was compiled from git, return `<version> (<hash>)`. Otherwise
|
||||||
/// return `<version>`.
|
/// return `<version>`.
|
||||||
pub const fn get_version_string() -> &'static str {
|
pub const fn get_version_string() -> &'static str {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::{BTreeSet, HashSet};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::fs::{create_dir, read_to_string, File};
|
use std::fs::{create_dir, read_to_string, File};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
@@ -13,8 +13,11 @@ use crate::path::get_relative_path;
|
|||||||
|
|
||||||
use super::{Package, Section};
|
use super::{Package, Section};
|
||||||
|
|
||||||
|
/// A set of groups
|
||||||
|
pub type Groups = BTreeSet<Group>;
|
||||||
|
|
||||||
/// Representation of a group file.
|
/// Representation of a group file.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
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).
|
||||||
@@ -37,8 +40,8 @@ impl Group {
|
|||||||
///
|
///
|
||||||
/// This function will return an error if any of the files under `group_dir` cannot
|
/// This function will return an error if any of the files under `group_dir` cannot
|
||||||
/// be accessed.
|
/// be accessed.
|
||||||
pub fn load(group_dir: &Path, warn_not_symlinks: bool) -> Result<HashSet<Self>> {
|
pub fn load(group_dir: &Path, warn_not_symlinks: bool) -> Result<Groups> {
|
||||||
let mut result = HashSet::new();
|
let mut result = Groups::new();
|
||||||
|
|
||||||
if !group_dir.is_dir() {
|
if !group_dir.is_dir() {
|
||||||
// we only need to create the innermost dir. The rest was already created from when
|
// we only need to create the innermost dir. The rest was already created from when
|
||||||
|
|||||||
@@ -14,5 +14,6 @@ mod package;
|
|||||||
mod section;
|
mod section;
|
||||||
|
|
||||||
pub use group::Group;
|
pub use group::Group;
|
||||||
|
pub use group::Groups;
|
||||||
pub use package::Package;
|
pub use package::Package;
|
||||||
pub use section::Section;
|
pub use section::Section;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use anyhow::{ensure, Context, Result};
|
|||||||
|
|
||||||
use super::Package;
|
use super::Package;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Section {
|
pub struct Section {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub packages: HashSet<Package>,
|
pub packages: HashSet<Package>,
|
||||||
|
|||||||
@@ -20,22 +20,25 @@
|
|||||||
missing_docs
|
missing_docs
|
||||||
)]
|
)]
|
||||||
|
|
||||||
mod args;
|
|
||||||
pub(crate) mod backend;
|
pub(crate) mod backend;
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub mod cli;
|
||||||
|
|
||||||
mod cmd;
|
mod cmd;
|
||||||
mod config;
|
mod config;
|
||||||
|
#[allow(clippy::unused_self, clippy::unnecessary_wraps)]
|
||||||
mod core;
|
mod core;
|
||||||
mod env;
|
mod env;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod grouping;
|
mod grouping;
|
||||||
pub mod path;
|
|
||||||
mod review;
|
mod review;
|
||||||
mod search;
|
mod search;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
pub use crate::args::get as get_args;
|
pub mod path;
|
||||||
|
|
||||||
pub use crate::config::Config;
|
pub use crate::config::Config;
|
||||||
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 use crate::grouping::Groups;
|
||||||
pub use crate::grouping::Package;
|
pub use crate::grouping::Package;
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ use std::process::{ExitCode, Termination};
|
|||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use pacdef::cli::MainArguments;
|
||||||
use pacdef::path::{get_config_path, get_config_path_old_version, get_group_dir};
|
use pacdef::path::{get_config_path, get_config_path_old_version, get_group_dir};
|
||||||
use pacdef::{get_args, Config, Error as PacdefError, Group, Pacdef};
|
use pacdef::{Config, Error as PacdefError, Group};
|
||||||
|
|
||||||
const MAJOR_UPDATE_MESSAGE: &str = "VERSION UPGRADE
|
const MAJOR_UPDATE_MESSAGE: &str = "VERSION UPGRADE
|
||||||
You seem to have used version 0.x of pacdef before.
|
You seem to have used version 0.x of pacdef before.
|
||||||
@@ -51,7 +53,7 @@ fn handle_final_result(result: Result<()>) -> ExitCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main_inner() -> Result<()> {
|
fn main_inner() -> Result<()> {
|
||||||
let args = get_args();
|
let main_arguments = MainArguments::parse();
|
||||||
|
|
||||||
let config_file = get_config_path().context("getting config file")?;
|
let config_file = get_config_path().context("getting config file")?;
|
||||||
|
|
||||||
@@ -73,8 +75,16 @@ fn main_inner() -> Result<()> {
|
|||||||
let groups = Group::load(&group_dir, config.warn_not_symlinks)
|
let groups = Group::load(&group_dir, config.warn_not_symlinks)
|
||||||
.with_context(|| format!("loading groups under {}", group_dir.to_string_lossy()))?;
|
.with_context(|| format!("loading groups under {}", group_dir.to_string_lossy()))?;
|
||||||
|
|
||||||
let pacdef = Pacdef::new(args, config, groups);
|
for group in groups.iter() {
|
||||||
pacdef.run_action_from_arg().context("running action")
|
if group.warn_symlink {
|
||||||
|
eprintln!(
|
||||||
|
"WARNING: group file {} is not a symlink",
|
||||||
|
group.path.to_string_lossy()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main_arguments.run(&groups, &config)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_default_config(config_file: &Path) -> Result<Config> {
|
fn load_default_config(config_file: &Path) -> Result<Config> {
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use crate::{backend::AnyBackend, Group, Package};
|
use crate::{backend::AnyBackend, Group, Package};
|
||||||
|
|
||||||
use super::strategy::Strategy;
|
use super::strategy::Strategy;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub(super) enum ReviewAction {
|
pub enum ReviewAction {
|
||||||
AsDependency(Package),
|
AsDependency(Package),
|
||||||
Delete(Package),
|
Delete(Package),
|
||||||
AssignGroup(Package, Rc<Group>),
|
AssignGroup(Package, Group),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(super) enum ReviewIntention {
|
pub enum ReviewIntention {
|
||||||
AsDependency,
|
AsDependency,
|
||||||
AssignGroup,
|
AssignGroup,
|
||||||
Delete,
|
Delete,
|
||||||
@@ -24,20 +22,20 @@ pub(super) enum ReviewIntention {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(super) struct ReviewsPerBackend {
|
pub struct ReviewsPerBackend {
|
||||||
items: Vec<(AnyBackend, Vec<ReviewAction>)>,
|
items: Vec<(AnyBackend, Vec<ReviewAction>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReviewsPerBackend {
|
impl ReviewsPerBackend {
|
||||||
pub(super) fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { items: vec![] }
|
Self { items: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn nothing_to_do(&self) -> bool {
|
pub fn nothing_to_do(&self) -> bool {
|
||||||
self.items.iter().all(|(_, vec)| vec.is_empty())
|
self.items.iter().all(|(_, vec)| vec.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn push(&mut self, value: (AnyBackend, Vec<ReviewAction>)) {
|
pub fn push(&mut self, value: (AnyBackend, Vec<ReviewAction>)) {
|
||||||
self.items.push(value);
|
self.items.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +44,7 @@ impl ReviewsPerBackend {
|
|||||||
///
|
///
|
||||||
/// If there are no actions for a `Backend`, then that `Backend` is removed from the return
|
/// If there are no actions for a `Backend`, then that `Backend` is removed from the return
|
||||||
/// value.
|
/// value.
|
||||||
pub(super) fn into_strategies(self) -> Vec<Strategy> {
|
pub fn into_strategies(self) -> Vec<Strategy> {
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
|
|
||||||
for (backend, actions) in self {
|
for (backend, actions) in self {
|
||||||
@@ -85,7 +83,7 @@ impl IntoIterator for ReviewsPerBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) enum ContinueWithReview {
|
pub enum ContinueWithReview {
|
||||||
Yes,
|
Yes,
|
||||||
No,
|
No,
|
||||||
NoAndApply,
|
NoAndApply,
|
||||||
@@ -94,7 +92,7 @@ pub(super) enum ContinueWithReview {
|
|||||||
fn extract_actions(
|
fn extract_actions(
|
||||||
actions: Vec<ReviewAction>,
|
actions: Vec<ReviewAction>,
|
||||||
to_delete: &mut Vec<Package>,
|
to_delete: &mut Vec<Package>,
|
||||||
assign_group: &mut Vec<(Package, Rc<Group>)>,
|
assign_group: &mut Vec<(Package, Group)>,
|
||||||
as_dependency: &mut Vec<Package>,
|
as_dependency: &mut Vec<Package>,
|
||||||
) {
|
) {
|
||||||
for action in actions {
|
for action in actions {
|
||||||
|
|||||||
@@ -2,26 +2,19 @@ mod datastructures;
|
|||||||
mod strategy;
|
mod strategy;
|
||||||
|
|
||||||
use std::io::{stdin, stdout, Write};
|
use std::io::{stdin, stdout, Write};
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::backend::backend_trait::Backend;
|
use crate::backend::backend_trait::Backend;
|
||||||
use crate::backend::todo_per_backend::ToDoPerBackend;
|
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, Groups, Package};
|
||||||
|
|
||||||
use self::datastructures::{ContinueWithReview, ReviewAction, ReviewIntention, ReviewsPerBackend};
|
use self::datastructures::{ContinueWithReview, ReviewAction, ReviewIntention, ReviewsPerBackend};
|
||||||
use self::strategy::Strategy;
|
use self::strategy::Strategy;
|
||||||
|
|
||||||
pub fn review(
|
pub fn review(todo_per_backend: ToDoPerBackend, groups: &Groups) -> Result<()> {
|
||||||
todo_per_backend: ToDoPerBackend,
|
|
||||||
groups: impl IntoIterator<Item = Group>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut reviews = ReviewsPerBackend::new();
|
let mut reviews = ReviewsPerBackend::new();
|
||||||
let mut groups: Vec<Rc<Group>> = groups.into_iter().map(Rc::new).collect();
|
|
||||||
|
|
||||||
groups.sort_unstable();
|
|
||||||
|
|
||||||
if todo_per_backend.nothing_to_do_for_all_backends() {
|
if todo_per_backend.nothing_to_do_for_all_backends() {
|
||||||
println!("nothing to do");
|
println!("nothing to do");
|
||||||
@@ -32,7 +25,7 @@ pub fn review(
|
|||||||
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 => {
|
||||||
@@ -76,7 +69,7 @@ pub fn review(
|
|||||||
|
|
||||||
fn get_action_for_package(
|
fn get_action_for_package(
|
||||||
package: Package,
|
package: Package,
|
||||||
groups: &[Rc<Group>],
|
groups: &Groups,
|
||||||
reviews: &mut Vec<ReviewAction>,
|
reviews: &mut Vec<ReviewAction>,
|
||||||
backend: &dyn Backend,
|
backend: &dyn Backend,
|
||||||
) -> Result<ContinueWithReview> {
|
) -> Result<ContinueWithReview> {
|
||||||
@@ -92,7 +85,7 @@ fn get_action_for_package(
|
|||||||
}
|
}
|
||||||
ReviewIntention::AssignGroup => {
|
ReviewIntention::AssignGroup => {
|
||||||
if let Ok(Some(group)) = ask_group(groups) {
|
if let Ok(Some(group)) = ask_group(groups) {
|
||||||
reviews.push(ReviewAction::AssignGroup(package, group));
|
reviews.push(ReviewAction::AssignGroup(package, group.clone()));
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -154,7 +147,7 @@ fn print_query(supports_as_dependency: bool) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_enumerated_groups(groups: &[Rc<Group>]) {
|
fn print_enumerated_groups(groups: &Groups) {
|
||||||
let number_digits = get_amount_of_digits_for_number(groups.len());
|
let number_digits = get_amount_of_digits_for_number(groups.len());
|
||||||
|
|
||||||
for (i, group) in groups.iter().enumerate() {
|
for (i, group) in groups.iter().enumerate() {
|
||||||
@@ -166,7 +159,7 @@ fn get_amount_of_digits_for_number(number: usize) -> usize {
|
|||||||
number.to_string().len()
|
number.to_string().len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ask_group(groups: &[Rc<Group>]) -> Result<Option<Rc<Group>>> {
|
fn ask_group(groups: &Groups) -> Result<Option<&Group>> {
|
||||||
print_enumerated_groups(groups);
|
print_enumerated_groups(groups);
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
stdin().read_line(&mut buf)?;
|
stdin().read_line(&mut buf)?;
|
||||||
@@ -179,7 +172,7 @@ fn ask_group(groups: &[Rc<Group>]) -> Result<Option<Rc<Group>>> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if idx < groups.len() {
|
if idx < groups.len() {
|
||||||
Ok(Some(groups[idx].clone()))
|
Ok(groups.iter().nth(idx))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -8,19 +6,19 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(super) struct Strategy {
|
pub struct Strategy {
|
||||||
backend: AnyBackend,
|
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, Group)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Strategy {
|
impl Strategy {
|
||||||
pub(super) fn new(
|
pub fn new(
|
||||||
backend: AnyBackend,
|
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, Group)>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
backend,
|
backend,
|
||||||
@@ -30,7 +28,7 @@ impl Strategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn execute(self) -> Result<()> {
|
pub fn execute(self) -> Result<()> {
|
||||||
if !self.delete.is_empty() {
|
if !self.delete.is_empty() {
|
||||||
self.backend.remove_packages(&self.delete, false)?;
|
self.backend.remove_packages(&self.delete, false)?;
|
||||||
}
|
}
|
||||||
@@ -46,7 +44,7 @@ impl Strategy {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn show(&self) {
|
pub fn show(&self) {
|
||||||
if self.nothing_to_do() {
|
if self.nothing_to_do() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -75,7 +73,7 @@ impl Strategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn nothing_to_do(&self) -> bool {
|
pub fn nothing_to_do(&self) -> bool {
|
||||||
self.delete.is_empty() && self.as_dependency.is_empty() && self.assign_group.is_empty()
|
self.delete.is_empty() && self.as_dependency.is_empty() && self.assign_group.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::vec::IntoIter;
|
use std::vec::IntoIter;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::grouping::{Group, Package, Section};
|
use crate::{
|
||||||
|
grouping::{Group, Package, Section},
|
||||||
|
Groups,
|
||||||
|
};
|
||||||
|
|
||||||
/// Find all packages in all groups whose name match the regex from the
|
/// Find all packages in all groups whose name match the regex from the
|
||||||
/// command-line arguments. Print the name of the packages per group and
|
/// command-line arguments. Print the name of the packages per group and
|
||||||
@@ -16,7 +18,7 @@ use crate::grouping::{Group, Package, Section};
|
|||||||
/// This function will return an error if
|
/// This function will return an error if
|
||||||
/// - an invalid regex was provided, or
|
/// - an invalid regex was provided, or
|
||||||
/// - no matching packages could be found.
|
/// - no matching packages could be found.
|
||||||
pub fn search_packages(regex_str: &str, groups: &HashSet<Group>) -> Result<()> {
|
pub fn search_packages(regex_str: &str, groups: &Groups) -> Result<()> {
|
||||||
if groups.is_empty() {
|
if groups.is_empty() {
|
||||||
eprintln!("WARNING: no group files found");
|
eprintln!("WARNING: no group files found");
|
||||||
bail!(crate::errors::Error::NoPackagesFound);
|
bail!(crate::errors::Error::NoPackagesFound);
|
||||||
|
|||||||
Reference in New Issue
Block a user