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 project_storage_dir(project_hash: &str) -> anyhow::Result<PathBuf> {
42 Ok(data_dir()?.join(project_hash))
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 #[test]
50 fn data_dir_returns_a_path_containing_task_journal() {
51 let p = data_dir().expect("data_dir");
52 let s = p.to_string_lossy();
53 assert!(s.contains("task-journal"), "got: {s}");
54 }
55
56 #[test]
57 fn project_dir_appends_subdir() {
58 let p = project_storage_dir("abc123").expect("project dir");
59 assert!(p.ends_with("abc123"), "got: {p:?}");
60 }
61
62 #[test]
67 #[cfg_attr(
68 not(unix),
69 ignore = "env semantics differ on Windows test runners; covered by integration tests"
70 )]
71 fn env_overrides_take_precedence() {
72 let prev_tjdd = std::env::var("TASK_JOURNAL_DATA_DIR").ok();
74 let prev_xdg = std::env::var("XDG_DATA_HOME").ok();
75
76 unsafe { std::env::remove_var("TASK_JOURNAL_DATA_DIR") };
78 unsafe { std::env::set_var("XDG_DATA_HOME", "/tmp/tj-paths-test-xdg") };
79 assert_eq!(
80 data_dir().unwrap(),
81 PathBuf::from("/tmp/tj-paths-test-xdg/task-journal")
82 );
83
84 unsafe { std::env::set_var("TASK_JOURNAL_DATA_DIR", "/tmp/tj-paths-test-explicit") };
85 assert_eq!(
86 data_dir().unwrap(),
87 PathBuf::from("/tmp/tj-paths-test-explicit")
88 );
89
90 unsafe { std::env::remove_var("TASK_JOURNAL_DATA_DIR") };
92 unsafe { std::env::remove_var("XDG_DATA_HOME") };
93 if let Some(v) = prev_tjdd {
94 unsafe { std::env::set_var("TASK_JOURNAL_DATA_DIR", v) };
95 }
96 if let Some(v) = prev_xdg {
97 unsafe { std::env::set_var("XDG_DATA_HOME", v) };
98 }
99 }
100}