1use std::path::{Path, PathBuf};
2
3pub const HARN_STATE_DIR_ENV: &str = "HARN_STATE_DIR";
4pub const HARN_RUN_DIR_ENV: &str = "HARN_RUN_DIR";
5pub const HARN_WORKTREE_DIR_ENV: &str = "HARN_WORKTREE_DIR";
6
7fn resolve_root(base_dir: &Path, env_key: &str, default_relative: &str) -> PathBuf {
8 match std::env::var(env_key) {
9 Ok(value) if !value.trim().is_empty() => {
10 let candidate = PathBuf::from(value);
11 if candidate.is_absolute() {
12 candidate
13 } else {
14 base_dir.join(candidate)
15 }
16 }
17 _ => base_dir.join(default_relative),
18 }
19}
20
21pub fn state_root(base_dir: &Path) -> PathBuf {
22 resolve_root(base_dir, HARN_STATE_DIR_ENV, ".harn")
23}
24
25pub fn run_root(base_dir: &Path) -> PathBuf {
26 resolve_root(base_dir, HARN_RUN_DIR_ENV, ".harn-runs")
27}
28
29pub fn worktree_root(base_dir: &Path) -> PathBuf {
30 match std::env::var(HARN_WORKTREE_DIR_ENV) {
31 Ok(value) if !value.trim().is_empty() => {
32 let candidate = PathBuf::from(value);
33 if candidate.is_absolute() {
34 candidate
35 } else {
36 base_dir.join(candidate)
37 }
38 }
39 _ => state_root(base_dir).join("worktrees"),
40 }
41}
42
43pub fn store_path(base_dir: &Path) -> PathBuf {
44 state_root(base_dir).join("store.json")
45}
46
47pub fn checkpoint_dir(base_dir: &Path) -> PathBuf {
48 state_root(base_dir).join("checkpoints")
49}
50
51pub fn metadata_dir(base_dir: &Path) -> PathBuf {
52 state_root(base_dir).join("metadata")
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn defaults_resolve_under_base_dir() {
61 let base = Path::new("/tmp/harn-runtime-paths");
62 assert_eq!(state_root(base), base.join(".harn"));
63 assert_eq!(run_root(base), base.join(".harn-runs"));
64 assert_eq!(worktree_root(base), base.join(".harn").join("worktrees"));
65 }
66}