1use anyhow::Context;
2use std::path::PathBuf;
3
4pub fn data_dir() -> anyhow::Result<PathBuf> {
14 if let Ok(custom) = std::env::var("TASK_JOURNAL_DATA_DIR") {
15 if !custom.is_empty() {
16 return Ok(PathBuf::from(custom));
17 }
18 }
19 if let Ok(xdg) = std::env::var("XDG_DATA_HOME") {
20 if !xdg.is_empty() {
21 return Ok(PathBuf::from(xdg).join("task-journal"));
22 }
23 }
24 let dirs = directories::ProjectDirs::from("", "", "task-journal")
25 .context("could not resolve OS data directories")?;
26 Ok(dirs.data_local_dir().to_path_buf())
27}
28
29pub fn events_dir() -> anyhow::Result<PathBuf> {
30 Ok(data_dir()?.join("events"))
31}
32
33pub fn state_dir() -> anyhow::Result<PathBuf> {
34 Ok(data_dir()?.join("state"))
35}
36
37pub fn metrics_dir() -> anyhow::Result<PathBuf> {
38 Ok(data_dir()?.join("metrics"))
39}
40
41pub fn memory_db() -> anyhow::Result<PathBuf> {
44 Ok(data_dir()?.join("memory.sqlite"))
45}
46
47pub fn project_storage_dir(project_hash: &str) -> anyhow::Result<PathBuf> {
48 Ok(data_dir()?.join(project_hash))
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[test]
56 fn data_dir_returns_a_path_containing_task_journal() {
57 let p = data_dir().expect("data_dir");
58 let s = p.to_string_lossy();
59 assert!(s.contains("task-journal"), "got: {s}");
60 }
61
62 #[test]
63 fn project_dir_appends_subdir() {
64 let p = project_storage_dir("abc123").expect("project dir");
65 assert!(p.ends_with("abc123"), "got: {p:?}");
66 }
67
68 #[test]
73 #[cfg_attr(
74 not(unix),
75 ignore = "env semantics differ on Windows test runners; covered by integration tests"
76 )]
77 fn env_overrides_take_precedence() {
78 let prev_tjdd = std::env::var("TASK_JOURNAL_DATA_DIR").ok();
80 let prev_xdg = std::env::var("XDG_DATA_HOME").ok();
81
82 unsafe { std::env::remove_var("TASK_JOURNAL_DATA_DIR") };
84 unsafe { std::env::set_var("XDG_DATA_HOME", "/tmp/tj-paths-test-xdg") };
85 assert_eq!(
86 data_dir().unwrap(),
87 PathBuf::from("/tmp/tj-paths-test-xdg/task-journal")
88 );
89
90 unsafe { std::env::set_var("TASK_JOURNAL_DATA_DIR", "/tmp/tj-paths-test-explicit") };
91 assert_eq!(
92 data_dir().unwrap(),
93 PathBuf::from("/tmp/tj-paths-test-explicit")
94 );
95
96 unsafe { std::env::remove_var("TASK_JOURNAL_DATA_DIR") };
98 unsafe { std::env::remove_var("XDG_DATA_HOME") };
99 if let Some(v) = prev_tjdd {
100 unsafe { std::env::set_var("TASK_JOURNAL_DATA_DIR", v) };
101 }
102 if let Some(v) = prev_xdg {
103 unsafe { std::env::set_var("XDG_DATA_HOME", v) };
104 }
105 }
106}