Skip to main content

systemprompt_cloud/paths/
project.rs

1use 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}