diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d286607..4d74868 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,5 @@ # Contributing -## General Steps - Thank you for considering to contribute to `pacdef`. The recommended workflow is this: @@ -10,8 +8,3 @@ this: 3. Fork the repository and implement your fix / feature. 4. Make sure your code generates no warnings, and passes `rustfmt` and `clippy`. 5. Open the pull request. - -## Rust-Analyzer Issues - -Rust Analyzer may not work unless both the `pacutils` and `apt` packages are -installed. On Arch that is, this may vary on other distros. diff --git a/Cargo.lock b/Cargo.lock index 17d9f25..896830e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,6 +74,22 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "assert_cmd" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -95,6 +111,17 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "cc" version = "1.1.21" @@ -240,6 +267,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "dirs" version = "5.0.1" @@ -261,6 +294,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "either" version = "1.11.0" @@ -362,6 +401,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "hostname" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +dependencies = [ + "cfg-if", + "libc", + "windows", +] + [[package]] name = "humantime" version = "2.1.0" @@ -490,16 +540,17 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" name = "pacdef" version = "1.6.0" dependencies = [ + "assert_cmd", "clap", "color-eyre", "derive_more", "dialoguer", "dirs", "home", + "hostname", "itertools", "libc", "log", - "path-absolutize", "pretty_env_logger", "regex", "serde", @@ -507,32 +558,40 @@ dependencies = [ "serde_json", "strum", "toml", - "walkdir", ] [[package]] -name = "path-absolutize" -version = "3.1.1" +name = "pin-project-lite" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5" -dependencies = [ - "path-dedot", -] +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] -name = "path-dedot" -version = "3.1.1" +name = "predicates" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ - "once_cell", + "anstyle", + "difflib", + "predicates-core", ] [[package]] -name = "pin-project-lite" -version = "0.2.14" +name = "predicates-core" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" + +[[package]] +name = "predicates-tree" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +dependencies = [ + "predicates-core", + "termtree", +] [[package]] name = "pretty_env_logger" @@ -633,15 +692,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "serde" version = "1.0.210" @@ -776,6 +826,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "thiserror" version = "1.0.63" @@ -918,13 +974,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] -name = "walkdir" -version = "2.5.0" +name = "wait-timeout" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ - "same-file", - "winapi-util", + "libc", ] [[package]] @@ -964,6 +1019,25 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index b15f8dd..a2a1a0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,9 +13,7 @@ rust-version = "1.78" [dependencies] color-eyre = "0.6.3" clap = { version = "4.5.18", features = ["derive"] } -path-absolutize = "3.1.1" regex = { version = "1.10.6", default-features = false, features = ["std"] } -walkdir = "2.5.0" libc = "0.2.159" log = { version = "0.4.22", features = ["std"] } serde = { version = "1.0.210", features = ["derive"] } @@ -29,8 +27,7 @@ pretty_env_logger = "0.5.0" dialoguer = "0.11.0" strum = { version = "0.26.3", features = ["derive"] } serde-inline-default = "0.2.1" +hostname = "0.4.0" -[profile.release] -lto = "off" -opt-level = "z" -strip = true +[dev-dependencies] +assert_cmd = "2.0.16" diff --git a/README.md b/README.md index ffb913f..6e48c70 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ can be set. The listed values are the defaults. ```toml aur_helper = "paru" # AUR helper to use on Arch Linux (paru, yay, ...) -aur_rm_args = [] # additional args to pass to AUR helper when removing packages (optional) +arch_rm_args = [] # additional args to pass to AUR helper when removing packages (optional) disabled_backends = [] # backends that pacdef should not manage, e.g. ["python"], this can reduce runtime if the package manager is notoriously slow (like pip) warn_not_symlinks = true # warn if a group file is not a symlink diff --git a/RELEASE-CHECKLIST.md b/RELEASE-CHECKLIST.md deleted file mode 100644 index a70a4ce..0000000 --- a/RELEASE-CHECKLIST.md +++ /dev/null @@ -1,13 +0,0 @@ -Release Checklist ------------------ -* Ensure local `main` is up to date with respect to `origin/main`. -* Run `cargo update` and review dependency updates. - Commit updated `Cargo.lock` with "chore(release): update lockfile". -* Run `cargo outdated` and review semver incompatible updates. - Unless there is a strong motivation otherwise, review and update every dependency. -* Update the date and version in all man pages: "chore(release): bump man pages". -* Run `cargo release -p pacdef `. - Verify everything works as expected. -* Rerun `cargo publish` with `--execute.` -* Generate GitHub release with `git cliff` -* Bump the AUR package. diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..5db77d0 --- /dev/null +++ b/config.toml @@ -0,0 +1,19 @@ +# Which arch package manager to use. For example: pacman, paru, yay, +# etc.. +arch_package_manager = "paru" + +# Extra arguments passed to pacman when removing an arch package. +arch_rm_args = [""] + +# Whether to install flatpak packages systemwide or for the current user. +flatpak_systemwide = true + +# Backends to disable from all pacdef behavior. See the README.md for +# the list of backend names +disabled_backends = ["apt"] + +# Which group files apply for which hostnames +[hostname_groups] +pc = ["example_group"] +laptop = ["example_group"] +server = ["example_group"] diff --git a/groups/example_group.toml b/groups/example_group.toml new file mode 100644 index 0000000..ed30c1f --- /dev/null +++ b/groups/example_group.toml @@ -0,0 +1,16 @@ +arch = ["pacdef", { package = "pacdef", optional_deps = ["git"] }] +cargo = [ + "pacdef", + { package = "pacdef", version = "0.1.0", git = "https://github.com/ripytide/pacdef", all_features = true, no_default_features = false, features = [ + "feature1", + ] }, +] +pipx = ["pacdef"] +apt = ["pacdef"] +xbps = ["pacdef"] +flatpak = ["pacdef"] +dnf = [ + "pacdef", + { package = "pacdef", repo = "/etc/yum.repos.d/fedora_extras.repo" }, +] +rustup = ["stable", { package = "stable", components = ["rust-analyzer"] }] diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index e69de29..0000000 diff --git a/src/backends/all.rs b/src/backends/all.rs index 457f63a..6f77699 100644 --- a/src/backends/all.rs +++ b/src/backends/all.rs @@ -113,7 +113,12 @@ macro_rules! query_infos { pub fn query_installed_packages(config: &Config) -> Result { Ok(Self { $( - $backend: $backend::query_installed_packages(config)?, + $backend: + if is_enabled(&$backend.to_string(), config) { + $backend::query_installed_packages(config)? + } else { + Default::default() + }, )* }) } @@ -138,7 +143,9 @@ macro_rules! install_options { pub fn install_packages(self, no_confirm: bool, config: &Config) -> Result<()> { $( - $backend::install_packages(&self.$backend, no_confirm, config)?; + if is_enabled(&$backend.to_string(), config) { + $backend::install_packages(&self.$backend, no_confirm, config)?; + } )* Ok(()) @@ -164,7 +171,9 @@ macro_rules! modification_options { pub fn modify_packages(self, config: &Config) -> Result<()> { $( - $backend::modify_packages(&self.$backend, config)?; + if is_enabled(&$backend.to_string(), config) { + $backend::modify_packages(&self.$backend, config)?; + } )* Ok(()) @@ -190,7 +199,9 @@ macro_rules! remove_options { pub fn remove_packages(self, no_confirm: bool, config: &Config) -> Result<()> { $( - $backend::remove_packages(&self.$backend, no_confirm, config)?; + if is_enabled(&$backend.to_string(), config) { + $backend::remove_packages(&self.$backend, no_confirm, config)?; + } )* Ok(()) @@ -199,3 +210,10 @@ macro_rules! remove_options { }; } apply_public_backends!(remove_options); + +fn is_enabled(backend: &str, config: &Config) -> bool { + !config + .disabled_backends + .iter() + .any(|x| x.to_lowercase() == backend.to_lowercase()) +} diff --git a/src/backends/arch.rs b/src/backends/arch.rs index 0d02644..ff4f5c5 100644 --- a/src/backends/arch.rs +++ b/src/backends/arch.rs @@ -8,11 +8,7 @@ use crate::cmd::{command_found, run_command, run_command_for_stdout}; use crate::prelude::*; #[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, derive_more::Display)] -pub struct Arch { - pub command: &'static str, -} - -pub type ArchPackageId = String; +pub struct Arch; #[derive(Debug, Clone)] pub struct ArchQueryInfo { @@ -28,25 +24,39 @@ pub struct ArchModificationOptions { #[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct ArchInstallOptions { #[serde_inline_default(ArchInstallOptions::default().optional_deps)] - pub optional_deps: Vec, + pub optional_deps: Vec, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct ArchRemoveOptions {} -impl Arch { - pub fn query_installed_packages( - &self, - _: &Config, - ) -> Result> { - if !command_found("pacman") { +impl Backend for Arch { + type PackageId = String; + type QueryInfo = ArchQueryInfo; + type InstallOptions = ArchInstallOptions; + type ModificationOptions = ArchModificationOptions; + type RemoveOptions = ArchRemoveOptions; + + fn query_installed_packages( + config: &Config, + ) -> Result> { + if !command_found(&config.arch_package_manager) { return Ok(BTreeMap::new()); } - let explicit = - run_command_for_stdout(["pacman", "--query", "--explicit", "--quiet"], Perms::Same)?; - let dependency = - run_command_for_stdout(["pacman", "--query", "--deps", "--quiet"], Perms::Same)?; + let explicit = run_command_for_stdout( + [ + &config.arch_package_manager, + "--query", + "--explicit", + "--quiet", + ], + Perms::Same, + )?; + let dependency = run_command_for_stdout( + [&config.arch_package_manager, "--query", "--deps", "--quiet"], + Perms::Same, + )?; Ok(dependency .lines() @@ -59,14 +69,13 @@ impl Arch { .collect()) } - pub fn install_packages( - &self, - packages: &BTreeMap, + fn install_packages( + packages: &BTreeMap, no_confirm: bool, - _: &Config, + config: &Config, ) -> Result<()> { run_command( - [self.command, "--sync"] + [&config.arch_package_manager, "--sync"] .into_iter() .chain(Some("--no_confirm").filter(|_| no_confirm)) .chain(packages.keys().map(String::as_str)) @@ -77,51 +86,48 @@ impl Arch { ) } - pub fn modify_packages( - &self, - packages: &BTreeMap, - _: &Config, + fn modify_packages( + packages: &BTreeMap, + config: &Config, ) -> Result<()> { run_command( - [self.command, "--database", "--asdeps"].into_iter().chain( - packages - .iter() - .filter(|(_, m)| m.make_implicit) - .map(|(p, _)| p.as_str()), - ), + [&config.arch_package_manager, "--database", "--asdeps"] + .into_iter() + .chain( + packages + .iter() + .filter(|(_, m)| m.make_implicit) + .map(|(p, _)| p.as_str()), + ), Perms::AsRoot, ) } - pub fn remove_packages( - &self, - packages: &BTreeMap, + fn remove_packages( + packages: &BTreeMap, no_confirm: bool, config: &Config, ) -> Result<()> { run_command( - [self.command, "--remove", "--recursive"] + [&config.arch_package_manager, "--remove", "--recursive"] .into_iter() - .chain(config.aur_rm_args.iter().map(String::as_str)) + .chain(config.arch_rm_args.iter().map(String::as_str)) .chain(Some("--no_confirm").filter(|_| no_confirm)) .chain(packages.keys().map(String::as_str)), Perms::AsRoot, ) } - pub fn try_parse_toml_package( - &self, + fn try_parse_toml_package( toml: &toml::Value, - ) -> Result<(ArchPackageId, ArchInstallOptions)> { + ) -> Result<(Self::PackageId, Self::InstallOptions)> { match toml { toml::Value::String(x) => Ok((x.to_string(), Default::default())), toml::Value::Table(x) => Ok(( x.clone().try_into::()?.package, x.clone().try_into()?, )), - _ => Err(eyre!( - "pacman/yay/paru packages must be either a string or a table" - )), + _ => Err(eyre!("arch packages must be either a string or a table")), } } } diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 6b3169d..a682403 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -4,13 +4,9 @@ pub mod arch; pub mod cargo; pub mod dnf; pub mod flatpak; -pub mod pacman; -pub mod paru; -pub mod pip; pub mod pipx; pub mod rustup; pub mod xbps; -pub mod yay; use std::collections::BTreeMap; @@ -20,7 +16,7 @@ use serde::{Deserialize, Serialize}; macro_rules! apply_public_backends { ($macro:ident) => { - $macro! { Apt, Cargo, Dnf, Flatpak, Pacman, Paru, Pip, Pipx, Rustup, Xbps, Yay } + $macro! { Arch, Apt, Cargo, Dnf, Flatpak, Pipx, Rustup, Xbps } }; } pub(crate) use apply_public_backends; diff --git a/src/backends/pacman.rs b/src/backends/pacman.rs deleted file mode 100644 index 6a79f0f..0000000 --- a/src/backends/pacman.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::prelude::*; -use color_eyre::Result; -use std::collections::BTreeMap; - -#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, derive_more::Display)] -pub struct Pacman; -impl Pacman { - const PACMAN: Arch = Arch { command: "pacman" }; -} - -impl Backend for Pacman { - type PackageId = ArchPackageId; - type QueryInfo = ArchQueryInfo; - type InstallOptions = ArchInstallOptions; - type ModificationOptions = ArchModificationOptions; - type RemoveOptions = ArchRemoveOptions; - - fn query_installed_packages( - config: &Config, - ) -> Result> { - Self::PACMAN.query_installed_packages(config) - } - fn install_packages( - packages: &BTreeMap, - no_confirm: bool, - config: &Config, - ) -> Result<()> { - Self::PACMAN.install_packages(packages, no_confirm, config) - } - fn modify_packages( - packages: &BTreeMap, - config: &Config, - ) -> Result<()> { - Self::PACMAN.modify_packages(packages, config) - } - fn remove_packages( - packages: &BTreeMap, - no_confirm: bool, - config: &Config, - ) -> Result<()> { - Self::PACMAN.remove_packages(packages, no_confirm, config) - } - fn try_parse_toml_package( - toml: &toml::Value, - ) -> Result<(Self::PackageId, Self::InstallOptions)> { - Self::PACMAN.try_parse_toml_package(toml) - } -} diff --git a/src/backends/paru.rs b/src/backends/paru.rs deleted file mode 100644 index 893353e..0000000 --- a/src/backends/paru.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::prelude::*; -use color_eyre::Result; -use std::collections::BTreeMap; - -#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, derive_more::Display)] -pub struct Paru; -impl Paru { - const PARU: Arch = Arch { command: "paru" }; -} - -impl Backend for Paru { - type PackageId = ArchPackageId; - type QueryInfo = ArchQueryInfo; - type InstallOptions = ArchInstallOptions; - type ModificationOptions = ArchModificationOptions; - type RemoveOptions = ArchRemoveOptions; - - fn query_installed_packages( - config: &Config, - ) -> Result> { - Self::PARU.query_installed_packages(config) - } - fn install_packages( - packages: &BTreeMap, - no_confirm: bool, - config: &Config, - ) -> Result<()> { - Self::PARU.install_packages(packages, no_confirm, config) - } - fn modify_packages( - packages: &BTreeMap, - config: &Config, - ) -> Result<()> { - Self::PARU.modify_packages(packages, config) - } - fn remove_packages( - packages: &BTreeMap, - no_confirm: bool, - config: &Config, - ) -> Result<()> { - Self::PARU.remove_packages(packages, no_confirm, config) - } - fn try_parse_toml_package( - toml: &toml::Value, - ) -> Result<(Self::PackageId, Self::InstallOptions)> { - Self::PARU.try_parse_toml_package(toml) - } -} diff --git a/src/backends/pip.rs b/src/backends/pip.rs deleted file mode 100644 index d2d43e1..0000000 --- a/src/backends/pip.rs +++ /dev/null @@ -1,103 +0,0 @@ -use color_eyre::eyre::eyre; -use color_eyre::Result; -use serde_json::Value; -use std::collections::BTreeMap; -use std::collections::BTreeSet; - -use crate::cmd::command_found; -use crate::cmd::run_command; -use crate::cmd::run_command_for_stdout; -use crate::prelude::*; - -#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, derive_more::Display)] -pub struct Pip; - -#[derive(Debug, Clone)] -pub struct PipQueryInfo { - pub explicit: bool, -} - -impl Backend for Pip { - type PackageId = String; - type QueryInfo = PipQueryInfo; - type InstallOptions = (); - type ModificationOptions = (); - type RemoveOptions = (); - - fn query_installed_packages(_: &Config) -> Result> { - if !command_found("pip") { - return Ok(BTreeMap::new()); - } - - let all = extract_package_names(run_command_for_stdout( - ["pip", "list", "--format", "json"], - Perms::Same, - )?)?; - let implicit = extract_package_names(run_command_for_stdout( - ["pip", "list", "--format", "json", "--not-required"], - Perms::Same, - )?)?; - - let explicit = all.difference(&implicit); - - Ok(implicit - .iter() - .map(|x| (x.to_string(), PipQueryInfo { explicit: false })) - .chain(explicit.map(|x| (x.to_string(), PipQueryInfo { explicit: true }))) - .collect()) - } - - fn install_packages( - packages: &BTreeMap, - _: bool, - _: &Config, - ) -> Result<()> { - run_command( - ["pip", "install"] - .into_iter() - .chain(packages.keys().map(String::as_str)), - Perms::AsRoot, - ) - } - - fn modify_packages( - _: &BTreeMap, - _: &Config, - ) -> Result<()> { - unimplemented!() - } - - fn remove_packages( - packages: &BTreeMap, - _: bool, - _: &Config, - ) -> Result<()> { - run_command( - ["pip", "uninstall"] - .into_iter() - .chain(packages.keys().map(String::as_str)), - Perms::AsRoot, - ) - } - - fn try_parse_toml_package( - toml: &toml::Value, - ) -> Result<(Self::PackageId, Self::InstallOptions)> { - match toml { - toml::Value::String(x) => Ok((x.to_string(), Default::default())), - _ => Err(eyre!("pip packages must be a string")), - } - } -} - -fn extract_package_names(stdout: String) -> Result> { - let value: Value = serde_json::from_str(&stdout)?; - - Ok(value - .as_array() - .ok_or(eyre!("getting inner json array"))? - .iter() - .map(|node| node["name"].as_str().expect("should always be a string")) - .map(String::from) - .collect()) -} diff --git a/src/backends/yay.rs b/src/backends/yay.rs deleted file mode 100644 index 106e946..0000000 --- a/src/backends/yay.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::prelude::*; -use color_eyre::Result; -use std::collections::BTreeMap; - -#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, derive_more::Display)] -pub struct Yay; -impl Yay { - const YAY: Arch = Arch { command: "yay" }; -} - -impl Backend for Yay { - type PackageId = ArchPackageId; - type QueryInfo = ArchQueryInfo; - type InstallOptions = ArchInstallOptions; - type ModificationOptions = ArchModificationOptions; - type RemoveOptions = ArchRemoveOptions; - - fn query_installed_packages( - config: &Config, - ) -> Result> { - Self::YAY.query_installed_packages(config) - } - fn install_packages( - packages: &BTreeMap, - no_confirm: bool, - config: &Config, - ) -> Result<()> { - Self::YAY.install_packages(packages, no_confirm, config) - } - fn modify_packages( - packages: &BTreeMap, - config: &Config, - ) -> Result<()> { - Self::YAY.modify_packages(packages, config) - } - fn remove_packages( - packages: &BTreeMap, - no_confirm: bool, - config: &Config, - ) -> Result<()> { - Self::YAY.remove_packages(packages, no_confirm, config) - } - fn try_parse_toml_package( - toml: &toml::Value, - ) -> Result<(Self::PackageId, Self::InstallOptions)> { - Self::YAY.try_parse_toml_package(toml) - } -} diff --git a/src/cli.rs b/src/cli.rs index 5ae8bc9..2225852 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,18 +1,24 @@ //! The clap declarative command line interface +use std::path::PathBuf; + use clap::{Args, Parser, Subcommand}; #[derive(Parser)] #[command( version, author, + about, arg_required_else_help(true), - subcommand_required(true), - disable_help_subcommand(true), - disable_version_flag(true) + subcommand_required(true) )] -/// multi-backend declarative package manager for Linux pub struct MainArguments { + #[arg(short = 'n', long)] + /// specify a different hostname + pub hostname: Option, + #[arg(short, long)] + /// specify a different config directory + pub config_dir: Option, #[command(subcommand)] pub subcommand: MainSubcommand, } @@ -23,14 +29,13 @@ pub enum MainSubcommand { Review(ReviewPackageAction), Sync(SyncPackageAction), Unmanaged(UnmanagedPackageAction), - Version(VersionArguments), } #[derive(Args)] #[command(visible_alias("c"))] /// remove unmanaged packages pub struct CleanPackageAction { - #[arg(long)] + #[arg(short, long)] /// do not ask for any confirmation pub no_confirm: bool, } @@ -41,10 +46,10 @@ pub struct CleanPackageAction { pub struct ReviewPackageAction {} #[derive(Args)] -#[command(visible_alias("sy"))] -/// install packages from all imported groups +#[command(visible_alias("s"))] +/// install packages from groups pub struct SyncPackageAction { - #[arg(long)] + #[arg(short, long)] /// do not ask for any confirmation pub no_confirm: bool, } @@ -53,6 +58,3 @@ pub struct SyncPackageAction { #[command(visible_alias("u"))] /// show explicitly installed packages not managed by pacdef pub struct UnmanagedPackageAction {} - -#[derive(Args)] -pub struct VersionArguments {} diff --git a/src/config.rs b/src/config.rs index 32e7587..1a6bcfa 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,12 +9,27 @@ use serde::{Deserialize, Serialize}; #[serde_inline_default] #[derive(Debug, Serialize, Deserialize)] pub struct Config { - #[serde(default)] - pub aur_rm_args: Vec, - #[serde_inline_default(true)] + #[serde_inline_default(Config::default().arch_package_manager)] + pub arch_package_manager: String, + #[serde_inline_default(Config::default().arch_rm_args)] + pub arch_rm_args: Vec, + #[serde_inline_default(Config::default().flatpak_systemwide)] pub flatpak_systemwide: bool, - #[serde(default)] - pub hostnames: BTreeMap>, + #[serde_inline_default(Config::default().disabled_backends)] + pub disabled_backends: Vec, + #[serde_inline_default(Config::default().hostname_groups)] + pub hostname_groups: BTreeMap>, +} +impl Default for Config { + fn default() -> Self { + Config { + arch_package_manager: "pacman".to_string(), + arch_rm_args: Vec::new(), + flatpak_systemwide: true, + disabled_backends: Vec::new(), + hostname_groups: BTreeMap::new(), + } + } } impl Config { diff --git a/src/core.rs b/src/core.rs index 43058fd..dc4ee60 100644 --- a/src/core.rs +++ b/src/core.rs @@ -6,22 +6,37 @@ use crate::prelude::*; use crate::review::review; impl MainArguments { - pub fn run(self, groups: &Groups, config: &Config) -> Result<()> { - match self.subcommand { - MainSubcommand::Clean(clean) => clean.run(groups, config), - MainSubcommand::Review(review) => review.run(groups, config), - MainSubcommand::Sync(sync) => sync.run(groups, config), - MainSubcommand::Unmanaged(unmanaged) => unmanaged.run(groups, config), - MainSubcommand::Version(version) => version.run(), - } - } -} + pub fn run(self) -> Result<()> { + let hostname = if let Some(x) = self.hostname { + x + } else { + hostname::get()? + .into_string() + .or(Err(eyre!("getting hostname")))? + }; + + let config_dir = if let Some(x) = self.config_dir { + x + } else { + dirs::config_dir() + .map(|path| path.join("pacdef/")) + .ok_or(eyre!("getting the default pacdef config directory"))? + }; -impl VersionArguments { - fn run(self) -> Result<()> { - println!("pacdef, version: {}\n", env!("CARGO_PKG_VERSION")); + let config = Config::load(&config_dir).wrap_err("loading config file")?; + let groups = + Groups::load(&config_dir, &hostname, &config).wrap_err("failed to load groups")?; - Ok(()) + if groups.is_empty() { + log::warn!("no group files found"); + } + + match self.subcommand { + MainSubcommand::Clean(clean) => clean.run(&groups, &config), + MainSubcommand::Review(review) => review.run(&groups, &config), + MainSubcommand::Sync(sync) => sync.run(&groups, &config), + MainSubcommand::Unmanaged(unmanaged) => unmanaged.run(&groups, &config), + } } } @@ -43,7 +58,7 @@ impl CleanPackageAction { .default(true) .show_default(true) .interact() - .wrap_err(eyre!("getting user confirmation"))? + .wrap_err("getting user confirmation")? { return Ok(()); } @@ -78,7 +93,7 @@ impl SyncPackageAction { .default(true) .show_default(true) .interact() - .wrap_err(eyre!("getting user confirmation"))? + .wrap_err("getting user confirmation")? { return Ok(()); } diff --git a/src/groups.rs b/src/groups.rs index 1a3cf53..3dc5f1c 100644 --- a/src/groups.rs +++ b/src/groups.rs @@ -1,10 +1,9 @@ use crate::prelude::*; use color_eyre::{ - eyre::{eyre, Context}, + eyre::{eyre, Context, ContextCompat}, Result, }; use toml::{Table, Value}; -use walkdir::{DirEntry, WalkDir}; use std::{collections::BTreeMap, fs::read_to_string, path::Path}; @@ -25,7 +24,7 @@ impl Groups { self.to_install_options().to_package_ids() } - pub fn load(group_dir: &Path) -> Result { + pub fn load(group_dir: &Path, hostname: &str, config: &Config) -> Result { let mut groups = Self::default(); let group_dir = group_dir.join("groups/"); @@ -35,28 +34,23 @@ impl Groups { )); } - let group_files: Vec = WalkDir::new(&group_dir) - .follow_links(true) - .into_iter() - .collect::>()?; - - for group_file in group_files.iter().filter(|path| path.path().is_file()) { - let group_name = group_file - .path() - .strip_prefix(&group_dir)? - .to_str() - .ok_or(eyre!("will not fail on linux"))? - .to_string(); + for group_name in config.hostname_groups.get(hostname).wrap_err(format!( + "no hostname entry in the hostname_groups config for the hostname: {hostname}" + ))? { + let mut group_file = group_dir.join(group_name); + group_file.set_extension("toml"); log::info!("parsing group file: {group_name}@{group_file:?}"); - let file_contents = read_to_string(group_file.path()).wrap_err("reading group file")?; + let file_contents = read_to_string(&group_file) + .wrap_err(format!("reading group file {group_name}@{group_file:?}"))?; - let install_options: InstallOptions = - parse_group_file(&group_name, &file_contents).wrap_err("parsing group file")?; + let install_options: InstallOptions = parse_group_file(group_name, &file_contents) + .wrap_err(format!("parsing group file {group_name}@{group_file:?}"))?; - groups.insert(group_name, install_options); + groups.insert(group_name.clone(), install_options); } + Ok(groups) } } diff --git a/src/main.rs b/src/main.rs index 00ea05e..6e8b4ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,31 +14,13 @@ missing_docs )] -use color_eyre::{ - eyre::{eyre, Context}, - Result, -}; - use clap::Parser; -use pacdef::{Config, Groups, MainArguments}; +use color_eyre::Result; +use pacdef::MainArguments; fn main() -> Result<()> { pretty_env_logger::init(); color_eyre::install()?; - let main_arguments = MainArguments::parse(); - - let pacdef_dir = dirs::config_dir() - .map(|path| path.join("pacdef/")) - .ok_or(eyre!("getting the pacdef config directory"))?; - let config = Config::load(&pacdef_dir).wrap_err(eyre!("loading config file"))?; - let groups = Groups::load(&pacdef_dir).wrap_err(eyre!("failed to load groups"))?; - - if groups.is_empty() { - log::warn!("no group files found"); - } - - main_arguments.run(&groups, &config)?; - - Ok(()) + MainArguments::parse().run() } diff --git a/src/prelude.rs b/src/prelude.rs index 18ae1b3..a1404ad 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -4,21 +4,16 @@ pub use crate::backends::all::{ pub(crate) use crate::backends::apply_public_backends; pub use crate::backends::apt::{Apt, AptModificationOptions, AptQueryInfo}; pub use crate::backends::arch::{ - Arch, ArchInstallOptions, ArchModificationOptions, ArchPackageId, ArchQueryInfo, - ArchRemoveOptions, + Arch, ArchInstallOptions, ArchModificationOptions, ArchQueryInfo, ArchRemoveOptions, }; pub use crate::backends::cargo::Cargo; pub use crate::backends::dnf::{Dnf, DnfInstallOptions, DnfQueryInfo}; pub use crate::backends::flatpak::{Flatpak, FlatpakQueryInfo}; -pub use crate::backends::pacman::Pacman; -pub use crate::backends::paru::Paru; -pub use crate::backends::pip::{Pip, PipQueryInfo}; pub use crate::backends::pipx::Pipx; pub use crate::backends::rustup::{ Rustup, RustupInstallOptions, RustupModificationOptions, RustupQueryInfo, }; pub use crate::backends::xbps::{Xbps, XbpsModificationOptions}; -pub use crate::backends::yay::Yay; pub use crate::backends::Backend; pub use crate::backends::StringPackageStruct; pub use crate::cli::CleanPackageAction; @@ -27,7 +22,6 @@ pub use crate::cli::MainSubcommand; pub use crate::cli::ReviewPackageAction; pub use crate::cli::SyncPackageAction; pub use crate::cli::UnmanagedPackageAction; -pub use crate::cli::VersionArguments; pub use crate::cmd::Perms; pub use crate::config::Config; pub use crate::groups::Groups; diff --git a/tests/cli_tests.rs b/tests/cli_tests.rs new file mode 100644 index 0000000..2dbaa7e --- /dev/null +++ b/tests/cli_tests.rs @@ -0,0 +1,9 @@ +use assert_cmd::{assert::OutputAssertExt, cargo::CommandCargoExt}; +use std::process::Command; + +#[test] +fn unmanaged() { + let mut cmd = Command::cargo_bin("pacdef").unwrap(); + cmd.args(["--hostname", "pc", "--config-dir", ".", "unmanaged"]); + cmd.assert().success(); +}