use std::{env, path::PathBuf, sync::atomic::AtomicBool}; use directories::ProjectDirs; use dotenv::from_path; use thiserror::Error; const CONFIG_FILE_NAME: &str = ".env"; static CONFIG_INITIALIZED: AtomicBool = AtomicBool::new(false); #[derive(Debug, Error)] pub enum ConfigError { #[error("No config file {0} found in the following directories: {1:#?}")] ConfigFileNotFound(String, Vec), #[error("Failed to load config file: {0}")] LoadConfigError(#[from] dotenv::Error), #[error("Environment variable error: {0}")] EnvVarError(#[from] std::env::VarError), } pub fn get_config_dirs() -> Vec { let mut config_dirs = vec![ PathBuf::from(""), // Current directory ]; if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { config_dirs.push(PathBuf::from(manifest_dir)); } if let Some(proj_dirs) = ProjectDirs::from("org", "P4pillon", "Krys4lide") { config_dirs.push(proj_dirs.config_dir().to_path_buf()); } config_dirs } pub fn get_config_files() -> Result, ConfigError> { let config_dirs = get_config_dirs(); let mut config_files = Vec::new(); for config_dir in config_dirs.iter() { let config_file = config_dir.join(CONFIG_FILE_NAME); if config_file.exists() { config_files.push(config_file); } } if config_files.is_empty() { return Err(ConfigError::ConfigFileNotFound( CONFIG_FILE_NAME.to_string(), config_dirs, )); } Ok(config_files) } pub fn load_config(force: Option) -> Result<(), ConfigError> { let force = force.unwrap_or(false); if CONFIG_INITIALIZED.load(std::sync::atomic::Ordering::Relaxed) && force { println!("DEBUG: Config already initialized, skipping"); return Ok(()); } let config_files = get_config_files()?; // Load the first config file found // TODO: add a verbose log to list all config files found println!( "DEBUG: Config files found (1st loaded): {:#?}", config_files ); from_path(config_files[0].as_path()).map_err(ConfigError::LoadConfigError)?; CONFIG_INITIALIZED.store(true, std::sync::atomic::Ordering::Relaxed); Ok(()) }