1use std::path::{Path, PathBuf};
2
3use anyhow::{Context, Result};
4
5use crate::entity::{EntityKind, kind_from_id};
6
7pub const IXCHEL_DIR_NAME: &str = ".ixchel";
8
9#[derive(Debug, Clone)]
10pub struct IxchelPaths {
11 repo_root: PathBuf,
12}
13
14impl IxchelPaths {
15 #[must_use]
16 pub const fn new(repo_root: PathBuf) -> Self {
17 Self { repo_root }
18 }
19
20 #[must_use]
21 pub fn repo_root(&self) -> &Path {
22 &self.repo_root
23 }
24
25 #[must_use]
26 pub fn ixchel_dir(&self) -> PathBuf {
27 self.repo_root.join(IXCHEL_DIR_NAME)
28 }
29
30 #[must_use]
31 pub fn config_path(&self) -> PathBuf {
32 self.ixchel_dir().join("config.toml")
33 }
34
35 #[must_use]
36 pub fn data_dir(&self) -> PathBuf {
37 self.ixchel_dir().join("data")
38 }
39
40 #[must_use]
41 pub fn kind_dir(&self, kind: EntityKind) -> PathBuf {
42 self.ixchel_dir().join(kind.directory_name())
43 }
44
45 #[must_use]
46 pub fn entity_path(&self, id: &str) -> Option<PathBuf> {
47 let kind = kind_from_id(id)?;
48 Some(self.kind_dir(kind).join(format!("{id}.md")))
49 }
50
51 pub fn ensure_layout(&self) -> Result<()> {
52 std::fs::create_dir_all(self.data_dir())
53 .with_context(|| format!("Failed to create {}", self.data_dir().display()))?;
54
55 for kind in [
56 EntityKind::Decision,
57 EntityKind::Issue,
58 EntityKind::Idea,
59 EntityKind::Report,
60 EntityKind::Source,
61 EntityKind::Citation,
62 EntityKind::Agent,
63 EntityKind::Session,
64 ] {
65 let dir = self.kind_dir(kind);
66 std::fs::create_dir_all(&dir)
67 .with_context(|| format!("Failed to create {}", dir.display()))?;
68 }
69
70 Ok(())
71 }
72}
73
74#[must_use]
75pub fn find_git_root(start: &Path) -> Option<PathBuf> {
76 let mut current = Some(start);
77 while let Some(dir) = current {
78 if dir.join(".git").exists() {
79 return Some(dir.to_path_buf());
80 }
81 current = dir.parent();
82 }
83 None
84}