rez_next_package/
developer_package.rs1use std::collections::HashSet;
19use std::path::{Path, PathBuf};
20
21use thiserror::Error;
22
23use crate::package::Package;
24
25#[derive(Debug, Error)]
27pub enum DeveloperPackageError {
28 #[error("Package definition file not found at: {path}")]
29 FileNotFound { path: String },
30
31 #[error("Failed to parse package definition: {0}")]
32 ParseError(String),
33
34 #[error("Invalid package: {0}")]
35 InvalidPackage(String),
36
37 #[error("Include path not in package_definition_python_path: {path}")]
38 InvalidInclude { path: String },
39
40 #[error("IO error: {0}")]
41 Io(#[from] std::io::Error),
42
43 #[error("{0}")]
44 Other(String),
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum PreprocessMode {
51 Before,
53 After,
55 Override,
57}
58
59#[derive(Debug, Clone)]
67pub struct DeveloperPackage {
68 pub package: Package,
70 pub filepath: PathBuf,
72 pub root: PathBuf,
74 pub includes: HashSet<String>,
76}
77
78impl DeveloperPackage {
79 pub fn new(package: Package, filepath: PathBuf) -> Self {
81 let root = filepath
82 .parent()
83 .map(|p| p.to_path_buf())
84 .unwrap_or_else(|| PathBuf::from("."));
85
86 Self {
87 package,
88 filepath,
89 root,
90 includes: HashSet::new(),
91 }
92 }
93
94 pub fn from_path(path: &Path) -> Result<Self, DeveloperPackageError> {
99 let filepath = Self::find_definition_file(path)?;
100 let package = Self::load_package_definition(&filepath)?;
101
102 let mut dev_pkg = Self::new(package, filepath);
103 dev_pkg.collect_includes()?;
104
105 Ok(dev_pkg)
106 }
107
108 fn find_definition_file(dir: &Path) -> Result<PathBuf, DeveloperPackageError> {
111 let candidates = [dir.join("package.py"), dir.join("package.yaml")];
112
113 for candidate in &candidates {
114 if candidate.exists() && candidate.is_file() {
115 return Ok(candidate.clone());
116 }
117 }
118
119 Err(DeveloperPackageError::FileNotFound {
120 path: dir.display().to_string(),
121 })
122 }
123
124 fn load_package_definition(filepath: &Path) -> Result<Package, DeveloperPackageError> {
126 let content = std::fs::read_to_string(filepath).map_err(DeveloperPackageError::Io)?;
127
128 if filepath.extension().and_then(|e| e.to_str()) == Some("yaml") {
129 crate::serialization::PackageSerializer::load_from_yaml(&content)
130 .map_err(|e| DeveloperPackageError::ParseError(e.to_string()))
131 } else {
132 crate::serialization::PackageSerializer::load_from_python(&content)
133 .map_err(|e| DeveloperPackageError::ParseError(e.to_string()))
134 }
135 }
136
137 fn collect_includes(&mut self) -> Result<(), DeveloperPackageError> {
143 Ok(())
148 }
149
150 pub fn name(&self) -> &str {
152 &self.package.name
153 }
154
155 pub fn version_string(&self) -> Option<&str> {
157 self.package.version.as_ref().map(|v| v.as_str())
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use tempfile::TempDir;
165
166 #[test]
167 fn test_find_definition_py() {
168 let tmp = TempDir::new().unwrap();
169 std::fs::write(tmp.path().join("package.py"), "name = 'test'").unwrap();
170
171 let filepath = DeveloperPackage::find_definition_file(tmp.path()).unwrap();
172 assert_eq!(filepath.file_name().unwrap(), "package.py");
173 }
174
175 #[test]
176 fn test_find_definition_yaml() {
177 let tmp = TempDir::new().unwrap();
178 std::fs::write(tmp.path().join("package.yaml"), "name: test").unwrap();
179
180 let filepath = DeveloperPackage::find_definition_file(tmp.path()).unwrap();
181 assert_eq!(filepath.file_name().unwrap(), "package.yaml");
182 }
183
184 #[test]
185 fn test_find_definition_py_preferred_over_yaml() {
186 let tmp = TempDir::new().unwrap();
187 std::fs::write(tmp.path().join("package.py"), "name = 'py'").unwrap();
188 std::fs::write(tmp.path().join("package.yaml"), "name: yaml").unwrap();
189
190 let filepath = DeveloperPackage::find_definition_file(tmp.path()).unwrap();
191 assert_eq!(filepath.file_name().unwrap(), "package.py");
192 }
193
194 #[test]
195 fn test_find_definition_not_found() {
196 let tmp = TempDir::new().unwrap();
197 let result = DeveloperPackage::find_definition_file(tmp.path());
198 assert!(result.is_err());
199 }
200
201 #[test]
202 fn test_developer_package_new() {
203 let pkg = Package::new("testpkg".to_string());
204 let filepath = PathBuf::from("/fake/path/package.py");
205 let dev_pkg = DeveloperPackage::new(pkg, filepath.clone());
206
207 assert_eq!(dev_pkg.name(), "testpkg");
208 assert_eq!(dev_pkg.filepath, filepath);
209 assert_eq!(dev_pkg.root, PathBuf::from("/fake/path"));
210 }
211
212 #[test]
213 fn test_from_path_basic() {
214 let tmp = TempDir::new().unwrap();
215 std::fs::write(
216 tmp.path().join("package.py"),
217 "name = 'mypkg'\nversion = '1.0.0'\ndescription = 'Test'",
218 )
219 .unwrap();
220
221 let dev_pkg = DeveloperPackage::from_path(tmp.path()).unwrap();
222 assert_eq!(dev_pkg.name(), "mypkg");
223 assert_eq!(dev_pkg.filepath, tmp.path().join("package.py"));
224 assert_eq!(dev_pkg.root, tmp.path().to_path_buf());
225 }
226
227 #[test]
228 fn test_version_string() {
229 let mut pkg = Package::new("verpkg".to_string());
230 pkg.version = Some(rez_next_version::Version::parse("2.0.0").unwrap());
231
232 let dev_pkg = DeveloperPackage::new(pkg, PathBuf::from("package.py"));
233 assert_eq!(dev_pkg.version_string(), Some("2.0.0"));
234 }
235
236 #[test]
237 fn test_version_string_none() {
238 let pkg = Package::new("noverpkg".to_string());
239 let dev_pkg = DeveloperPackage::new(pkg, PathBuf::from("package.py"));
240 assert_eq!(dev_pkg.version_string(), None);
241 }
242
243 #[test]
244 fn test_preprocess_mode_enum() {
245 assert_eq!(PreprocessMode::Before as i32, 0);
246 assert_eq!(PreprocessMode::After as i32, 1);
247 assert_eq!(PreprocessMode::Override as i32, 2);
248 }
249}