use directories::ProjectDirs;
use std::ffi::OsString;
use std::path::PathBuf;
pub(crate) const CONFIG_DIR_ENV: &str = "BACNET_CONFIG_DIR";
pub(crate) fn project_dirs() -> Option<ProjectDirs> {
ProjectDirs::from("", "", "bacnet")
}
pub(crate) fn config_root(
env_override: Option<OsString>,
dirs: Option<&ProjectDirs>,
) -> Result<PathBuf, String> {
if let Some(root) = env_override {
if root.is_empty() {
return Err(format!("{CONFIG_DIR_ENV} must not be empty"));
}
return Ok(PathBuf::from(root));
}
dirs.map(|dirs| dirs.config_dir().to_owned())
.ok_or_else(|| format!("cannot locate the config directory; set {CONFIG_DIR_ENV}"))
}
pub(crate) fn discover_config_root() -> Result<PathBuf, String> {
config_root(std::env::var_os(CONFIG_DIR_ENV), project_dirs().as_ref())
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
fn dirs() -> ProjectDirs {
project_dirs().expect("CI runners have a home directory")
}
#[test]
fn override_wins_and_must_not_be_empty() {
let root = config_root(Some("/tmp/site-a".into()), Some(&dirs())).unwrap();
assert_eq!(root, Path::new("/tmp/site-a"));
assert_eq!(
config_root(Some("relative".into()), None).unwrap(),
Path::new("relative")
);
assert!(config_root(Some(OsString::new()), Some(&dirs()))
.unwrap_err()
.contains(CONFIG_DIR_ENV));
assert!(config_root(None, None)
.unwrap_err()
.contains(CONFIG_DIR_ENV));
}
#[test]
fn default_config_root_is_the_platform_config_dir() {
let dirs = dirs();
assert_eq!(config_root(None, Some(&dirs)).unwrap(), dirs.config_dir());
}
#[cfg(target_os = "linux")]
#[test]
fn linux_uses_the_xdg_config_dir() {
let dirs = dirs();
let home = std::env::var_os("HOME").map(PathBuf::from).unwrap();
let config = std::env::var_os("XDG_CONFIG_HOME")
.filter(|v| Path::new(v).is_absolute())
.map_or_else(|| home.join(".config"), PathBuf::from);
assert_eq!(
config_root(None, Some(&dirs)).unwrap(),
config.join("bacnet")
);
}
#[cfg(target_os = "macos")]
#[test]
fn macos_uses_application_support() {
let dirs = dirs();
let home = std::env::var_os("HOME").map(PathBuf::from).unwrap();
let support = home.join("Library/Application Support/bacnet");
assert_eq!(config_root(None, Some(&dirs)).unwrap(), support);
}
#[cfg(windows)]
#[test]
fn windows_uses_roaming_app_data_not_home() {
let dirs = dirs();
let roaming = std::env::var_os("APPDATA").map(PathBuf::from).unwrap();
assert_eq!(
config_root(None, Some(&dirs)).unwrap(),
roaming.join("bacnet").join("config")
);
}
}