use std::ffi::OsString;
use std::path::{Path, PathBuf};
use super::name::ProfileName;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConfigDir {
root: PathBuf,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("can't find a configuration directory: set HOME or XDG_CONFIG_HOME")]
pub struct NoConfigDir;
impl ConfigDir {
pub fn new(
xdg_config_home: Option<OsString>,
home: Option<PathBuf>,
) -> Result<Self, NoConfigDir> {
let base = xdg_config_home
.map(PathBuf::from)
.filter(|path| path.is_absolute())
.or_else(|| home.map(|home| home.join(".config")))
.ok_or(NoConfigDir)?;
Ok(Self {
root: base.join("gitaway"),
})
}
pub fn from_env() -> Result<Self, NoConfigDir> {
Self::new(std::env::var_os("XDG_CONFIG_HOME"), std::env::home_dir())
}
pub fn path(&self) -> &Path {
&self.root
}
pub fn config_file(&self) -> PathBuf {
self.root.join("config.toml")
}
pub fn profiles_dir(&self) -> PathBuf {
self.root.join("profiles")
}
pub fn profile_file(&self, name: &ProfileName) -> PathBuf {
self.profiles_dir().join(format!("{name}.toml"))
}
pub fn keys_dir(&self) -> PathBuf {
self.root.join("keys")
}
pub fn key_file(&self, name: &ProfileName) -> PathBuf {
self.keys_dir().join(format!("{name}.key"))
}
pub fn lock_file(&self, name: &ProfileName) -> PathBuf {
self.root.join("locks").join(format!("{name}.lock"))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn home() -> Option<PathBuf> {
Some(PathBuf::from("/home/alice"))
}
#[test]
fn absolute_xdg_config_home_wins() {
let custom = if cfg!(windows) {
r"C:\custom"
} else {
"/custom"
};
let dir = ConfigDir::new(Some(custom.into()), home()).unwrap();
assert_eq!(dir.path(), Path::new(custom).join("gitaway"));
}
#[test]
fn relative_or_empty_xdg_config_home_is_ignored() {
for xdg in ["relative/dir", ""] {
let dir = ConfigDir::new(Some(xdg.into()), home()).unwrap();
assert_eq!(
dir.path(),
Path::new("/home/alice/.config/gitaway"),
"{xdg:?}"
);
}
}
#[test]
fn no_home_and_no_xdg_is_an_error() {
assert_eq!(ConfigDir::new(None, None), Err(NoConfigDir));
assert_eq!(
ConfigDir::new(Some("relative".into()), None),
Err(NoConfigDir)
);
}
#[test]
fn profile_files_live_in_the_profiles_dir() {
let dir = ConfigDir::new(None, home()).unwrap();
let name = "office".parse().unwrap();
assert_eq!(
dir.profile_file(&name),
Path::new("/home/alice/.config/gitaway/profiles/office.toml")
);
assert_eq!(
dir.key_file(&name),
Path::new("/home/alice/.config/gitaway/keys/office.key")
);
}
}