diff --git a/paxy/src/app/config.rs b/paxy/src/app/config.rs index f4144b1..0a59e0c 100644 --- a/paxy/src/app/config.rs +++ b/paxy/src/app/config.rs @@ -1,58 +1,57 @@ -pub fn init_config() -> Result<(Config, Vec), Error> { - let xdg_app_dirs = - directories::BaseDirs::new().context(RetreiveConfigUserAppBaseDirectoriesSnafu {})?; - - #[cfg(target_os = "linux")] - let candidate_config_filepath_stubs = [ - format!("/etc/xdg/{}", *app::APP_NAME), - format!("/etc/{}", *app::APP_NAME), - format!( - "{}/{}", - xdg_app_dirs - .config_dir() - .to_string_lossy(), - *app::APP_NAME - ), - ]; - - #[cfg(any(target_os = "windows", target_os = "macos"))] - let candidate_config_filepath_stubs = [format!( - "{}/{}", - xdg_app_dirs +/// Initializes a layered configuration deriving values from the app-wide +/// defaults, overridden by values from global paths, overridden by values from +/// local paths. overridden by environment variables starting with `PAXY_`, +/// overridden by the configuration file specified by the commandline. +/// Values from only files with supported file extensions would be merged. +pub fn init_config(config_filepath: Option<&Path>) -> Result<(Config, Vec), Error> { + let mut candidate_config_filepath_stubs: Vec = Vec::new(); + + // Global directories + #[cfg(target_family = "unix")] + candidate_config_filepath_stubs.extend(["/etc/xdg".into(), "/etc".into()]); + #[cfg(target_os = "windows")] + candidate_config_filepath_stubs.extend([""]); + + // Local directories + candidate_config_filepath_stubs.push( + directories::BaseDirs::new() + .context(RetreiveConfigUserAppBaseDirectoriesSnafu {})? .config_dir() - .to_string_lossy(), - *app::APP_NAME - )]; + .to_path_buf(), + ); + // Append filename to create filepath stubs + candidate_config_filepath_stubs + .iter_mut() + .for_each(|f| f.push(*app::APP_NAME)); + // Initialize configuration with app-wide defaults let mut figment = Figment::from(Config::default()); + // Merge configuration values from global and local filepaths figment = candidate_config_filepath_stubs .iter() .fold( figment, - move |mut figment, candidate_config_filepath_stub| { - figment = figment.admerge(Toml::file(format!( - "{}.toml", - candidate_config_filepath_stub - ))); - figment = figment.admerge(Json::file(format!( - "{}.json", - candidate_config_filepath_stub - ))); - figment = figment.admerge(Yaml::file(format!( - "{}.yml", - candidate_config_filepath_stub - ))); - figment = figment.admerge(Yaml::file(format!( - "{}.yaml", - candidate_config_filepath_stub - ))); - figment + move |figment, candidate_config_filepath_stub| { + admerge_from_stub(candidate_config_filepath_stub, figment) }, ); - figment = figment.admerge(Env::prefixed("PAXY_")); + // Merge configuration values from environment variables + figment = figment.admerge(Env::prefixed(&format!("{}_", *app::APP_NAME))); + + // Merge configuration values from config filepath specified at the CLI + if let Some(config_filepath) = config_filepath { + if let Some(parent) = config_filepath.parent() { + if let Some(stem) = config_filepath.file_stem() { + let mut stub = PathBuf::from(parent); + stub.push(stem); + figment = admerge_from_stub(&stub, figment); + candidate_config_filepath_stubs.push(stub); + } + } + } Ok(( figment @@ -65,6 +64,22 @@ pub fn init_config() -> Result<(Config, Vec), Error> { )) } +fn admerge_from_stub(candidate_config_filepath_stub: &PathBuf, mut figment: Figment) -> Figment { + figment = figment.admerge(Toml::file( + candidate_config_filepath_stub.with_extension("toml"), + )); + figment = figment.admerge(Json::file( + candidate_config_filepath_stub.with_extension("json"), + )); + figment = figment.admerge(Yaml::file( + candidate_config_filepath_stub.with_extension("yml"), + )); + figment = figment.admerge(Yaml::file( + candidate_config_filepath_stub.with_extension("yaml"), + )); + figment +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Config { pub log_directory: Option, @@ -124,7 +139,7 @@ pub enum Error { // region: IMPORTS -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use figment::{ providers::{Env, Format, Json, Toml, Yaml}, diff --git a/paxy/src/ui/mod.rs b/paxy/src/ui/mod.rs index 11437d9..d1d0756 100644 --- a/paxy/src/ui/mod.rs +++ b/paxy/src/ui/mod.rs @@ -4,14 +4,19 @@ where C: clap::Parser + CliModifier + fmt::Debug, ::L: LogLevel, { - // Obtain user configuration - let (config, config_filepaths) = config::init_config() - .context(app::ConfigSnafu {}) - .context(crate::AppSnafu)?; - // Obtain CLI arguments let cli_input = C::parse(); + // Obtain user configuration + let (config, config_filepaths) = config::init_config( + cli_input + .config_file() + .as_ref() + .map(|f| PathBuf::as_path(&f)), + ) + .context(app::ConfigSnafu {}) + .context(crate::AppSnafu)?; + // Turn off colors if needed let mut is_cli_uncolored = cli_input.is_uncolored(); if !is_cli_uncolored { @@ -126,7 +131,7 @@ where tracing::debug!( "{} {} {:?}", console::Emoji("📂", ""), - "Config Filepath(s):".magenta(), + "Config Filepath(s) (without file extensions):".magenta(), config_filepaths, ); tracing::debug!(