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"
|
||||
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
@@ -102,12 +96,6 @@ version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.4"
|
||||
@@ -115,6 +103,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -129,6 +118,18 @@ dependencies = [
|
||||
"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]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.0"
|
||||
@@ -243,113 +244,18 @@ dependencies = [
|
||||
"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]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
@@ -411,10 +317,8 @@ dependencies = [
|
||||
"libc",
|
||||
"path-absolutize",
|
||||
"regex",
|
||||
"rstest",
|
||||
"rust-apt",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"termios",
|
||||
@@ -439,18 +343,6 @@ dependencies = [
|
||||
"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]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.30"
|
||||
@@ -504,41 +396,6 @@ version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "rust-apt"
|
||||
version = "0.7.0"
|
||||
@@ -550,15 +407,6 @@ dependencies = [
|
||||
"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]]
|
||||
name = "rustix"
|
||||
version = "0.38.32"
|
||||
@@ -593,12 +441,6 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.197"
|
||||
@@ -643,15 +485,6 @@ dependencies = [
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
|
||||
@@ -12,7 +12,7 @@ categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
clap = "4.5"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
const_format = { version = "0.2", default-features = false }
|
||||
path-absolutize = "3.1"
|
||||
regex = { version = "1.10", default-features = false, features = ["std"] }
|
||||
@@ -21,8 +21,7 @@ walkdir = "2.5"
|
||||
libc = "0.2"
|
||||
enum_dispatch = "0.3"
|
||||
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
|
||||
@@ -30,9 +29,6 @@ serde_yaml = "0.9"
|
||||
alpm = { version = "3.0", optional = true }
|
||||
rust-apt = { version = "0.7", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rstest = "0.19"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
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::macros::impl_backend_constants;
|
||||
use crate::cmd::run_external_command;
|
||||
use crate::{Group, Package};
|
||||
use crate::Package;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
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::root::build_base_command_with_privileges;
|
||||
use crate::cmd::run_external_command;
|
||||
use crate::{Group, Package};
|
||||
use crate::Package;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Debian {
|
||||
|
||||
@@ -6,7 +6,7 @@ use anyhow::Result;
|
||||
use crate::backend::backend_trait::{Backend, Switches, Text};
|
||||
use crate::backend::macros::impl_backend_constants;
|
||||
use crate::cmd::run_external_command;
|
||||
use crate::{Group, Package};
|
||||
use crate::Package;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Fedora {
|
||||
|
||||
@@ -6,7 +6,7 @@ use anyhow::Result;
|
||||
use crate::backend::backend_trait::{Backend, Switches, Text};
|
||||
use crate::backend::macros::impl_backend_constants;
|
||||
use crate::cmd::run_external_command;
|
||||
use crate::{Group, Package};
|
||||
use crate::Package;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Flatpak {
|
||||
|
||||
@@ -7,7 +7,7 @@ use serde_json::Value;
|
||||
|
||||
use crate::backend::backend_trait::{Backend, Switches, Text};
|
||||
use crate::backend::macros::impl_backend_constants;
|
||||
use crate::{Group, Package};
|
||||
use crate::Package;
|
||||
|
||||
macro_rules! ERROR{
|
||||
($bin:expr) => {
|
||||
|
||||
@@ -8,7 +8,7 @@ use serde_json::Value;
|
||||
|
||||
use crate::backend::backend_trait::{Backend, Switches, Text};
|
||||
use crate::backend::macros::impl_backend_constants;
|
||||
use crate::{Group, Package};
|
||||
use crate::Package;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Rust {
|
||||
|
||||
@@ -4,7 +4,7 @@ mod types;
|
||||
use crate::backend::backend_trait::{Backend, Switches, Text};
|
||||
use crate::backend::macros::impl_backend_constants;
|
||||
use crate::cmd::run_external_command;
|
||||
use crate::{Group, Package};
|
||||
use crate::Package;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::collections::HashSet;
|
||||
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::root::build_base_command_with_privileges;
|
||||
use crate::cmd::run_external_command;
|
||||
use crate::{Group, Package};
|
||||
use crate::Package;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Void {
|
||||
|
||||
@@ -2,12 +2,11 @@ use std::cmp::{Eq, Ord};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::hash::Hash;
|
||||
use std::process::Command;
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::cmd::run_external_command;
|
||||
use crate::{Group, Package};
|
||||
use crate::{Group, Groups, Package};
|
||||
|
||||
pub type Switches = &'static [&'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,
|
||||
/// 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
|
||||
/// 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
|
||||
/// 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 section_header = format!("[{}]", self.get_section());
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ macro_rules! impl_backend_constants {
|
||||
&self.packages
|
||||
}
|
||||
|
||||
fn load(&mut self, groups: &HashSet<Group>) {
|
||||
fn load(&mut self, groups: &crate::Groups) {
|
||||
let own_section_name = self.get_section();
|
||||
|
||||
groups
|
||||
|
||||
@@ -7,11 +7,11 @@ pub mod todo_per_backend;
|
||||
use crate::backend::backend_trait::Switches;
|
||||
use crate::backend::backend_trait::Text;
|
||||
use crate::Group;
|
||||
use crate::Groups;
|
||||
use crate::Package;
|
||||
use anyhow::Result;
|
||||
use backend_trait::Backend;
|
||||
use std::collections::HashSet;
|
||||
use std::rc::Rc;
|
||||
|
||||
use self::actual::{
|
||||
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 anyhow::{bail, Context, Result};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Update the master README if fields change.
|
||||
/// Config for the program, as listed in `$XDG_CONFIG_HOME/pacdef/pacdef.yaml`.
|
||||
|
||||
+357
-373
@@ -1,49 +1,32 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
use std::env::current_dir;
|
||||
use std::fs::{copy, create_dir_all, remove_file, rename, File};
|
||||
use std::os::unix::fs::symlink;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
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::todo_per_backend::ToDoPerBackend;
|
||||
use crate::backend::AnyBackend;
|
||||
use crate::cmd::run_edit_command;
|
||||
use crate::env::should_print_debug_info;
|
||||
use crate::cli::{
|
||||
CleanPackageAction, EditGroupAction, ExportGroupAction, GroupAction, GroupArguments,
|
||||
ImportGroupAction, ListGroupAction, MainArguments, MainSubcommand, NewGroupAction,
|
||||
PackageAction, PackageArguments, RemoveGroupAction, ReviewPackageAction, SearchPackageAction,
|
||||
ShowGroupAction, SyncPackageAction, UnmanagedPackageAction, VersionArguments,
|
||||
};
|
||||
use crate::cmd::{run_edit_command, run_external_command};
|
||||
use crate::env::{get_editor, should_print_debug_info};
|
||||
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::Config;
|
||||
use crate::Group;
|
||||
use crate::{review, Error};
|
||||
|
||||
/// Most data that is required during runtime of the program.
|
||||
pub struct Pacdef {
|
||||
/// The command line arguments. Is an `Option` so that we can take ownership later without cloning.
|
||||
args: Option<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,
|
||||
}
|
||||
}
|
||||
use crate::{Config, Error, Groups};
|
||||
|
||||
impl MainArguments {
|
||||
/// Run the action that was provided by the user as first argument.
|
||||
///
|
||||
/// For convenience sake, all called functions take a `&self` argument, even if
|
||||
@@ -52,305 +35,132 @@ impl Pacdef {
|
||||
/// # Errors
|
||||
///
|
||||
/// This function propagates errors from the underlying functions.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if the `args` field is `None`.
|
||||
pub fn run_action_from_arg(mut self) -> Result<()> {
|
||||
match self
|
||||
.args
|
||||
.take()
|
||||
.expect("if there were no args we would not get to here")
|
||||
{
|
||||
Arguments::Group(group_args) => self.run_group_subcommand(&group_args),
|
||||
Arguments::Package(package_args) => self.run_package_subcommand(&package_args),
|
||||
Arguments::Version => {
|
||||
self.show_version();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn run(self, groups: &Groups, config: &Config) -> Result<()> {
|
||||
match self.subcommand {
|
||||
MainSubcommand::Group(group) => group.run(groups),
|
||||
MainSubcommand::Package(package) => package.run(groups, config),
|
||||
MainSubcommand::Version(version) => version.run(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_group_subcommand(self, args: &GroupAction) -> Result<()> {
|
||||
match args {
|
||||
GroupAction::Edit(Groups(groups)) => self.edit_groups(groups),
|
||||
GroupAction::Export(Groups(groups), OutputDir(dir), Force(force)) => {
|
||||
self.export_groups(groups, dir.as_ref(), *force)
|
||||
}
|
||||
GroupAction::Import(Groups(groups)) => self.import_groups(groups),
|
||||
GroupAction::List => self.show_groups(),
|
||||
GroupAction::New(Groups(groups), Edit(edit)) => self.new_groups(groups, *edit),
|
||||
GroupAction::Remove(Groups(groups)) => self.remove_groups(groups),
|
||||
GroupAction::Show(Groups(groups)) => self.show_group_content(groups),
|
||||
impl VersionArguments {
|
||||
/// If the crate was compiled from git, return `pacdef, <version> (<hash>)`.
|
||||
/// Otherwise return `pacdef, <version>`.
|
||||
fn run(self) -> Result<()> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupArguments {
|
||||
fn run(self, groups: &Groups) -> Result<()> {
|
||||
match self.group_action {
|
||||
GroupAction::Edit(edit) => edit.run(groups),
|
||||
GroupAction::Export(export) => export.run(groups),
|
||||
GroupAction::Import(import) => import.run(),
|
||||
GroupAction::List(list) => list.run(groups),
|
||||
GroupAction::New(new) => new.run(),
|
||||
GroupAction::Remove(remove) => remove.run(groups),
|
||||
GroupAction::Show(show) => show.run(groups),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_package_subcommand(mut self, args: &PackageAction) -> Result<()> {
|
||||
match args {
|
||||
PackageAction::Clean(Noconfirm(noconfirm)) => self.clean_packages(*noconfirm),
|
||||
PackageAction::Review => review::review(self.get_unmanaged_packages()?, self.groups),
|
||||
PackageAction::Search(Regex(regex)) => {
|
||||
self.warn_about_groups_that_arent_symlinks();
|
||||
search::search_packages(regex, &self.groups)
|
||||
}
|
||||
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() {
|
||||
impl EditGroupAction {
|
||||
fn run(self, groups: &Groups) -> Result<()> {
|
||||
if groups.is_empty() {
|
||||
eprintln!("WARNING: no group files found");
|
||||
}
|
||||
|
||||
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_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)
|
||||
let group_files: Vec<_> = find_groups_by_name(&self.edit_groups, groups)
|
||||
.context("getting group files for args")?
|
||||
.into_iter()
|
||||
.map(|g| g.path.as_path())
|
||||
.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(())
|
||||
}
|
||||
}
|
||||
|
||||
fn show_version(self) {
|
||||
println!("{}", get_name_and_version());
|
||||
}
|
||||
|
||||
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.
|
||||
impl ExportGroupAction {
|
||||
/// 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.
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// This function will propagate errors from the individual backends.
|
||||
fn get_unmanaged_packages(&mut self) -> Result<ToDoPerBackend> {
|
||||
self.warn_about_groups_that_arent_symlinks();
|
||||
|
||||
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 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.
|
||||
///
|
||||
/// This methods cannot return an error. It returns a `Result` to be consistent
|
||||
/// with other methods.
|
||||
fn show_groups(self) -> Result<()> {
|
||||
self.warn_about_groups_that_arent_symlinks();
|
||||
/// # 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 run(self, groups: &Groups) -> Result<()> {
|
||||
let groups = find_groups_by_name(&self.export_groups, groups)?;
|
||||
let output_dir = match self.output_dir {
|
||||
Some(p) => p,
|
||||
None => current_dir().context("no output dir specified, getting current directory")?,
|
||||
};
|
||||
|
||||
if self.groups.is_empty() {
|
||||
eprintln!("WARNING: no group files found");
|
||||
}
|
||||
|
||||
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!(
|
||||
errors.is_empty(),
|
||||
crate::Error::MultipleGroupsNotFound(errors)
|
||||
output_dir.exists() && output_dir.is_dir(),
|
||||
"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() {
|
||||
if show_more_than_one_group {
|
||||
let name = &group.name;
|
||||
println!("{name}");
|
||||
for _ in 0..name.len() {
|
||||
print!("-");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
ensure!(
|
||||
!self.force && !exported_path.exists(),
|
||||
"{exported_path:?} already exists"
|
||||
);
|
||||
|
||||
println!("{group}");
|
||||
if iter.peek().is_some() {
|
||||
println!();
|
||||
}
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
fn import_groups(&self, file_names: &[String]) -> Result<()> {
|
||||
let files = get_absolutized_file_paths(file_names)?;
|
||||
impl ImportGroupAction {
|
||||
fn run(self) -> Result<()> {
|
||||
let files = get_absolutized_file_paths(&self.import_groups)?;
|
||||
let groups_dir = get_group_dir()?;
|
||||
|
||||
for target in files {
|
||||
@@ -377,21 +187,29 @@ impl Pacdef {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_groups(&self, groups: &[String]) -> Result<()> {
|
||||
if self.groups.is_empty() {
|
||||
impl ListGroupAction {
|
||||
/// 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");
|
||||
}
|
||||
|
||||
let found = find_groups_by_name(groups, &self.groups)?;
|
||||
|
||||
for group in found {
|
||||
remove_file(&group.path)?;
|
||||
let mut vec: Vec<_> = groups.iter().collect();
|
||||
vec.sort_unstable();
|
||||
for g in vec {
|
||||
println!("{}", g.name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NewGroupAction {
|
||||
/// Create empty group files.
|
||||
///
|
||||
/// 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,
|
||||
/// - the editor cannot be run, or
|
||||
/// - 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()?;
|
||||
|
||||
// prevent group names that resolve to directories
|
||||
for name in new_groups {
|
||||
for new_group in &self.new_groups {
|
||||
ensure!(
|
||||
*name != "." && *name != "..",
|
||||
crate::Error::InvalidGroupName(name.clone())
|
||||
new_group != "." && new_group != "..",
|
||||
crate::Error::InvalidGroupName(new_group.clone())
|
||||
);
|
||||
}
|
||||
|
||||
let paths: Vec<_> = new_groups
|
||||
let paths: Vec<_> = self
|
||||
.new_groups
|
||||
.iter()
|
||||
.map(|name| {
|
||||
let mut base = group_path.clone();
|
||||
@@ -435,72 +254,251 @@ impl Pacdef {
|
||||
File::create(file)?;
|
||||
}
|
||||
|
||||
if edit {
|
||||
if self.edit {
|
||||
run_edit_command(&paths).context("running editor")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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")?,
|
||||
};
|
||||
impl RemoveGroupAction {
|
||||
fn run(self, groups: &Groups) -> Result<()> {
|
||||
if groups.is_empty() {
|
||||
eprintln!("WARNING: no group files found");
|
||||
}
|
||||
|
||||
ensure!(
|
||||
output_dir.exists() && output_dir.is_dir(),
|
||||
"output must be a directory and exist"
|
||||
);
|
||||
let found = find_groups_by_name(&self.remove_groups, groups)?;
|
||||
|
||||
for group in &groups {
|
||||
ensure!(!&group.path.is_symlink(), "cannot export symlinks");
|
||||
|
||||
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")?;
|
||||
for group in found {
|
||||
remove_file(&group.path)?;
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
/// Do nothing otherwise.
|
||||
@@ -558,7 +556,7 @@ where
|
||||
///
|
||||
/// This function will return an error if any of the file names do not match one
|
||||
/// 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> =
|
||||
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
|
||||
/// return `<version>`.
|
||||
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::fs::{create_dir, read_to_string, File};
|
||||
use std::hash::Hash;
|
||||
@@ -13,8 +13,11 @@ use crate::path::get_relative_path;
|
||||
|
||||
use super::{Package, Section};
|
||||
|
||||
/// A set of groups
|
||||
pub type Groups = BTreeSet<Group>;
|
||||
|
||||
/// Representation of a group file.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Group {
|
||||
/// Name of the group (file name from which it was read, relative to the group
|
||||
/// base dir).
|
||||
@@ -37,8 +40,8 @@ impl Group {
|
||||
///
|
||||
/// This function will return an error if any of the files under `group_dir` cannot
|
||||
/// be accessed.
|
||||
pub fn load(group_dir: &Path, warn_not_symlinks: bool) -> Result<HashSet<Self>> {
|
||||
let mut result = HashSet::new();
|
||||
pub fn load(group_dir: &Path, warn_not_symlinks: bool) -> Result<Groups> {
|
||||
let mut result = Groups::new();
|
||||
|
||||
if !group_dir.is_dir() {
|
||||
// we only need to create the innermost dir. The rest was already created from when
|
||||
|
||||
@@ -14,5 +14,6 @@ mod package;
|
||||
mod section;
|
||||
|
||||
pub use group::Group;
|
||||
pub use group::Groups;
|
||||
pub use package::Package;
|
||||
pub use section::Section;
|
||||
|
||||
@@ -7,7 +7,7 @@ use anyhow::{ensure, Context, Result};
|
||||
|
||||
use super::Package;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Section {
|
||||
pub name: String,
|
||||
pub packages: HashSet<Package>,
|
||||
|
||||
@@ -20,22 +20,25 @@
|
||||
missing_docs
|
||||
)]
|
||||
|
||||
mod args;
|
||||
pub(crate) mod backend;
|
||||
#[allow(missing_docs)]
|
||||
pub mod cli;
|
||||
|
||||
mod cmd;
|
||||
mod config;
|
||||
#[allow(clippy::unused_self, clippy::unnecessary_wraps)]
|
||||
mod core;
|
||||
mod env;
|
||||
mod errors;
|
||||
mod grouping;
|
||||
pub mod path;
|
||||
mod review;
|
||||
mod search;
|
||||
mod ui;
|
||||
|
||||
pub use crate::args::get as get_args;
|
||||
pub mod path;
|
||||
|
||||
pub use crate::config::Config;
|
||||
pub use crate::core::Pacdef;
|
||||
pub use crate::errors::Error;
|
||||
pub use crate::grouping::Group;
|
||||
pub use crate::grouping::Groups;
|
||||
pub use crate::grouping::Package;
|
||||
|
||||
@@ -19,8 +19,10 @@ use std::process::{ExitCode, Termination};
|
||||
|
||||
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::{get_args, Config, Error as PacdefError, Group, Pacdef};
|
||||
use pacdef::{Config, Error as PacdefError, Group};
|
||||
|
||||
const MAJOR_UPDATE_MESSAGE: &str = "VERSION UPGRADE
|
||||
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<()> {
|
||||
let args = get_args();
|
||||
let main_arguments = MainArguments::parse();
|
||||
|
||||
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)
|
||||
.with_context(|| format!("loading groups under {}", group_dir.to_string_lossy()))?;
|
||||
|
||||
let pacdef = Pacdef::new(args, config, groups);
|
||||
pacdef.run_action_from_arg().context("running action")
|
||||
for group in groups.iter() {
|
||||
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> {
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{backend::AnyBackend, Group, Package};
|
||||
|
||||
use super::strategy::Strategy;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(super) enum ReviewAction {
|
||||
pub enum ReviewAction {
|
||||
AsDependency(Package),
|
||||
Delete(Package),
|
||||
AssignGroup(Package, Rc<Group>),
|
||||
AssignGroup(Package, Group),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) enum ReviewIntention {
|
||||
pub enum ReviewIntention {
|
||||
AsDependency,
|
||||
AssignGroup,
|
||||
Delete,
|
||||
@@ -24,20 +22,20 @@ pub(super) enum ReviewIntention {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ReviewsPerBackend {
|
||||
pub struct ReviewsPerBackend {
|
||||
items: Vec<(AnyBackend, Vec<ReviewAction>)>,
|
||||
}
|
||||
|
||||
impl ReviewsPerBackend {
|
||||
pub(super) fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
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())
|
||||
}
|
||||
|
||||
pub(super) fn push(&mut self, value: (AnyBackend, Vec<ReviewAction>)) {
|
||||
pub fn push(&mut self, value: (AnyBackend, Vec<ReviewAction>)) {
|
||||
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
|
||||
/// value.
|
||||
pub(super) fn into_strategies(self) -> Vec<Strategy> {
|
||||
pub fn into_strategies(self) -> Vec<Strategy> {
|
||||
let mut result = vec![];
|
||||
|
||||
for (backend, actions) in self {
|
||||
@@ -85,7 +83,7 @@ impl IntoIterator for ReviewsPerBackend {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) enum ContinueWithReview {
|
||||
pub enum ContinueWithReview {
|
||||
Yes,
|
||||
No,
|
||||
NoAndApply,
|
||||
@@ -94,7 +92,7 @@ pub(super) enum ContinueWithReview {
|
||||
fn extract_actions(
|
||||
actions: Vec<ReviewAction>,
|
||||
to_delete: &mut Vec<Package>,
|
||||
assign_group: &mut Vec<(Package, Rc<Group>)>,
|
||||
assign_group: &mut Vec<(Package, Group)>,
|
||||
as_dependency: &mut Vec<Package>,
|
||||
) {
|
||||
for action in actions {
|
||||
|
||||
@@ -2,26 +2,19 @@ mod datastructures;
|
||||
mod strategy;
|
||||
|
||||
use std::io::{stdin, stdout, Write};
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::backend::backend_trait::Backend;
|
||||
use crate::backend::todo_per_backend::ToDoPerBackend;
|
||||
use crate::ui::{get_user_confirmation, read_single_char_from_terminal};
|
||||
use crate::{Group, Package};
|
||||
use crate::{Group, Groups, Package};
|
||||
|
||||
use self::datastructures::{ContinueWithReview, ReviewAction, ReviewIntention, ReviewsPerBackend};
|
||||
use self::strategy::Strategy;
|
||||
|
||||
pub fn review(
|
||||
todo_per_backend: ToDoPerBackend,
|
||||
groups: impl IntoIterator<Item = Group>,
|
||||
) -> Result<()> {
|
||||
pub fn review(todo_per_backend: ToDoPerBackend, groups: &Groups) -> Result<()> {
|
||||
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() {
|
||||
println!("nothing to do");
|
||||
@@ -32,7 +25,7 @@ pub fn review(
|
||||
let mut actions = vec![];
|
||||
for package in packages {
|
||||
println!("{}: {package}", backend.get_section());
|
||||
match get_action_for_package(package, &groups, &mut actions, &backend)? {
|
||||
match get_action_for_package(package, groups, &mut actions, &backend)? {
|
||||
ContinueWithReview::Yes => continue,
|
||||
ContinueWithReview::No => return Ok(()),
|
||||
ContinueWithReview::NoAndApply => {
|
||||
@@ -76,7 +69,7 @@ pub fn review(
|
||||
|
||||
fn get_action_for_package(
|
||||
package: Package,
|
||||
groups: &[Rc<Group>],
|
||||
groups: &Groups,
|
||||
reviews: &mut Vec<ReviewAction>,
|
||||
backend: &dyn Backend,
|
||||
) -> Result<ContinueWithReview> {
|
||||
@@ -92,7 +85,7 @@ fn get_action_for_package(
|
||||
}
|
||||
ReviewIntention::AssignGroup => {
|
||||
if let Ok(Some(group)) = ask_group(groups) {
|
||||
reviews.push(ReviewAction::AssignGroup(package, group));
|
||||
reviews.push(ReviewAction::AssignGroup(package, group.clone()));
|
||||
break;
|
||||
};
|
||||
}
|
||||
@@ -154,7 +147,7 @@ fn print_query(supports_as_dependency: bool) -> Result<()> {
|
||||
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());
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
fn ask_group(groups: &[Rc<Group>]) -> Result<Option<Rc<Group>>> {
|
||||
fn ask_group(groups: &Groups) -> Result<Option<&Group>> {
|
||||
print_enumerated_groups(groups);
|
||||
let mut buf = String::new();
|
||||
stdin().read_line(&mut buf)?;
|
||||
@@ -179,7 +172,7 @@ fn ask_group(groups: &[Rc<Group>]) -> Result<Option<Rc<Group>>> {
|
||||
};
|
||||
|
||||
if idx < groups.len() {
|
||||
Ok(Some(groups[idx].clone()))
|
||||
Ok(groups.iter().nth(idx))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
@@ -8,19 +6,19 @@ use crate::{
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct Strategy {
|
||||
pub struct Strategy {
|
||||
backend: AnyBackend,
|
||||
delete: Vec<Package>,
|
||||
as_dependency: Vec<Package>,
|
||||
assign_group: Vec<(Package, Rc<Group>)>,
|
||||
assign_group: Vec<(Package, Group)>,
|
||||
}
|
||||
|
||||
impl Strategy {
|
||||
pub(super) fn new(
|
||||
pub fn new(
|
||||
backend: AnyBackend,
|
||||
delete: Vec<Package>,
|
||||
as_dependency: Vec<Package>,
|
||||
assign_group: Vec<(Package, Rc<Group>)>,
|
||||
assign_group: Vec<(Package, Group)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
backend,
|
||||
@@ -30,7 +28,7 @@ impl Strategy {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn execute(self) -> Result<()> {
|
||||
pub fn execute(self) -> Result<()> {
|
||||
if !self.delete.is_empty() {
|
||||
self.backend.remove_packages(&self.delete, false)?;
|
||||
}
|
||||
@@ -46,7 +44,7 @@ impl Strategy {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn show(&self) {
|
||||
pub fn show(&self) {
|
||||
if self.nothing_to_do() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::collections::HashSet;
|
||||
use std::iter::Peekable;
|
||||
use std::vec::IntoIter;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
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
|
||||
/// 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
|
||||
/// - an invalid regex was provided, or
|
||||
/// - 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() {
|
||||
eprintln!("WARNING: no group files found");
|
||||
bail!(crate::errors::Error::NoPackagesFound);
|
||||
|
||||
Reference in New Issue
Block a user