use crate::{PackageHashes, UrlOrPath};
use pep440_rs::VersionSpecifiers;
use pep508_rs::{ExtraName, PackageName, Requirement};
use rattler_digest::{digest::Digest, Sha256};
use std::cmp::Ordering;
use std::collections::BTreeSet;
use std::fs;
use std::path::Path;
#[derive(Eq, PartialEq, Clone, Debug, Hash)]
pub struct PypiPackageData {
pub name: PackageName,
pub version: pep440_rs::Version,
pub location: UrlOrPath,
pub hash: Option<PackageHashes>,
pub requires_dist: Vec<Requirement>,
pub requires_python: Option<VersionSpecifiers>,
pub editable: bool,
}
#[derive(Clone, Debug, Default)]
pub struct PypiPackageEnvironmentData {
pub extras: BTreeSet<ExtraName>,
}
impl PartialOrd for PypiPackageData {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PypiPackageData {
fn cmp(&self, other: &Self) -> Ordering {
self.name
.cmp(&other.name)
.then_with(|| self.version.cmp(&other.version))
.then_with(|| self.location.cmp(&other.location))
.then_with(|| self.hash.cmp(&other.hash))
}
}
impl PypiPackageData {
pub fn satisfies(&self, spec: &Requirement) -> bool {
if spec.name != self.name {
return false;
}
match &spec.version_or_url {
None => {}
Some(pep508_rs::VersionOrUrl::Url(_)) => return false,
Some(pep508_rs::VersionOrUrl::VersionSpecifier(spec)) => {
if !spec.contains(&self.version) {
return false;
}
}
}
true
}
}
pub struct PypiSourceTreeHashable {
pub pyproject_toml: Option<String>,
pub setup_py: Option<String>,
pub setup_cfg: Option<String>,
}
fn ignore_not_found<C>(result: std::io::Result<C>) -> std::io::Result<Option<C>> {
match result {
Ok(content) => Ok(Some(content)),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
fn normalize_file_contents(contents: &str) -> String {
contents.replace("\r\n", "\n")
}
impl PypiSourceTreeHashable {
pub fn from_directory(directory: impl AsRef<Path>) -> std::io::Result<Self> {
let directory = directory.as_ref();
let pyproject_toml =
ignore_not_found(fs::read_to_string(directory.join("pyproject.toml")))?;
let setup_py = ignore_not_found(fs::read_to_string(directory.join("setup.py")))?;
let setup_cfg = ignore_not_found(fs::read_to_string(directory.join("setup.cfg")))?;
Ok(Self {
pyproject_toml: pyproject_toml.as_deref().map(normalize_file_contents),
setup_py: setup_py.as_deref().map(normalize_file_contents),
setup_cfg: setup_cfg.as_deref().map(normalize_file_contents),
})
}
pub fn hash(&self) -> PackageHashes {
let mut hasher = Sha256::new();
if let Some(pyproject_toml) = &self.pyproject_toml {
hasher.update(pyproject_toml.as_bytes());
}
if let Some(setup_py) = &self.setup_py {
hasher.update(setup_py.as_bytes());
}
if let Some(setup_cfg) = &self.setup_cfg {
hasher.update(setup_cfg.as_bytes());
}
PackageHashes::Sha256(hasher.finalize())
}
}