1use std::ffi::OsString;
16use std::path::{Path, PathBuf};
17
18use thiserror::Error;
19
20const APP_DIR: &str = "openlogi";
22
23#[derive(Debug, Error)]
24pub enum PathsError {
25 #[error("could not resolve a home directory for the current user")]
26 HomeNotFound,
27}
28
29fn home() -> Result<PathBuf, PathsError> {
31 std::env::var_os("HOME")
32 .or_else(|| std::env::var_os("USERPROFILE"))
33 .filter(|h| !h.is_empty())
34 .map(PathBuf::from)
35 .ok_or(PathsError::HomeNotFound)
36}
37
38fn xdg_base(env_value: Option<OsString>, fallback: &[&str]) -> Result<PathBuf, PathsError> {
45 match env_value {
46 Some(v) if Path::new(&v).is_absolute() => Ok(PathBuf::from(v).join(APP_DIR)),
47 _ => {
48 let mut dir = home()?;
49 dir.extend(fallback);
50 dir.push(APP_DIR);
51 Ok(dir)
52 }
53 }
54}
55
56pub fn config_dir() -> Result<PathBuf, PathsError> {
60 xdg_base(std::env::var_os("XDG_CONFIG_HOME"), &[".config"])
61}
62
63pub fn config_path() -> Result<PathBuf, PathsError> {
65 Ok(config_dir()?.join("config.toml"))
66}
67
68pub fn data_dir() -> Result<PathBuf, PathsError> {
73 xdg_base(std::env::var_os("XDG_DATA_HOME"), &[".local", "share"])
74}
75
76fn runtime_base(env_value: Option<OsString>) -> Result<PathBuf, PathsError> {
81 match env_value {
82 Some(v) if Path::new(&v).is_absolute() => Ok(PathBuf::from(v).join(APP_DIR)),
83 _ => config_dir(),
84 }
85}
86
87pub fn runtime_dir() -> Result<PathBuf, PathsError> {
89 runtime_base(std::env::var_os("XDG_RUNTIME_DIR"))
90}
91
92pub fn agent_socket_path() -> Result<PathBuf, PathsError> {
95 Ok(runtime_dir()?.join("agent.sock"))
96}
97
98#[cfg(all(test, unix))]
99#[allow(clippy::expect_used, reason = "expect/unwrap are idiomatic in tests")]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn absolute_xdg_override_is_used_verbatim() {
105 let dir = xdg_base(Some("/tmp/xdg-config".into()), &[".config"])
106 .expect("absolute override needs no home dir");
107 assert_eq!(dir, PathBuf::from("/tmp/xdg-config/openlogi"));
108 }
109
110 #[test]
111 fn relative_xdg_value_is_ignored_per_spec() {
112 let dir = xdg_base(Some("relative/dir".into()), &[".config"]).expect("home dir resolves");
115 assert!(dir.ends_with("openlogi"));
116 assert!(!dir.to_string_lossy().contains("relative"));
117 }
118
119 #[test]
120 fn absolute_runtime_dir_is_used_verbatim() {
121 let dir = runtime_base(Some("/run/user/501".into())).expect("absolute override");
122 assert_eq!(dir, PathBuf::from("/run/user/501/openlogi"));
123 }
124
125 #[test]
126 fn relative_runtime_dir_falls_back_to_config() {
127 let dir = runtime_base(Some("relative/run".into())).expect("config dir resolves");
130 assert!(dir.ends_with("openlogi"));
131 assert!(!dir.to_string_lossy().contains("relative"));
132 }
133}