1use std::{
2 env, fs,
3 path::{Path, PathBuf},
4};
5
6use crate::{HashId, errors::ConfigError};
7
8pub const CHANGESET_DIR_NAME: &str = "changesets";
9
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct Workspace {
12 base_dir: PathBuf,
13}
14
15impl Workspace {
16 pub fn new(base_dir: impl Into<PathBuf>) -> Self {
17 Self {
18 base_dir: base_dir.into(),
19 }
20 }
21
22 pub fn from_current_dir() -> Self {
23 Self::new(env::current_dir().unwrap())
24 }
25
26 pub fn base_dir(&self) -> &Path {
27 &self.base_dir
28 }
29
30 pub fn ensure_gen_dir(&self) -> PathBuf {
31 let gen_path = self.base_dir.join(".gen");
32 ensure_dir(&gen_path);
33 let changesets = gen_path.join(CHANGESET_DIR_NAME);
34 ensure_dir(&changesets);
35 gen_path
36 }
37
38 pub fn find_gen_dir(&self) -> Option<PathBuf> {
39 let mut cur_dir = self.base_dir.as_path();
40 let mut gen_path = cur_dir.join(".gen");
41 while !gen_path.is_dir() {
42 cur_dir = cur_dir.parent()?;
43 gen_path = cur_dir.join(".gen");
44 }
45 Some(gen_path)
46 }
47
48 pub fn repo_root(&self) -> Result<PathBuf, ConfigError> {
49 let gen_dir = self
50 .find_gen_dir()
51 .ok_or(ConfigError::GenDirectoryNotFound)?;
52
53 gen_dir
54 .parent()
55 .map(Path::to_path_buf)
56 .ok_or(ConfigError::RepoRootNotFound)
57 }
58
59 pub fn gen_db_path(&self) -> Result<PathBuf, ConfigError> {
60 self.find_gen_dir()
61 .map(|dir| dir.join("gen.db"))
62 .ok_or(ConfigError::GenDirectoryNotFound)
63 }
64
65 pub fn changeset_path(&self, hash: &HashId) -> PathBuf {
66 let path = self
67 .ensure_gen_dir()
68 .join(CHANGESET_DIR_NAME)
69 .join(format!("{hash}"));
70 ensure_dir(&path);
71 path
72 }
73}
74
75fn ensure_dir(path: &Path) {
76 if !path.is_dir() {
77 fs::create_dir_all(path).unwrap();
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use std::fs;
84
85 use tempfile::tempdir;
86
87 use super::*;
88
89 #[test]
90 fn ensure_gen_dir_creates_directory() {
91 let tmp_dir = tempdir().unwrap();
92 let tmp_dir_path = tmp_dir.path().to_path_buf();
93 let workspace = Workspace::new(&tmp_dir_path);
94
95 let gen_dir = workspace.ensure_gen_dir();
96
97 assert_eq!(gen_dir, tmp_dir_path.join(".gen"));
98 assert!(gen_dir.is_dir());
99 }
100
101 #[test]
102 fn find_gen_dir_walks_up_tree() {
103 let tmp_dir = tempdir().unwrap();
104 let tmp_dir_path = tmp_dir.path().to_path_buf();
105 let root_workspace = Workspace::new(&tmp_dir_path);
106 let gen_dir = root_workspace.ensure_gen_dir();
107
108 let nested_dir = tmp_dir_path.join("nested").join("deep");
109 fs::create_dir_all(&nested_dir).unwrap();
110 let nested_workspace = Workspace::new(&nested_dir);
111
112 assert_eq!(nested_workspace.find_gen_dir(), Some(gen_dir));
113 }
114
115 #[test]
116 fn repo_root_returns_parent_of_gen_dir() {
117 let tmp_dir = tempdir().unwrap();
118 let tmp_dir_path = tmp_dir.path().to_path_buf();
119 let workspace = Workspace::new(&tmp_dir_path);
120 workspace.ensure_gen_dir();
121
122 assert_eq!(workspace.repo_root().unwrap(), tmp_dir_path);
123 }
124
125 #[test]
126 fn repo_root_errors_when_missing_gen_dir() {
127 let tmp_dir = tempdir().unwrap();
128 let tmp_dir_path = tmp_dir.path().to_path_buf();
129 let workspace = Workspace::new(&tmp_dir_path);
130
131 assert_eq!(
132 Err(ConfigError::GenDirectoryNotFound),
133 workspace.repo_root()
134 );
135 }
136
137 #[test]
138 fn gen_db_path_resolves_inside_gen_dir() {
139 let tmp_dir = tempdir().unwrap();
140 let tmp_dir_path = tmp_dir.path().to_path_buf();
141 let workspace = Workspace::new(&tmp_dir_path);
142 let gen_dir = workspace.ensure_gen_dir();
143
144 assert_eq!(workspace.gen_db_path().unwrap(), gen_dir.join("gen.db"));
145 }
146
147 #[test]
148 fn changeset_path_creates_directory_for_hash() {
149 let tmp_dir = tempdir().unwrap();
150 let tmp_dir_path = tmp_dir.path().to_path_buf();
151 let workspace = Workspace::new(&tmp_dir_path);
152 let hash = HashId([0; 32]);
153
154 let path = workspace.changeset_path(&hash);
155
156 assert_eq!(
157 path,
158 tmp_dir_path
159 .join(".gen")
160 .join(CHANGESET_DIR_NAME)
161 .join(format!("{hash}"))
162 );
163 assert!(path.is_dir());
164 }
165}