elph_agent/utils/
paths.rs1use std::path::{Path, PathBuf};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct PathResolver {
8 pub home_env: &'static str,
10 pub data_env: &'static str,
12 pub project_env: &'static str,
14 pub config_dir_name: &'static str,
16 pub data_dir_name: &'static str,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct ResolvedPaths {
23 pub config_dir: PathBuf,
24 pub data_dir: PathBuf,
25 pub project_dir: PathBuf,
26}
27
28impl PathResolver {
29 pub fn resolve(&self) -> std::io::Result<ResolvedPaths> {
30 Ok(ResolvedPaths::from_dirs(
31 self.config_dir()?,
32 self.data_dir()?,
33 self.project_dir()?,
34 ))
35 }
36
37 fn config_dir(&self) -> std::io::Result<PathBuf> {
38 if let Some(path) = env_path(self.home_env) {
39 return Ok(path);
40 }
41
42 Ok(user_home()?.join(self.config_dir_name))
43 }
44
45 fn data_dir(&self) -> std::io::Result<PathBuf> {
46 if let Some(path) = env_path(self.data_env) {
47 return Ok(path);
48 }
49
50 if let Ok(xdg) = std::env::var("XDG_DATA_HOME") {
51 let trimmed = xdg.trim();
52 if !trimmed.is_empty() {
53 return Ok(Path::new(trimmed).join(self.data_dir_name));
54 }
55 }
56
57 Ok(user_home()?.join(".local").join("share").join(self.data_dir_name))
58 }
59
60 fn project_dir(&self) -> std::io::Result<PathBuf> {
61 if let Some(path) = env_path(self.project_env) {
62 return Ok(path);
63 }
64
65 std::env::current_dir()
66 }
67}
68
69impl ResolvedPaths {
70 pub fn from_dirs(config_dir: PathBuf, data_dir: PathBuf, project_dir: PathBuf) -> Self {
71 Self {
72 config_dir,
73 data_dir,
74 project_dir,
75 }
76 }
77}
78
79fn env_path(name: &str) -> Option<PathBuf> {
80 let value = std::env::var(name).ok()?;
81 let trimmed = value.trim();
82 if trimmed.is_empty() {
83 None
84 } else {
85 Some(PathBuf::from(trimmed))
86 }
87}
88
89fn user_home() -> std::io::Result<PathBuf> {
90 std::env::var_os("HOME")
91 .map(PathBuf::from)
92 .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "HOME is not set"))
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 const TEST_RESOLVER: PathResolver = PathResolver {
100 home_env: "TEST_AGENT_HOME",
101 data_env: "TEST_AGENT_DATA",
102 project_env: "TEST_AGENT_PROJECT",
103 config_dir_name: ".test-agent",
104 data_dir_name: "test-agent",
105 };
106
107 #[test]
108 fn resolves_from_explicit_dirs() {
109 let paths = ResolvedPaths::from_dirs(PathBuf::from("/cfg"), PathBuf::from("/data"), PathBuf::from("/repo"));
110
111 assert_eq!(paths.config_dir, PathBuf::from("/cfg"));
112 assert_eq!(paths.data_dir, PathBuf::from("/data"));
113 assert_eq!(paths.project_dir, PathBuf::from("/repo"));
114 }
115
116 #[test]
117 fn resolver_exposes_static_names() {
118 assert_eq!(TEST_RESOLVER.config_dir_name, ".test-agent");
119 assert_eq!(TEST_RESOLVER.data_dir_name, "test-agent");
120 }
121}