agpm_cli/resolver/
source_context.rs1use crate::manifest::ResourceDependency;
8use crate::utils::{compute_relative_path, normalize_path_for_storage};
9use std::path::{Path, PathBuf};
10
11#[derive(Debug, Clone)]
18pub enum SourceContext {
19 Local(PathBuf),
21 Git(PathBuf),
23 Remote(String),
25}
26
27impl SourceContext {
28 pub fn local(manifest_dir: impl Into<PathBuf>) -> Self {
30 Self::Local(manifest_dir.into())
31 }
32
33 pub fn git(repo_root: impl Into<PathBuf>) -> Self {
35 Self::Git(repo_root.into())
36 }
37
38 pub fn remote(source_name: impl Into<String>) -> Self {
40 Self::Remote(source_name.into())
41 }
42
43 pub fn is_local(&self) -> bool {
45 matches!(self, Self::Local(_))
46 }
47
48 pub fn is_git(&self) -> bool {
50 matches!(self, Self::Git(_))
51 }
52
53 pub fn is_remote(&self) -> bool {
55 matches!(self, Self::Remote(_))
56 }
57}
58
59pub fn compute_canonical_name(path: &str, source_context: &SourceContext) -> String {
66 let path = Path::new(path);
67
68 let without_ext = path.with_extension("");
70
71 match source_context {
72 SourceContext::Local(manifest_dir) => {
73 let relative = compute_relative_path(manifest_dir, &without_ext);
75 normalize_path_for_storage(relative)
76 }
77 SourceContext::Git(repo_root) => compute_relative_to_repo(&without_ext, repo_root),
78 SourceContext::Remote(_source_name) => {
79 normalize_path_for_storage(&without_ext)
83 }
84 }
85}
86
87pub fn create_source_context_for_dependency(
92 dep: &ResourceDependency,
93 manifest_dir: Option<&Path>,
94 repo_root: Option<&Path>,
95) -> SourceContext {
96 if let Some(source_name) = dep.get_source() {
98 if let Some(repo_root) = repo_root {
100 SourceContext::git(repo_root)
101 } else {
102 SourceContext::remote(source_name)
104 }
105 } else if let Some(manifest_dir) = manifest_dir {
106 SourceContext::local(manifest_dir)
108 } else {
109 SourceContext::remote("unknown")
111 }
112}
113
114pub fn create_source_context_from_locked_resource(
118 resource: &crate::lockfile::LockedResource,
119 manifest_dir: Option<&Path>,
120) -> SourceContext {
121 if let Some(source_name) = &resource.source {
122 SourceContext::remote(source_name.clone())
124 } else {
125 if let Some(manifest_dir) = manifest_dir {
127 SourceContext::local(manifest_dir)
128 } else {
129 SourceContext::local(std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
131 }
132 }
133}
134
135fn compute_relative_to_repo(file_path: &Path, repo_root: &Path) -> String {
139 compute_relative_path(repo_root, file_path)
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 use std::path::Path;
148
149 #[test]
150 fn test_source_context_creation() {
151 let local = SourceContext::local("/project");
152 assert!(local.is_local());
153 assert!(!local.is_git());
154 assert!(!local.is_remote());
155
156 let git = SourceContext::git("/repo");
157 assert!(!git.is_local());
158 assert!(git.is_git());
159 assert!(!git.is_remote());
160
161 let remote = SourceContext::remote("community");
162 assert!(!remote.is_local());
163 assert!(!remote.is_git());
164 assert!(remote.is_remote());
165 }
166
167 #[test]
168 fn test_compute_canonical_name_integration() {
169 let local_ctx = SourceContext::local("/project");
171 let name = compute_canonical_name("/project/local-deps/agents/helper.md", &local_ctx);
172 assert_eq!(name, "local-deps/agents/helper");
173
174 let git_ctx = SourceContext::git("/repo");
176 let name = compute_canonical_name("/repo/agents/helper.md", &git_ctx);
177 assert_eq!(name, "agents/helper");
178
179 let remote_ctx = SourceContext::remote("community");
181 let name = compute_canonical_name("agents/helper.md", &remote_ctx);
182 assert_eq!(name, "agents/helper");
183 }
184
185 #[test]
187 fn test_create_source_context_for_dependency() {
188 use crate::manifest::{DetailedDependency, ResourceDependency};
189
190 let local_dep = ResourceDependency::Detailed(Box::new(DetailedDependency {
192 path: "agents/helper.md".to_string(),
193 source: None,
194 version: None,
195 branch: None,
196 rev: None,
197 command: None,
198 args: None,
199 target: None,
200 filename: None,
201 dependencies: None,
202 tool: None,
203 flatten: None,
204 install: None,
205 template_vars: None,
206 }));
207
208 let manifest_dir = Path::new("/project");
209 let ctx = create_source_context_for_dependency(&local_dep, Some(manifest_dir), None);
210 assert!(ctx.is_local());
211
212 let git_dep = ResourceDependency::Detailed(Box::new(DetailedDependency {
214 path: "agents/helper.md".to_string(),
215 source: Some("community".to_string()),
216 version: None,
217 branch: None,
218 rev: None,
219 command: None,
220 args: None,
221 target: None,
222 filename: None,
223 dependencies: None,
224 tool: None,
225 flatten: None,
226 install: None,
227 template_vars: None,
228 }));
229
230 let repo_root = Path::new("/repo");
231 let ctx =
232 create_source_context_for_dependency(&git_dep, Some(manifest_dir), Some(repo_root));
233 assert!(ctx.is_git());
234
235 let ctx = create_source_context_for_dependency(&git_dep, Some(manifest_dir), None);
237 assert!(ctx.is_remote());
238 }
239
240 #[test]
241 fn test_create_source_context_from_locked_resource() {
242 use crate::lockfile::LockedResource;
243
244 let local_resource = LockedResource {
246 name: "helper".to_string(),
247 source: None,
248 url: None,
249 path: "agents/helper.md".to_string(),
250 version: None,
251 resolved_commit: None,
252 checksum: "abc123".to_string(),
253 installed_at: "agents/helper.md".to_string(),
254 dependencies: vec![],
255 resource_type: crate::core::ResourceType::Agent,
256 tool: Some("claude-code".to_string()),
257 manifest_alias: None,
258 context_checksum: None,
259 applied_patches: std::collections::BTreeMap::new(),
260 install: None,
261 variant_inputs: crate::resolver::lockfile_builder::VariantInputs::default(),
262 };
263
264 let manifest_dir = Path::new("/project");
265 let ctx = create_source_context_from_locked_resource(&local_resource, Some(manifest_dir));
266 assert!(ctx.is_local());
267
268 let mut remote_resource = local_resource.clone();
270 remote_resource.source = Some("community".to_string());
271
272 let ctx = create_source_context_from_locked_resource(&remote_resource, Some(manifest_dir));
273 assert!(ctx.is_remote());
274 assert_eq!(format!("{:?}", ctx), "Remote(\"community\")");
275 }
276}