diff --git a/crates/pacdef_core/src/args.rs b/crates/pacdef_core/src/args.rs index 6db19f6..19bc7ad 100644 --- a/crates/pacdef_core/src/args.rs +++ b/crates/pacdef_core/src/args.rs @@ -67,6 +67,7 @@ fn get_arg_parser() -> Command { .subcommand(Command::new(VERSION).about("show version info")) } +/// Get and parse the CLI arguments. #[must_use] pub fn get() -> clap::ArgMatches { get_arg_parser().get_matches() diff --git a/crates/pacdef_core/src/backend/macros.rs b/crates/pacdef_core/src/backend/macros.rs index 5ca5715..a6fb42a 100644 --- a/crates/pacdef_core/src/backend/macros.rs +++ b/crates/pacdef_core/src/backend/macros.rs @@ -1,3 +1,5 @@ +/// Used to implement parts of the `Backend` trait that should not change between the actual +/// backends (boilerplate). #[macro_export] macro_rules! impl_backend_constants { () => { diff --git a/crates/pacdef_core/src/config.rs b/crates/pacdef_core/src/config.rs index 38ab5a2..0a84cba 100644 --- a/crates/pacdef_core/src/config.rs +++ b/crates/pacdef_core/src/config.rs @@ -1,30 +1,34 @@ use std::fs::{read_to_string, File}; use std::io::{ErrorKind, Write}; -use std::path::PathBuf; +use std::path::Path; use anyhow::{bail, Context, Result}; use serde_derive::{Deserialize, Serialize}; -use crate::path::get_pacdef_base_dir; - -const CONFIG_FILE_NAME: &str = "pacdef.yaml"; - +/// Config for the program, as listed in `$XDG_CONFIG_HOME/pacdef/pacdef.yaml`. #[derive(Debug, Serialize, Deserialize)] pub struct Config { + /// The AUR helper to use for Arch Linux. pub aur_helper: String, + /// Additional arguments to pass to `aur_helper` when removing a package. pub aur_rm_args: Option>, + /// Warn the user when a group is not a symlink. pub warn_not_symlinks: bool, } impl Config { - pub fn load() -> Result { - let file = get_config_file()?; - let from_file = read_to_string(&file); + /// Load the config from the associated file. If the config does not exist, create a default + /// config. + pub fn load(config_file: &Path) -> Result { + let from_file = read_to_string(config_file); if let Err(e) = from_file { if e.kind() == ErrorKind::NotFound { - println!("creating default config under {}", file.to_string_lossy()); - return Self::use_default_and_save_to(file); + println!( + "creating default config under {}", + config_file.to_string_lossy() + ); + return Self::use_default_and_save_to(config_file); } bail!("unexpected error occured: {e:?}"); } @@ -34,7 +38,7 @@ impl Config { serde_yaml::from_str(&content).context("parsing yaml config") } - fn use_default_and_save_to(file: PathBuf) -> Result { + fn use_default_and_save_to(file: &Path) -> Result { let result = Self::default(); let content = serde_yaml::to_string(&result).context("converting Config to yaml")?; @@ -45,12 +49,6 @@ impl Config { } } -fn get_config_file() -> Result { - let mut file = get_pacdef_base_dir().context("getting pacdef base dir for config file")?; - file.push(CONFIG_FILE_NAME); - Ok(file) -} - impl Default for Config { fn default() -> Self { Self { diff --git a/crates/pacdef_core/src/core.rs b/crates/pacdef_core/src/core.rs index 723a9fc..5db8e82 100644 --- a/crates/pacdef_core/src/core.rs +++ b/crates/pacdef_core/src/core.rs @@ -18,6 +18,7 @@ use crate::ui::get_user_confirmation; use crate::Config; use crate::Group; +/// Most data that is required during runtime of the program. pub struct Pacdef { args: ArgMatches, config: Config, @@ -25,6 +26,8 @@ pub struct Pacdef { } 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: ArgMatches, config: Config, groups: HashSet) -> Self { Self { @@ -34,6 +37,18 @@ impl Pacdef { } } + /// Run the action that was provided by the user as first argument. + /// + /// For convenience sake, all called functions take a `&self` argument, even if these are not + /// strictly required. + /// + /// # Panics + /// + /// Panics if the user passed an unexpected action. This means all fields from `crate::action::Action` must be matched in this function. + /// + /// # Errors + /// + /// This function will return an error if any of the underlying functions return an error. #[allow(clippy::unit_arg)] pub fn run_action_from_arg(mut self) -> Result<()> { match self.args.subcommand() { diff --git a/crates/pacdef_core/src/grouping/group.rs b/crates/pacdef_core/src/grouping/group.rs index 33bcf6c..37b9ed3 100644 --- a/crates/pacdef_core/src/grouping/group.rs +++ b/crates/pacdef_core/src/grouping/group.rs @@ -8,8 +8,8 @@ use anyhow::{Context, Result}; use super::{Package, Section}; -use crate::Config; - +/// Representation of a group file, composed of a name (file name from which it was read), the +/// sections in the file, and the absolute path of the original file. #[derive(Debug)] pub struct Group { pub(crate) name: String, @@ -18,15 +18,21 @@ pub struct Group { } impl Group { - pub fn load(config: &Config) -> Result> { + /// Load all group files from the pacdef group dir. If a group file is not a symlink and + /// `warn_not_symlinks` is true, a warning is printed. + /// + /// # Errors + /// + /// 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> { let mut result = HashSet::new(); - let path = crate::path::get_pacdef_group_dir().context("getting pacdef group dir")?; - for entry in path.read_dir().context("reading group dir")? { + for entry in group_dir.read_dir().context("reading group dir")? { let file = entry.context("getting group file")?; let path = file.path(); - if config.warn_not_symlinks && !path.is_symlink() { + if warn_not_symlinks && !path.is_symlink() { eprintln!( "WARNING: group file {} is not a symlink", path.to_string_lossy() diff --git a/crates/pacdef_core/src/lib.rs b/crates/pacdef_core/src/lib.rs index 6a48cd4..6fbcd42 100644 --- a/crates/pacdef_core/src/lib.rs +++ b/crates/pacdef_core/src/lib.rs @@ -6,11 +6,12 @@ clippy::unwrap_used, clippy::use_debug, clippy::use_self, - clippy::wildcard_dependencies + clippy::wildcard_dependencies, + missing_docs )] mod action; -pub mod args; +mod args; mod backend; mod cmd; mod config; @@ -22,10 +23,12 @@ mod review; mod search; mod ui; +pub use crate::args::get as get_args; pub use crate::config::Config; pub use crate::core::Pacdef; pub use crate::grouping::Group; pub(crate) use crate::grouping::Package; +pub use crate::path::{get_config_path, get_pacdef_group_dir}; pub use crate::search::NO_PACKAGES_FOUND; extern crate pacdef_macros; diff --git a/crates/pacdef_core/src/path.rs b/crates/pacdef_core/src/path.rs index 8d0d616..8c039ab 100644 --- a/crates/pacdef_core/src/path.rs +++ b/crates/pacdef_core/src/path.rs @@ -2,6 +2,10 @@ use std::{env, path::PathBuf}; use anyhow::{Context, Result}; +const CONFIG_FILE_NAME: &str = "pacdef.yaml"; + +/// Get the group directory where all group files are located. This is +/// `$XDG_CONFIG_HOME/pacdef/groups`, which defaults to `$HOME/.config/pacdef/groups`. pub fn get_pacdef_group_dir() -> Result { let mut result = get_pacdef_base_dir().context("getting pacdef base dir")?; result.push("groups"); @@ -27,3 +31,11 @@ fn get_xdg_config_home() -> Result { pub fn get_home_dir() -> Result { Ok(env::var("HOME").context("getting $HOME variable")?.into()) } + +/// Get the group directory where all group files are located. This is +/// `$XDG_CONFIG_HOME/pacdef/pacdef.yaml`. +pub fn get_config_path() -> Result { + let mut file = get_pacdef_base_dir().context("getting pacdef base dir for config file")?; + file.push(CONFIG_FILE_NAME); + Ok(file) +} diff --git a/crates/pacdef_core/src/search.rs b/crates/pacdef_core/src/search.rs index 17e94f5..f620b09 100644 --- a/crates/pacdef_core/src/search.rs +++ b/crates/pacdef_core/src/search.rs @@ -8,6 +8,7 @@ use regex::Regex; use crate::grouping::{Group, Package, Section}; +/// Error message provided when a package search yields no results. pub const NO_PACKAGES_FOUND: &str = "no packages matching query"; pub fn search_packages(args: &ArgMatches, groups: &HashSet) -> Result<()> { diff --git a/crates/pacdef_macros/src/lib.rs b/crates/pacdef_macros/src/lib.rs index dbf6439..f25b1cf 100644 --- a/crates/pacdef_macros/src/lib.rs +++ b/crates/pacdef_macros/src/lib.rs @@ -1,13 +1,29 @@ +#![warn( + clippy::as_conversions, + clippy::option_if_let_else, + clippy::redundant_pub_crate, + clippy::unused_self, + clippy::unwrap_used, + clippy::use_debug, + clippy::use_self, + clippy::wildcard_dependencies, + missing_docs +)] + mod action; mod register; use proc_macro::TokenStream; +/// Derive (1) an iterator over the variants, (2) imports for the individual backends, and (3) +/// instatiation code for each backend. #[proc_macro_derive(Register)] pub fn register(input: TokenStream) -> TokenStream { 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) diff --git a/crates/pacdef_main/main.rs b/crates/pacdef_main/main.rs index 94b8d1c..6cc8e62 100644 --- a/crates/pacdef_main/main.rs +++ b/crates/pacdef_main/main.rs @@ -3,7 +3,7 @@ use std::process::{ExitCode, Termination}; use anyhow::{Context, Result}; use pacdef_core as core; -use pacdef_core::{args, Config, Group, Pacdef}; +use pacdef_core::{get_args, get_config_path, get_pacdef_group_dir, Config, Group, Pacdef}; fn main() -> ExitCode { handle_final_result(main_inner()) @@ -27,9 +27,14 @@ fn handle_final_result(result: Result<()>) -> ExitCode { } fn main_inner() -> Result<()> { - let args = args::get(); - let config = Config::load().context("loading config file")?; - let groups = Group::load(&config).context("loading groups").unwrap(); + let args = get_args(); + + let config_file = get_config_path().context("getting config file")?; + let config = Config::load(&config_file).context("loading config file")?; + + let group_dir = get_pacdef_group_dir().context("resolving group dir")?; + let groups = Group::load(&group_dir, config.warn_not_symlinks).context("loading groups")?; + let pacdef = Pacdef::new(args, config, groups); pacdef.run_action_from_arg().context("running action") }