refact: overhaul arg parsing
This commit is contained in:
Generated
+12
-12
@@ -107,18 +107,18 @@ checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.2.7"
|
version = "4.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938"
|
checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.2.7"
|
version = "4.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd"
|
checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -129,9 +129,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
|
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codespan-reporting"
|
name = "codespan-reporting"
|
||||||
@@ -321,9 +321,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.3.7"
|
version = "0.3.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
|
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "numtoa"
|
name = "numtoa"
|
||||||
@@ -437,18 +437,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.8.1"
|
version = "1.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
|
checksum = "d1a59b5d8e97dee33696bf13c5ba8ab85341c002922fba050069326b9c498974"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.7.1"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-apt"
|
name = "rust-apt"
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ categories = ["command-line-utilities"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
clap = "4.2"
|
clap = "4.3"
|
||||||
const_format = { version = "0.2", default-features = false }
|
const_format = { version = "0.2", default-features = false }
|
||||||
path-absolutize = "3.0"
|
path-absolutize = "3.1"
|
||||||
regex = { version = "1.7", default-features = false, features = ["std"] }
|
regex = { version = "1.8", default-features = false, features = ["std"] }
|
||||||
termios = "0.3"
|
termios = "0.3"
|
||||||
walkdir = "2.3"
|
walkdir = "2.3"
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
use pacdef_macros::Action;
|
|
||||||
|
|
||||||
/// All main actions the program can perform. Variants of the enum relate to
|
|
||||||
/// the different subcommands.
|
|
||||||
#[derive(Debug, Action)]
|
|
||||||
pub enum Actions {
|
|
||||||
Clean,
|
|
||||||
Edit,
|
|
||||||
Import,
|
|
||||||
List,
|
|
||||||
New,
|
|
||||||
Remove,
|
|
||||||
Review,
|
|
||||||
Search,
|
|
||||||
Show,
|
|
||||||
Sync,
|
|
||||||
Unmanaged,
|
|
||||||
Version,
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,11 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
|
||||||
use path_absolutize::Absolutize;
|
|
||||||
|
|
||||||
use crate::action::*;
|
|
||||||
use crate::core::get_version_string;
|
use crate::core::get_version_string;
|
||||||
|
use clap::{Arg, ArgAction, Command};
|
||||||
|
|
||||||
/// Build the `pacdef` argument parser, with subcommands for `version`,
|
#[must_use]
|
||||||
/// `group` and `package`.
|
pub(super) fn build_cli() -> Command {
|
||||||
fn build_cli() -> Command {
|
|
||||||
let package_cmd = get_package_cmd();
|
let package_cmd = get_package_cmd();
|
||||||
let group_cmd = get_group_cmd();
|
let group_cmd = get_group_cmd();
|
||||||
let version_cmd = Command::new(VERSION).about("show version info");
|
let version_cmd = Command::new("version").about("show version info");
|
||||||
|
|
||||||
Command::new("pacdef")
|
Command::new("pacdef")
|
||||||
.about("multi-backend declarative package manager for Linux")
|
.about("multi-backend declarative package manager for Linux")
|
||||||
@@ -27,7 +20,7 @@ fn build_cli() -> Command {
|
|||||||
|
|
||||||
/// Build the `pacdef group` subcommand.
|
/// Build the `pacdef group` subcommand.
|
||||||
fn get_group_cmd() -> Command {
|
fn get_group_cmd() -> Command {
|
||||||
let edit = Command::new(EDIT)
|
let edit = Command::new("edit")
|
||||||
.about("edit one or more existing group")
|
.about("edit one or more existing group")
|
||||||
.arg_required_else_help(true)
|
.arg_required_else_help(true)
|
||||||
.arg(
|
.arg(
|
||||||
@@ -38,7 +31,7 @@ fn get_group_cmd() -> Command {
|
|||||||
)
|
)
|
||||||
.visible_alias("e");
|
.visible_alias("e");
|
||||||
|
|
||||||
let import = Command::new(IMPORT)
|
let import = Command::new("import")
|
||||||
.about("import one or more group files")
|
.about("import one or more group files")
|
||||||
.arg_required_else_help(true)
|
.arg_required_else_help(true)
|
||||||
.arg(
|
.arg(
|
||||||
@@ -49,11 +42,11 @@ fn get_group_cmd() -> Command {
|
|||||||
)
|
)
|
||||||
.visible_alias("i");
|
.visible_alias("i");
|
||||||
|
|
||||||
let list = Command::new(LIST)
|
let list = Command::new("list")
|
||||||
.about("list names of imported groups")
|
.about("list names of imported groups")
|
||||||
.visible_alias("l");
|
.visible_alias("l");
|
||||||
|
|
||||||
let new = Command::new(NEW)
|
let new = Command::new("new")
|
||||||
.about("create new group files")
|
.about("create new group files")
|
||||||
.arg_required_else_help(true)
|
.arg_required_else_help(true)
|
||||||
.arg(
|
.arg(
|
||||||
@@ -66,7 +59,7 @@ fn get_group_cmd() -> Command {
|
|||||||
.arg(Arg::new("groups").num_args(1..).required(true))
|
.arg(Arg::new("groups").num_args(1..).required(true))
|
||||||
.visible_alias("n");
|
.visible_alias("n");
|
||||||
|
|
||||||
let remove = Command::new(REMOVE)
|
let remove = Command::new("remove")
|
||||||
.about("remove one or more previously imported groups")
|
.about("remove one or more previously imported groups")
|
||||||
.arg_required_else_help(true)
|
.arg_required_else_help(true)
|
||||||
.arg(
|
.arg(
|
||||||
@@ -77,7 +70,7 @@ fn get_group_cmd() -> Command {
|
|||||||
)
|
)
|
||||||
.visible_alias("r");
|
.visible_alias("r");
|
||||||
|
|
||||||
let show = Command::new(SHOW)
|
let show = Command::new("show")
|
||||||
.about("show packages under an imported group")
|
.about("show packages under an imported group")
|
||||||
.arg_required_else_help(true)
|
.arg_required_else_help(true)
|
||||||
.arg(
|
.arg(
|
||||||
@@ -98,25 +91,25 @@ fn get_group_cmd() -> Command {
|
|||||||
|
|
||||||
/// Build the `pacdef package` subcommand.
|
/// Build the `pacdef package` subcommand.
|
||||||
fn get_package_cmd() -> Command {
|
fn get_package_cmd() -> Command {
|
||||||
let sync = Command::new(SYNC)
|
let sync = Command::new("sync")
|
||||||
.about("install packages from all imported groups")
|
.about("install packages from all imported groups")
|
||||||
.visible_alias("sy")
|
.visible_alias("sy")
|
||||||
.arg(build_noconfirm_arg());
|
.arg(build_noconfirm_arg());
|
||||||
|
|
||||||
let clean = Command::new(CLEAN)
|
let clean = Command::new("clean")
|
||||||
.about("remove unmanaged packages")
|
.about("remove unmanaged packages")
|
||||||
.visible_alias("c")
|
.visible_alias("c")
|
||||||
.arg(build_noconfirm_arg());
|
.arg(build_noconfirm_arg());
|
||||||
|
|
||||||
let unmanaged = Command::new(UNMANAGED)
|
let unmanaged = Command::new("unmanaged")
|
||||||
.about("show explicitly installed packages not managed by pacdef")
|
.about("show explicitly installed packages not managed by pacdef")
|
||||||
.visible_alias("u");
|
.visible_alias("u");
|
||||||
|
|
||||||
let review = Command::new(REVIEW)
|
let review = Command::new("review")
|
||||||
.about("review unmanaged packages")
|
.about("review unmanaged packages")
|
||||||
.visible_alias("r");
|
.visible_alias("r");
|
||||||
|
|
||||||
let search = Command::new(SEARCH)
|
let search = Command::new("search")
|
||||||
.visible_alias("se")
|
.visible_alias("se")
|
||||||
.about("search for packages which match a provided regex")
|
.about("search for packages which match a provided regex")
|
||||||
.arg_required_else_help(true)
|
.arg_required_else_help(true)
|
||||||
@@ -140,23 +133,3 @@ fn build_noconfirm_arg() -> Arg {
|
|||||||
.help("do not ask for any confirmation")
|
.help("do not ask for any confirmation")
|
||||||
.action(ArgAction::SetTrue)
|
.action(ArgAction::SetTrue)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get and parse the CLI arguments. Returns an instance of [`clap::ArgMatches`].
|
|
||||||
#[must_use]
|
|
||||||
pub fn get() -> clap::ArgMatches {
|
|
||||||
build_cli().get_matches()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// For each file argument, return the absolute path to the file.
|
|
||||||
pub fn get_absolutized_file_paths(arg_match: &ArgMatches) -> Result<Vec<PathBuf>> {
|
|
||||||
Ok(arg_match
|
|
||||||
.get_many::<String>("files")
|
|
||||||
.context("getting files from args")?
|
|
||||||
.map(PathBuf::from)
|
|
||||||
.map(|path| {
|
|
||||||
path.absolutize()
|
|
||||||
.expect("absolute path should exist")
|
|
||||||
.into_owned()
|
|
||||||
})
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Arguments {
|
||||||
|
Group(GroupAction),
|
||||||
|
Package(PackageAction),
|
||||||
|
Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum GroupAction {
|
||||||
|
Edit(Groups),
|
||||||
|
Import(Groups),
|
||||||
|
List,
|
||||||
|
New(Groups, Edit),
|
||||||
|
Remove(Groups),
|
||||||
|
Show(Groups),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Files(pub Vec<String>);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Groups(pub Vec<String>);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum PackageAction {
|
||||||
|
Clean(Noconfirm),
|
||||||
|
Review,
|
||||||
|
Search(Regex),
|
||||||
|
Sync(Noconfirm),
|
||||||
|
Unmanaged,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Regex(pub String);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Edit(pub bool);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Noconfirm(pub bool);
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
mod cli;
|
||||||
|
mod datastructure;
|
||||||
|
mod parsing;
|
||||||
|
|
||||||
|
pub use datastructure::*;
|
||||||
|
|
||||||
|
/// Get and parse the CLI arguments.
|
||||||
|
#[must_use]
|
||||||
|
pub fn get() -> Arguments {
|
||||||
|
let args = cli::build_cli().get_matches();
|
||||||
|
parsing::parse(args)
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
use super::datastructure::*;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
use GroupAction::*;
|
||||||
|
|
||||||
|
match args.subcommand() {
|
||||||
|
Some(("edit", args)) => Edit(get_groups(args)),
|
||||||
|
Some(("import", args)) => Import(get_groups(args)),
|
||||||
|
Some(("list", _)) => List,
|
||||||
|
Some(("new", args)) => New(get_groups(args), get_edit(args)),
|
||||||
|
Some(("remove", args)) => Remove(get_groups(args)),
|
||||||
|
Some(("show", args)) => Show(get_groups(args)),
|
||||||
|
Some(value) => panic!("group subcommand was not matched: {value:?}"),
|
||||||
|
None => unreachable!("prevented by clap"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_package_args(args: &clap::ArgMatches) -> PackageAction {
|
||||||
|
use PackageAction::*;
|
||||||
|
|
||||||
|
match args.subcommand() {
|
||||||
|
Some(("clean", args)) => Clean(get_noconfirm(args)),
|
||||||
|
Some(("review", _)) => Review,
|
||||||
|
Some(("search", args)) => Search(get_regex(args)),
|
||||||
|
Some(("sync", args)) => Sync(get_noconfirm(args)),
|
||||||
|
Some(("unmanaged", _)) => Unmanaged,
|
||||||
|
Some(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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,28 +4,24 @@ use std::os::unix::fs::symlink;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{bail, ensure, Context, Result};
|
use anyhow::{bail, ensure, Context, Result};
|
||||||
use clap::ArgMatches;
|
|
||||||
use const_format::formatcp;
|
use const_format::formatcp;
|
||||||
|
|
||||||
use crate::action::*;
|
use crate::args::{self, PackageAction};
|
||||||
use crate::args;
|
|
||||||
use crate::backend::{Backend, Backends, ToDoPerBackend};
|
use crate::backend::{Backend, Backends, ToDoPerBackend};
|
||||||
use crate::cmd::run_edit_command;
|
use crate::cmd::run_edit_command;
|
||||||
use crate::env::get_single_var;
|
use crate::env::get_single_var;
|
||||||
use crate::path::{binary_in_path, get_group_dir};
|
use crate::path::{binary_in_path, get_absolutized_file_paths, get_group_dir};
|
||||||
use crate::review;
|
use crate::review;
|
||||||
use crate::search;
|
use crate::search;
|
||||||
use crate::ui::get_user_confirmation;
|
use crate::ui::get_user_confirmation;
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
use crate::Group;
|
use crate::Group;
|
||||||
|
|
||||||
const UNREACHABLE_ARM: &str = "argument parser requires some subcommand to return an `ArgMatches`";
|
|
||||||
const ACTION_NOT_MATCHED: &str = "could not match action";
|
|
||||||
|
|
||||||
/// Most data that is required during runtime of the program.
|
/// Most data that is required during runtime of the program.
|
||||||
|
/// `args` is an `Option` so that we can take ownership later without cloning.
|
||||||
#[allow(dead_code)] // "`config` is only needed on Arch Linux"
|
#[allow(dead_code)] // "`config` is only needed on Arch Linux"
|
||||||
pub struct Pacdef {
|
pub struct Pacdef {
|
||||||
args: ArgMatches,
|
args: Option<args::Arguments>,
|
||||||
config: Config,
|
config: Config,
|
||||||
groups: HashSet<Group>,
|
groups: HashSet<Group>,
|
||||||
}
|
}
|
||||||
@@ -34,9 +30,9 @@ impl Pacdef {
|
|||||||
/// Creates a new [`Pacdef`]. `config` should be passed from [`Config::load`], and `args` from
|
/// Creates a new [`Pacdef`]. `config` should be passed from [`Config::load`], and `args` from
|
||||||
/// [`args::get`].
|
/// [`args::get`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn new(args: ArgMatches, config: Config, groups: HashSet<Group>) -> Self {
|
pub const fn new(args: args::Arguments, config: Config, groups: HashSet<Group>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
args,
|
args: Some(args),
|
||||||
config,
|
config,
|
||||||
groups,
|
groups,
|
||||||
}
|
}
|
||||||
@@ -57,37 +53,39 @@ impl Pacdef {
|
|||||||
/// This function propagates errors from the underlying functions.
|
/// This function propagates errors from the underlying functions.
|
||||||
#[allow(clippy::unit_arg)]
|
#[allow(clippy::unit_arg)]
|
||||||
pub fn run_action_from_arg(mut self) -> Result<()> {
|
pub fn run_action_from_arg(mut self) -> Result<()> {
|
||||||
match self.args.subcommand() {
|
match self
|
||||||
Some(("group", args)) => match args.subcommand() {
|
.args
|
||||||
Some((EDIT, args)) => self.edit_group_files(args).context("editing group files"),
|
.take()
|
||||||
Some((IMPORT, args)) => self.import_groups(args).context("importing groups"),
|
.expect("if there were no args we would not get to here")
|
||||||
Some((LIST, _)) => Ok(self.show_groups()),
|
{
|
||||||
Some((NEW, args)) => self.new_groups(args).context("creating new group files"),
|
args::Arguments::Group(group_args) => self.run_group_subcommand(&group_args),
|
||||||
Some((REMOVE, args)) => self.remove_groups(args).context("removing groups"),
|
args::Arguments::Package(package_args) => self.run_package_subcommand(&package_args),
|
||||||
Some((SHOW, args)) => self.show_group_content(args).context("showing groups"),
|
args::Arguments::Version => Ok(self.show_version()),
|
||||||
|
}
|
||||||
Some((_, _)) => panic!("{ACTION_NOT_MATCHED}"),
|
|
||||||
None => unreachable!("{UNREACHABLE_ARM}"),
|
|
||||||
},
|
|
||||||
|
|
||||||
Some(("package", args)) => match args.subcommand() {
|
|
||||||
Some((CLEAN, args)) => self.clean_packages(&args.clone()),
|
|
||||||
Some((REVIEW, _)) => review::review(self.get_unmanaged_packages()?, self.groups)
|
|
||||||
.context("review unmanaged packages"),
|
|
||||||
Some((SEARCH, args)) => {
|
|
||||||
search::search_packages(args, &self.groups).context("searching packages")
|
|
||||||
}
|
}
|
||||||
Some((SYNC, args)) => self.install_packages(&args.clone()), // TODO fix cloning
|
|
||||||
Some((UNMANAGED, _)) => self.show_unmanaged_packages(),
|
|
||||||
|
|
||||||
Some((_, _)) => panic!("{ACTION_NOT_MATCHED}"),
|
fn run_group_subcommand(self, args: &args::GroupAction) -> Result<()> {
|
||||||
None => unreachable!("{UNREACHABLE_ARM}"),
|
use args::GroupAction::*;
|
||||||
},
|
|
||||||
|
|
||||||
Some((VERSION, _)) => Ok(self.show_version()),
|
match args {
|
||||||
|
Edit(groups) => self.edit_groups(&groups.0),
|
||||||
|
Import(groups) => self.import_groups(&groups.0),
|
||||||
|
List => self.show_groups(),
|
||||||
|
New(groups, args::Edit(edit)) => self.new_groups(&groups.0, *edit),
|
||||||
|
Remove(groups) => self.remove_groups(&groups.0),
|
||||||
|
Show(groups) => self.show_group_content(&groups.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Some((_, _)) => panic!("{ACTION_NOT_MATCHED}"),
|
fn run_package_subcommand(mut self, args: &PackageAction) -> Result<()> {
|
||||||
None => unreachable!("{UNREACHABLE_ARM}"),
|
use args::PackageAction::*;
|
||||||
|
|
||||||
|
match args {
|
||||||
|
Clean(noconfirm) => self.clean_packages(noconfirm.0),
|
||||||
|
Review => review::review(self.get_unmanaged_packages()?, self.groups),
|
||||||
|
Search(regex) => search::search_packages(®ex.0, &self.groups),
|
||||||
|
Sync(noconfirm) => self.install_packages(noconfirm.0),
|
||||||
|
Unmanaged => self.show_unmanaged_packages(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +135,7 @@ impl Pacdef {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_packages(&mut self, args: &ArgMatches) -> Result<()> {
|
fn install_packages(&mut self, noconfirm: bool) -> Result<()> {
|
||||||
let to_install = self.get_missing_packages()?;
|
let to_install = self.get_missing_packages()?;
|
||||||
|
|
||||||
if to_install.nothing_to_do_for_all_backends() {
|
if to_install.nothing_to_do_for_all_backends() {
|
||||||
@@ -148,10 +146,6 @@ impl Pacdef {
|
|||||||
println!("Would install the following packages:\n");
|
println!("Would install the following packages:\n");
|
||||||
to_install.show().context("printing things to do")?;
|
to_install.show().context("printing things to do")?;
|
||||||
|
|
||||||
let noconfirm = *args
|
|
||||||
.get_one::<bool>("noconfirm")
|
|
||||||
.expect("has a default value");
|
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
if noconfirm {
|
if noconfirm {
|
||||||
println!("proceeding without confirmation");
|
println!("proceeding without confirmation");
|
||||||
@@ -162,8 +156,8 @@ impl Pacdef {
|
|||||||
to_install.install_missing_packages(noconfirm)
|
to_install.install_missing_packages(noconfirm)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edit_group_files(&self, arg_matches: &ArgMatches) -> Result<()> {
|
fn edit_groups(&self, groups: &[String]) -> Result<()> {
|
||||||
let group_files = get_group_file_paths_matching_args(arg_matches, &self.groups)
|
let group_files = get_group_file_paths_matching_args(groups, &self.groups)
|
||||||
.context("getting group files for args")?;
|
.context("getting group files for args")?;
|
||||||
|
|
||||||
let success = run_edit_command(&group_files)
|
let success = run_edit_command(&group_files)
|
||||||
@@ -225,15 +219,16 @@ impl Pacdef {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_groups(self) {
|
fn show_groups(self) -> Result<()> {
|
||||||
let mut vec: Vec<_> = self.groups.iter().collect();
|
let mut vec: Vec<_> = self.groups.iter().collect();
|
||||||
vec.sort_unstable();
|
vec.sort_unstable();
|
||||||
for g in vec {
|
for g in vec {
|
||||||
println!("{}", g.name);
|
println!("{}", g.name);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clean_packages(&mut self, args: &ArgMatches) -> Result<()> {
|
fn clean_packages(&mut self, noconfirm: bool) -> Result<()> {
|
||||||
let to_remove = self.get_unmanaged_packages()?;
|
let to_remove = self.get_unmanaged_packages()?;
|
||||||
|
|
||||||
if to_remove.nothing_to_do_for_all_backends() {
|
if to_remove.nothing_to_do_for_all_backends() {
|
||||||
@@ -244,10 +239,6 @@ impl Pacdef {
|
|||||||
println!("Would remove the following packages:\n");
|
println!("Would remove the following packages:\n");
|
||||||
to_remove.show().context("printing things to do")?;
|
to_remove.show().context("printing things to do")?;
|
||||||
|
|
||||||
let noconfirm = *args
|
|
||||||
.get_one::<bool>("noconfirm")
|
|
||||||
.expect("has a default value");
|
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
if noconfirm {
|
if noconfirm {
|
||||||
println!("proceeding without confirmation");
|
println!("proceeding without confirmation");
|
||||||
@@ -258,17 +249,12 @@ impl Pacdef {
|
|||||||
to_remove.remove_unmanaged_packages(noconfirm)
|
to_remove.remove_unmanaged_packages(noconfirm)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_group_content(&self, groups: &ArgMatches) -> Result<()> {
|
fn show_group_content(&self, args: &[String]) -> Result<()> {
|
||||||
let args: Vec<_> = groups
|
|
||||||
.get_many::<String>("groups")
|
|
||||||
.context("getting groups from args")?
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
let mut groups = vec![];
|
let mut groups = vec![];
|
||||||
|
|
||||||
// make sure all args exist before doing anything
|
// make sure all args exist before doing anything
|
||||||
for arg_group in &args {
|
for arg_group in args {
|
||||||
let group = self.groups.iter().find(|g| g.name == **arg_group);
|
let group = self.groups.iter().find(|g| g.name == **arg_group);
|
||||||
|
|
||||||
let group = match group {
|
let group = match group {
|
||||||
@@ -312,8 +298,8 @@ impl Pacdef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unused_self)]
|
#[allow(clippy::unused_self)]
|
||||||
fn import_groups(&self, args: &ArgMatches) -> Result<()> {
|
fn import_groups(&self, file_names: &[String]) -> Result<()> {
|
||||||
let files = args::get_absolutized_file_paths(args)?;
|
let files = get_absolutized_file_paths(file_names)?;
|
||||||
let groups_dir = get_group_dir()?;
|
let groups_dir = get_group_dir()?;
|
||||||
|
|
||||||
for target in files {
|
for target in files {
|
||||||
@@ -341,8 +327,8 @@ impl Pacdef {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_groups(&self, arg_match: &ArgMatches) -> Result<()> {
|
fn remove_groups(&self, groups: &[String]) -> Result<()> {
|
||||||
let paths = get_group_file_paths_matching_args(arg_match, &self.groups)?;
|
let paths = get_group_file_paths_matching_args(groups, &self.groups)?;
|
||||||
|
|
||||||
for file in paths {
|
for file in paths {
|
||||||
remove_file(file)?;
|
remove_file(file)?;
|
||||||
@@ -352,16 +338,11 @@ impl Pacdef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unused_self)]
|
#[allow(clippy::unused_self)]
|
||||||
fn new_groups(&self, arg_matches: &ArgMatches) -> Result<()> {
|
fn new_groups(&self, new_groups: &[String], edit: bool) -> Result<()> {
|
||||||
let group_path = get_group_dir()?;
|
let group_path = get_group_dir()?;
|
||||||
|
|
||||||
let new_group_names: Vec<_> = arg_matches
|
|
||||||
.get_many::<String>("groups")
|
|
||||||
.context("getting groups from args")?
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// prevent group names that resolve to directories
|
// prevent group names that resolve to directories
|
||||||
for name in &new_group_names {
|
for name in new_groups {
|
||||||
ensure!(
|
ensure!(
|
||||||
*name != ".",
|
*name != ".",
|
||||||
crate::Error::InvalidGroupName(".".to_string())
|
crate::Error::InvalidGroupName(".".to_string())
|
||||||
@@ -372,8 +353,8 @@ impl Pacdef {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let paths: Vec<_> = new_group_names
|
let paths: Vec<_> = new_groups
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|name| {
|
.map(|name| {
|
||||||
let mut base = group_path.clone();
|
let mut base = group_path.clone();
|
||||||
base.push(name);
|
base.push(name);
|
||||||
@@ -392,7 +373,7 @@ impl Pacdef {
|
|||||||
File::create(file)?;
|
File::create(file)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if arg_matches.get_flag("edit") {
|
if edit {
|
||||||
let success = run_edit_command(&paths)
|
let success = run_edit_command(&paths)
|
||||||
.context("running editor")?
|
.context("running editor")?
|
||||||
.success();
|
.success();
|
||||||
@@ -410,14 +391,9 @@ impl Pacdef {
|
|||||||
///
|
///
|
||||||
/// This function will return an error if any of the arguments do not match one of group names.
|
/// This function will return an error if any of the arguments do not match one of group names.
|
||||||
fn get_group_file_paths_matching_args<'a>(
|
fn get_group_file_paths_matching_args<'a>(
|
||||||
arg_match: &ArgMatches,
|
file_names: &[String],
|
||||||
groups: &'a HashSet<Group>,
|
groups: &'a HashSet<Group>,
|
||||||
) -> Result<Vec<&'a Path>> {
|
) -> Result<Vec<&'a Path>> {
|
||||||
let file_names: Vec<_> = arg_match
|
|
||||||
.get_many::<String>("groups")
|
|
||||||
.context("getting groups from args")?
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ This library contains all logic that happens in `pacdef` under the hood.
|
|||||||
missing_docs
|
missing_docs
|
||||||
)]
|
)]
|
||||||
|
|
||||||
mod action;
|
|
||||||
mod args;
|
mod args;
|
||||||
mod backend;
|
mod backend;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::path::PathBuf;
|
|||||||
use std::{env, path::Path};
|
use std::{env, path::Path};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use path_absolutize::Absolutize;
|
||||||
|
|
||||||
const CONFIG_FILE_NAME: &str = "pacdef.yaml";
|
const CONFIG_FILE_NAME: &str = "pacdef.yaml";
|
||||||
const CONFIG_FILE_NAME_OLD: &str = "pacdef.conf";
|
const CONFIG_FILE_NAME_OLD: &str = "pacdef.conf";
|
||||||
@@ -120,6 +121,19 @@ where
|
|||||||
relative_path
|
relative_path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For each file argument, return the absolute path to the file.
|
||||||
|
pub(crate) fn get_absolutized_file_paths(arg_match: &[String]) -> Result<Vec<PathBuf>> {
|
||||||
|
Ok(arg_match
|
||||||
|
.iter()
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.map(|path| {
|
||||||
|
path.absolutize()
|
||||||
|
.expect("absolute path should exist")
|
||||||
|
.into_owned()
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ use std::collections::HashSet;
|
|||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::vec::IntoIter;
|
use std::vec::IntoIter;
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Result};
|
||||||
use clap::ArgMatches;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::grouping::{Group, Package, Section};
|
use crate::grouping::{Group, Package, Section};
|
||||||
@@ -17,8 +16,8 @@ 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(args: &ArgMatches, groups: &HashSet<Group>) -> Result<()> {
|
pub fn search_packages(regex_str: &str, groups: &HashSet<Group>) -> Result<()> {
|
||||||
let re = get_regex_from_args(args)?;
|
let re = Regex::new(regex_str)?;
|
||||||
|
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
|
|
||||||
@@ -41,21 +40,6 @@ pub fn search_packages(args: &ArgMatches, groups: &HashSet<Group>) -> Result<()>
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the user-provided regex from the command-line args.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function will return an error if an invalid regex was provided.
|
|
||||||
fn get_regex_from_args(args: &ArgMatches) -> Result<Regex, anyhow::Error> {
|
|
||||||
let search_string = args
|
|
||||||
.get_one::<String>("regex")
|
|
||||||
.context("getting search string from arg")?;
|
|
||||||
|
|
||||||
let re = Regex::new(search_string)?;
|
|
||||||
|
|
||||||
Ok(re)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_triples(mut vec: Vec<(&Group, &Section, &Package)>) {
|
fn print_triples(mut vec: Vec<(&Group, &Section, &Package)>) {
|
||||||
vec.sort_unstable();
|
vec.sort_unstable();
|
||||||
|
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
use proc_macro::TokenStream;
|
|
||||||
use quote::quote;
|
|
||||||
use syn::{DeriveInput, __private::TokenStream2};
|
|
||||||
|
|
||||||
pub fn action(input: TokenStream) -> TokenStream {
|
|
||||||
let input = syn::parse::<DeriveInput>(input).expect("I don't know when this could fail");
|
|
||||||
|
|
||||||
let name = &input.ident;
|
|
||||||
|
|
||||||
let syn::Data::Enum(enum_data) = &input.data else {
|
|
||||||
panic!("`Register` can only be used on enums");
|
|
||||||
};
|
|
||||||
|
|
||||||
let variant_description = generate_variant_description(enum_data);
|
|
||||||
let variant_constants = generate_variant_constants(name, enum_data);
|
|
||||||
|
|
||||||
let expanded = compile_output(name, variant_description, variant_constants);
|
|
||||||
|
|
||||||
TokenStream::from(expanded)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_variant_description(
|
|
||||||
enum_data: &syn::DataEnum,
|
|
||||||
) -> impl Iterator<Item = TokenStream2> + '_ {
|
|
||||||
let variant_matches_backend = enum_data.variants.iter().map(|variant| {
|
|
||||||
let variant_name = &variant.ident;
|
|
||||||
let variant_lowercase =
|
|
||||||
proc_macro2::Literal::string(&variant_name.to_string().to_lowercase());
|
|
||||||
quote! {
|
|
||||||
Self::#variant_name => #variant_lowercase,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
variant_matches_backend
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_variant_constants<'a>(
|
|
||||||
name: &'a syn::Ident,
|
|
||||||
enum_data: &'a syn::DataEnum,
|
|
||||||
) -> impl Iterator<Item = TokenStream2> + 'a {
|
|
||||||
let result = enum_data.variants.iter().map(move |variant| {
|
|
||||||
let variant_name = &variant.ident;
|
|
||||||
|
|
||||||
let variant_uppercase = proc_macro2::Ident::new(
|
|
||||||
&variant_name.to_string().to_uppercase(),
|
|
||||||
proc_macro2::Span::call_site(),
|
|
||||||
);
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
pub const #variant_uppercase: &str = #name::#variant_name.name();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compile_output<T, U>(
|
|
||||||
name: &syn::Ident,
|
|
||||||
variant_description: T,
|
|
||||||
variant_constants: U,
|
|
||||||
) -> TokenStream2
|
|
||||||
where
|
|
||||||
T: Iterator<Item = TokenStream2>,
|
|
||||||
U: Iterator<Item = TokenStream2>,
|
|
||||||
{
|
|
||||||
let expanded = quote! {
|
|
||||||
impl #name {
|
|
||||||
/// Return the lowercase name of the enum variant as `&str`.
|
|
||||||
const fn name(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
#(#variant_description)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#(#variant_constants)*
|
|
||||||
};
|
|
||||||
expanded
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,6 @@ Procedural macros for `pacdef`.
|
|||||||
missing_docs
|
missing_docs
|
||||||
)]
|
)]
|
||||||
|
|
||||||
mod action;
|
|
||||||
mod register;
|
mod register;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
@@ -32,10 +31,3 @@ use proc_macro::TokenStream;
|
|||||||
pub fn register(input: TokenStream) -> TokenStream {
|
pub fn register(input: TokenStream) -> TokenStream {
|
||||||
register::register(input)
|
register::register(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Derive public constants from each variant of the enum, such that the name of the constant is
|
|
||||||
/// the name of the variant in all caps, and the value is the name of the variant in lowercase.
|
|
||||||
#[proc_macro_derive(Action)]
|
|
||||||
pub fn action(input: TokenStream) -> TokenStream {
|
|
||||||
action::action(input)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user