Skip to main content

edda_ledger/
paths.rs

1use std::path::{Path, PathBuf};
2
3/// All well-known paths under `.edda/`.
4#[derive(Debug, Clone)]
5pub struct EddaPaths {
6    pub root: PathBuf,
7    pub edda_dir: PathBuf,
8    pub ledger_dir: PathBuf,
9    pub ledger_db: PathBuf,
10    pub blobs_dir: PathBuf,
11    pub branches_dir: PathBuf,
12    pub drafts_dir: PathBuf,
13    pub lock_file: PathBuf,
14    pub config_json: PathBuf,
15    pub patterns_dir: PathBuf,
16    pub blob_meta_json: PathBuf,
17    pub tombstones_jsonl: PathBuf,
18    pub archive_dir: PathBuf,
19    pub archive_blobs_dir: PathBuf,
20}
21
22impl EddaPaths {
23    /// Derive all paths from a repo root. Pure computation, no I/O.
24    pub fn discover(repo_root: impl Into<PathBuf>) -> Self {
25        let root = repo_root.into();
26        let edda_dir = root.join(".edda");
27        let ledger_dir = edda_dir.join("ledger");
28        let archive_dir = edda_dir.join("archive");
29        Self {
30            ledger_db: edda_dir.join("ledger.db"),
31            blobs_dir: ledger_dir.join("blobs"),
32            blob_meta_json: ledger_dir.join("blob_meta.json"),
33            tombstones_jsonl: ledger_dir.join("tombstones.jsonl"),
34            branches_dir: edda_dir.join("branches"),
35            drafts_dir: edda_dir.join("drafts"),
36            lock_file: edda_dir.join("LOCK"),
37            config_json: edda_dir.join("config.json"),
38            patterns_dir: edda_dir.join("patterns"),
39            archive_blobs_dir: archive_dir.join("blobs"),
40            archive_dir,
41            ledger_dir,
42            edda_dir,
43            root,
44        }
45    }
46
47    /// Create all required directories. Idempotent.
48    pub fn ensure_layout(&self) -> anyhow::Result<()> {
49        for dir in [
50            &self.ledger_dir,
51            &self.blobs_dir,
52            &self.branches_dir,
53            &self.drafts_dir,
54            &self.patterns_dir,
55        ] {
56            std::fs::create_dir_all(dir)?;
57        }
58        Ok(())
59    }
60
61    /// Check whether `.edda/` exists.
62    pub fn is_initialized(&self) -> bool {
63        self.edda_dir.is_dir()
64    }
65
66    /// Resolve a branch directory under `.edda/branches/<name>/`.
67    pub fn branch_dir(&self, name: &str) -> PathBuf {
68        self.branches_dir.join(name)
69    }
70}
71
72impl EddaPaths {
73    /// Walk up from `start` looking for a directory containing `.edda/`.
74    /// Returns `None` if not found.
75    pub fn find_root(start: &Path) -> Option<PathBuf> {
76        let mut cur = start.to_path_buf();
77        loop {
78            if cur.join(".edda").is_dir() {
79                return Some(cur);
80            }
81            if !cur.pop() {
82                return None;
83            }
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn discover_builds_correct_paths() {
94        let p = EddaPaths::discover("/tmp/repo");
95        assert_eq!(p.edda_dir, PathBuf::from("/tmp/repo/.edda"));
96        assert_eq!(p.blobs_dir, PathBuf::from("/tmp/repo/.edda/ledger/blobs"));
97        assert_eq!(p.lock_file, PathBuf::from("/tmp/repo/.edda/LOCK"));
98        assert_eq!(p.patterns_dir, PathBuf::from("/tmp/repo/.edda/patterns"));
99        assert_eq!(
100            p.blob_meta_json,
101            PathBuf::from("/tmp/repo/.edda/ledger/blob_meta.json")
102        );
103        assert_eq!(
104            p.tombstones_jsonl,
105            PathBuf::from("/tmp/repo/.edda/ledger/tombstones.jsonl")
106        );
107        assert_eq!(p.archive_dir, PathBuf::from("/tmp/repo/.edda/archive"));
108        assert_eq!(
109            p.archive_blobs_dir,
110            PathBuf::from("/tmp/repo/.edda/archive/blobs")
111        );
112    }
113
114    #[test]
115    fn ensure_layout_creates_dirs() {
116        let tmp = std::env::temp_dir().join(format!("edda_test_{}", std::process::id()));
117        let _ = std::fs::remove_dir_all(&tmp);
118        let p = EddaPaths::discover(&tmp);
119        p.ensure_layout().unwrap();
120        assert!(p.ledger_dir.is_dir());
121        assert!(p.blobs_dir.is_dir());
122        assert!(p.branches_dir.is_dir());
123        assert!(p.drafts_dir.is_dir());
124        assert!(p.patterns_dir.is_dir());
125        let _ = std::fs::remove_dir_all(&tmp);
126    }
127}