use std::collections::HashSet;
use std::path::{Path, PathBuf};
use thiserror::Error;
use crate::package::Package;
#[derive(Debug, Error)]
pub enum DeveloperPackageError {
#[error("Package definition file not found at: {path}")]
FileNotFound { path: String },
#[error("Failed to parse package definition: {0}")]
ParseError(String),
#[error("Invalid package: {0}")]
InvalidPackage(String),
#[error("Include path not in package_definition_python_path: {path}")]
InvalidInclude { path: String },
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
Other(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PreprocessMode {
Before,
After,
Override,
}
#[derive(Debug, Clone)]
pub struct DeveloperPackage {
pub package: Package,
pub filepath: PathBuf,
pub root: PathBuf,
pub includes: HashSet<String>,
}
impl DeveloperPackage {
pub fn new(package: Package, filepath: PathBuf) -> Self {
let root = filepath
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_else(|| PathBuf::from("."));
Self {
package,
filepath,
root,
includes: HashSet::new(),
}
}
pub fn from_path(path: &Path) -> Result<Self, DeveloperPackageError> {
let filepath = Self::find_definition_file(path)?;
let package = Self::load_package_definition(&filepath)?;
let mut dev_pkg = Self::new(package, filepath);
dev_pkg.collect_includes()?;
Ok(dev_pkg)
}
fn find_definition_file(dir: &Path) -> Result<PathBuf, DeveloperPackageError> {
let candidates = [dir.join("package.py"), dir.join("package.yaml")];
for candidate in &candidates {
if candidate.exists() && candidate.is_file() {
return Ok(candidate.clone());
}
}
Err(DeveloperPackageError::FileNotFound {
path: dir.display().to_string(),
})
}
fn load_package_definition(filepath: &Path) -> Result<Package, DeveloperPackageError> {
let content = std::fs::read_to_string(filepath).map_err(DeveloperPackageError::Io)?;
if filepath.extension().and_then(|e| e.to_str()) == Some("yaml") {
crate::serialization::PackageSerializer::load_from_yaml(&content)
.map_err(|e| DeveloperPackageError::ParseError(e.to_string()))
} else {
crate::serialization::PackageSerializer::load_from_python(&content)
.map_err(|e| DeveloperPackageError::ParseError(e.to_string()))
}
}
fn collect_includes(&mut self) -> Result<(), DeveloperPackageError> {
Ok(())
}
pub fn name(&self) -> &str {
&self.package.name
}
pub fn version_string(&self) -> Option<&str> {
self.package.version.as_ref().map(|v| v.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_find_definition_py() {
let tmp = TempDir::new().unwrap();
std::fs::write(tmp.path().join("package.py"), "name = 'test'").unwrap();
let filepath = DeveloperPackage::find_definition_file(tmp.path()).unwrap();
assert_eq!(filepath.file_name().unwrap(), "package.py");
}
#[test]
fn test_find_definition_yaml() {
let tmp = TempDir::new().unwrap();
std::fs::write(tmp.path().join("package.yaml"), "name: test").unwrap();
let filepath = DeveloperPackage::find_definition_file(tmp.path()).unwrap();
assert_eq!(filepath.file_name().unwrap(), "package.yaml");
}
#[test]
fn test_find_definition_py_preferred_over_yaml() {
let tmp = TempDir::new().unwrap();
std::fs::write(tmp.path().join("package.py"), "name = 'py'").unwrap();
std::fs::write(tmp.path().join("package.yaml"), "name: yaml").unwrap();
let filepath = DeveloperPackage::find_definition_file(tmp.path()).unwrap();
assert_eq!(filepath.file_name().unwrap(), "package.py");
}
#[test]
fn test_find_definition_not_found() {
let tmp = TempDir::new().unwrap();
let result = DeveloperPackage::find_definition_file(tmp.path());
assert!(result.is_err());
}
#[test]
fn test_developer_package_new() {
let pkg = Package::new("testpkg".to_string());
let filepath = PathBuf::from("/fake/path/package.py");
let dev_pkg = DeveloperPackage::new(pkg, filepath.clone());
assert_eq!(dev_pkg.name(), "testpkg");
assert_eq!(dev_pkg.filepath, filepath);
assert_eq!(dev_pkg.root, PathBuf::from("/fake/path"));
}
#[test]
fn test_from_path_basic() {
let tmp = TempDir::new().unwrap();
std::fs::write(
tmp.path().join("package.py"),
"name = 'mypkg'\nversion = '1.0.0'\ndescription = 'Test'",
)
.unwrap();
let dev_pkg = DeveloperPackage::from_path(tmp.path()).unwrap();
assert_eq!(dev_pkg.name(), "mypkg");
assert_eq!(dev_pkg.filepath, tmp.path().join("package.py"));
assert_eq!(dev_pkg.root, tmp.path().to_path_buf());
}
#[test]
fn test_version_string() {
let mut pkg = Package::new("verpkg".to_string());
pkg.version = Some(rez_next_version::Version::parse("2.0.0").unwrap());
let dev_pkg = DeveloperPackage::new(pkg, PathBuf::from("package.py"));
assert_eq!(dev_pkg.version_string(), Some("2.0.0"));
}
#[test]
fn test_version_string_none() {
let pkg = Package::new("noverpkg".to_string());
let dev_pkg = DeveloperPackage::new(pkg, PathBuf::from("package.py"));
assert_eq!(dev_pkg.version_string(), None);
}
#[test]
fn test_preprocess_mode_enum() {
assert_eq!(PreprocessMode::Before as i32, 0);
assert_eq!(PreprocessMode::After as i32, 1);
assert_eq!(PreprocessMode::Override as i32, 2);
}
}