systemprompt_cloud/paths/
project.rs1use std::path::{Path, PathBuf};
2
3use crate::constants::{dir_names, paths};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub enum ProjectPath {
7 Root,
8 ProfilesDir,
9 DockerDir,
10 StorageDir,
11 SessionsDir,
12 Dockerfile,
13 LocalCredentials,
14 LocalTenants,
15 LocalSession,
16}
17
18impl ProjectPath {
19 #[must_use]
20 pub const fn segments(&self) -> &'static [&'static str] {
21 match self {
22 Self::Root => &[paths::ROOT_DIR],
23 Self::ProfilesDir => &[paths::ROOT_DIR, paths::PROFILES_DIR],
24 Self::DockerDir => &[paths::ROOT_DIR, paths::DOCKER_DIR],
25 Self::StorageDir => &[paths::ROOT_DIR, paths::STORAGE_DIR],
26 Self::SessionsDir => &[paths::ROOT_DIR, dir_names::SESSIONS],
27 Self::Dockerfile => &[paths::ROOT_DIR, paths::DOCKERFILE],
28 Self::LocalCredentials => &[paths::ROOT_DIR, paths::CREDENTIALS_FILE],
29 Self::LocalTenants => &[paths::ROOT_DIR, paths::TENANTS_FILE],
30 Self::LocalSession => &[paths::ROOT_DIR, paths::SESSION_FILE],
31 }
32 }
33
34 #[must_use]
35 pub const fn is_dir(&self) -> bool {
36 matches!(
37 self,
38 Self::Root | Self::ProfilesDir | Self::DockerDir | Self::StorageDir | Self::SessionsDir
39 )
40 }
41
42 #[must_use]
43 pub fn resolve(&self, project_root: &Path) -> PathBuf {
44 let mut path = project_root.to_path_buf();
45 for segment in self.segments() {
46 path.push(segment);
47 }
48 path
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
53pub enum ProfilePath {
54 Config,
55 Secrets,
56 DockerDir,
57 Dockerfile,
58 Entrypoint,
59 Dockerignore,
60 Compose,
61}
62
63impl ProfilePath {
64 #[must_use]
65 pub const fn filename(&self) -> &'static str {
66 match self {
67 Self::Config => paths::PROFILE_CONFIG,
68 Self::Secrets => paths::PROFILE_SECRETS,
69 Self::DockerDir => paths::PROFILE_DOCKER_DIR,
70 Self::Dockerfile => paths::DOCKERFILE,
71 Self::Entrypoint => paths::ENTRYPOINT,
72 Self::Dockerignore => paths::DOCKERIGNORE,
73 Self::Compose => paths::COMPOSE_FILE,
74 }
75 }
76
77 #[must_use]
78 pub const fn is_docker_file(&self) -> bool {
79 matches!(
80 self,
81 Self::Dockerfile | Self::Entrypoint | Self::Dockerignore | Self::Compose
82 )
83 }
84
85 #[must_use]
86 pub fn resolve(&self, profile_dir: &Path) -> PathBuf {
87 match self {
88 Self::Dockerfile | Self::Entrypoint | Self::Dockerignore | Self::Compose => profile_dir
89 .join(paths::PROFILE_DOCKER_DIR)
90 .join(self.filename()),
91 _ => profile_dir.join(self.filename()),
92 }
93 }
94}
95
96#[derive(Debug, Clone)]
97pub struct ProjectContext {
98 root: PathBuf,
99}
100
101impl ProjectContext {
102 #[must_use]
103 pub const fn new(root: PathBuf) -> Self {
104 Self { root }
105 }
106
107 #[must_use]
108 pub fn discover() -> Self {
109 let cwd = match std::env::current_dir() {
110 Ok(dir) => dir,
111 Err(e) => {
112 tracing::debug!(error = %e, "Failed to get current directory, using '.'");
113 PathBuf::from(".")
114 },
115 };
116 Self::discover_from(&cwd)
117 }
118
119 #[must_use]
120 pub fn discover_from(start: &Path) -> Self {
121 let mut current = start.to_path_buf();
122 loop {
123 if current.join(paths::ROOT_DIR).is_dir() {
124 return Self::new(current);
125 }
126 if !current.pop() {
127 break;
128 }
129 }
130 Self::new(start.to_path_buf())
131 }
132
133 #[must_use]
134 pub fn root(&self) -> &Path {
135 &self.root
136 }
137
138 #[must_use]
139 pub fn resolve(&self, path: ProjectPath) -> PathBuf {
140 path.resolve(&self.root)
141 }
142
143 #[must_use]
144 pub fn systemprompt_dir(&self) -> PathBuf {
145 self.resolve(ProjectPath::Root)
146 }
147
148 #[must_use]
149 pub fn profiles_dir(&self) -> PathBuf {
150 self.resolve(ProjectPath::ProfilesDir)
151 }
152
153 #[must_use]
154 pub fn profile_dir(&self, name: &str) -> PathBuf {
155 self.profiles_dir().join(name)
156 }
157
158 #[must_use]
159 pub fn profile_path(&self, name: &str, path: ProfilePath) -> PathBuf {
160 path.resolve(&self.profile_dir(name))
161 }
162
163 #[must_use]
164 pub fn profile_docker_dir(&self, name: &str) -> PathBuf {
165 self.profile_path(name, ProfilePath::DockerDir)
166 }
167
168 #[must_use]
169 pub fn profile_dockerfile(&self, name: &str) -> PathBuf {
170 self.profile_path(name, ProfilePath::Dockerfile)
171 }
172
173 #[must_use]
174 pub fn profile_entrypoint(&self, name: &str) -> PathBuf {
175 self.profile_path(name, ProfilePath::Entrypoint)
176 }
177
178 #[must_use]
179 pub fn profile_dockerignore(&self, name: &str) -> PathBuf {
180 self.profile_path(name, ProfilePath::Dockerignore)
181 }
182
183 #[must_use]
184 pub fn profile_compose(&self, name: &str) -> PathBuf {
185 self.profile_path(name, ProfilePath::Compose)
186 }
187
188 #[must_use]
189 pub fn docker_dir(&self) -> PathBuf {
190 self.resolve(ProjectPath::DockerDir)
191 }
192
193 #[must_use]
194 pub fn storage_dir(&self) -> PathBuf {
195 self.resolve(ProjectPath::StorageDir)
196 }
197
198 #[must_use]
199 pub fn dockerfile(&self) -> PathBuf {
200 self.resolve(ProjectPath::Dockerfile)
201 }
202
203 #[must_use]
204 pub fn local_credentials(&self) -> PathBuf {
205 self.resolve(ProjectPath::LocalCredentials)
206 }
207
208 #[must_use]
209 pub fn local_tenants(&self) -> PathBuf {
210 self.resolve(ProjectPath::LocalTenants)
211 }
212
213 #[must_use]
214 pub fn sessions_dir(&self) -> PathBuf {
215 self.resolve(ProjectPath::SessionsDir)
216 }
217
218 #[must_use]
219 pub fn exists(&self, path: ProjectPath) -> bool {
220 self.resolve(path).exists()
221 }
222
223 #[must_use]
224 pub fn profile_exists(&self, name: &str) -> bool {
225 self.profile_dir(name).exists()
226 }
227}