use crate::prelude::*;
use super::rfc822ish::RFC822ish;
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Serialize))]
pub struct WheelCoreMetadata {
pub name: PackageName,
pub version: Version,
pub requires_dist: Vec<PackageRequirement>,
pub requires_python: Specifiers,
pub extras: HashSet<Extra>,
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Serialize))]
pub struct PybiCoreMetadata {
pub name: PackageName,
pub version: Version,
pub environment_marker_variables: HashMap<String, String>,
pub tags: Vec<String>,
pub paths: HashMap<String, NicePathBuf>,
}
impl PybiCoreMetadata {
pub fn path(&self, key: &str) -> Result<&NicePathBuf> {
Ok(self
.paths
.get(key)
.ok_or(eyre!("bad pybi: no '{key}' path"))?)
}
}
fn parse_common(input: &[u8]) -> Result<(PackageName, Version, RFC822ish)> {
let input = String::from_utf8_lossy(input);
let mut parsed = RFC822ish::parse(&input)?;
static NEXT_MAJOR_METADATA_VERSION: Lazy<Version> =
Lazy::new(|| "3".try_into().unwrap());
let metadata_version: Version = parsed.take_the("Metadata-Version")?.try_into()?;
if metadata_version >= *NEXT_MAJOR_METADATA_VERSION {
bail!("unsupported Metadata-Version {}", metadata_version);
}
Ok((
parsed.take_the("Name")?.parse()?,
parsed.take_the("Version")?.try_into()?,
parsed,
))
}
impl TryFrom<&[u8]> for WheelCoreMetadata {
type Error = eyre::Report;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let (name, version, mut parsed) = parse_common(value)?;
let mut requires_dist = Vec::new();
for req_str in parsed.take_all("Requires-Dist").drain(..) {
requires_dist.push(req_str.try_into()?);
}
let requires_python = match parsed.maybe_take_the("Requires-Python")? {
Some(rp_str) => rp_str.try_into()?,
None => Specifiers(Vec::new()),
};
let mut extras: HashSet<Extra> = HashSet::new();
for extra in parsed.take_all("Provides-Extra").drain(..) {
extras.insert(extra.parse()?);
}
Ok(WheelCoreMetadata {
name,
version,
requires_dist,
requires_python,
extras,
})
}
}
impl TryFrom<&[u8]> for PybiCoreMetadata {
type Error = eyre::Report;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let (name, version, mut parsed) = parse_common(value)?;
Ok(PybiCoreMetadata {
name,
version,
environment_marker_variables: serde_json::from_str(
&parsed.take_the("Pybi-Environment-Marker-Variables")?,
)?,
tags: parsed.take_all("Pybi-Wheel-Tag"),
paths: serde_json::from_str(&parsed.take_the("Pybi-Paths")?)?,
})
}
}
#[cfg(test)]
mod test {
use super::*;
use indoc::indoc;
#[test]
fn test_basic_core_parse() {
let metadata_text = indoc! {r#"
Metadata-Version: 2.1
Name: trio
Version: 0.16.0
Summary: A friendly Python library for async concurrency and I/O
Classifier: Framework :: Trio
Requires-Python: >=3.6
Requires-Dist: attrs (>=19.2.0)
Requires-Dist: sortedcontainers
Requires-Dist: contextvars[foo] (>=2.1) ; python_version < "3.7"
The Trio project's goal is...
"#}
.as_bytes();
let metadata: WheelCoreMetadata = metadata_text.try_into().unwrap();
insta::assert_ron_snapshot!(metadata, @r###"
WheelCoreMetadata(
name: "trio",
version: "0.16.0",
requires_dist: [
"attrs >= 19.2.0",
"sortedcontainers",
"contextvars[foo] >= 2.1; python_version < \"3.7\"",
],
requires_python: ">= 3.6",
extras: [],
)
"###);
}
#[test]
fn test_basic_pybi_parse() {
let metadata_text = indoc! {r#"
Metadata-Version: 2.1
Name: CPython
Version: 3.11.2
Pybi-Environment-Marker-Variables: {"implementation_name": "cpython", "os_name": "posix"}
pybi-wheel-tag: cp311-cp311-linux_x86_64
Pybi-Wheel-Tag: py3-none-any
Pybi-Paths: {"data": ".", "include": "include/python3.11"}
This is CPython, the standard interpreter for the Python language...
"#}
.as_bytes();
let metadata: PybiCoreMetadata = metadata_text.try_into().unwrap();
insta::assert_ron_snapshot!(metadata,
{
".paths" => insta::sorted_redaction(),
".environment_marker_variables" => insta::sorted_redaction(),
},
@r###"
PybiCoreMetadata(
name: "CPython",
version: "3.11.2",
environment_marker_variables: {
"implementation_name": "cpython",
"os_name": "posix",
},
tags: [
"cp311-cp311-linux_x86_64",
"py3-none-any",
],
paths: {
"data": ".",
"include": "include/python3.11",
},
)
"###
);
}
}