use super::ids::{DevHead, PostHead, PostHeader, PreHeader, ReleaseHeader};
use super::validate_440_version;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Derivative, Debug, Serialize, Deserialize)]
#[derivative(PartialOrd, PartialEq)]
pub struct PackageVersion {
#[derivative(PartialOrd = "ignore", PartialEq = "ignore")]
pub original: String,
#[derivative(PartialOrd = "ignore", PartialEq = "ignore")]
pub local: Option<String>,
pub dev: Option<DevHead>,
pub post: Option<PostHeader>,
pub pre: Option<PreHeader>,
pub release: ReleaseHeader,
pub epoch: Option<u32>,
}
impl PackageVersion {
pub fn new(version: &str) -> Result<Self> {
let version_match = validate_440_version(version)?;
let epoch: Option<u32> = match version_match.name("epoch") {
Some(v) => Some(v.as_str().parse::<u32>()?),
None => None,
};
let release: ReleaseHeader = match version_match.name("release") {
Some(v) => {
if v.as_str().contains('.') {
let split: Vec<&str> = v.as_str().split('.').into_iter().collect();
ReleaseHeader {
major: split[0].parse::<u32>()?,
minor: split[1].parse::<u32>()?,
}
} else {
ReleaseHeader {
major: v.as_str().parse::<u32>()?,
minor: 0,
}
}
}
None => anyhow::bail!("Failed to decode version {}", version),
};
let pre: Option<PreHeader> = match version_match.name("pre") {
Some(_) => {
let pre_n = match version_match.name("pre_n") {
Some(v) => Some(v.as_str().parse::<u32>()?),
None => None,
};
match version_match.name("pre_l").unwrap().as_str() {
"alpha" => Some(PreHeader::Alpha(pre_n)),
"a" => Some(PreHeader::Alpha(pre_n)),
"beta" => Some(PreHeader::Beta(pre_n)),
"b" => Some(PreHeader::Beta(pre_n)),
"rc" => Some(PreHeader::ReleaseCandidate(pre_n)),
"c" => Some(PreHeader::ReleaseCandidate(pre_n)),
"preview" => Some(PreHeader::Preview(pre_n)),
"pre" => Some(PreHeader::Preview(pre_n)),
_ => None,
}
}
None => None,
};
let post: Option<PostHeader> = match version_match.name("post") {
Some(_) => {
let post_num: Option<u32> = match version_match.name("post_n1") {
Some(v) => Some(v.as_str().parse::<u32>()?),
None => match version_match.name("post_n2") {
Some(v) => Some(v.as_str().parse::<u32>()?),
_ => None,
},
};
let post_head: Option<PostHead> = match version_match.name("post_l") {
Some(v) => {
match v.as_str() {
"post" => Some(PostHead::Post),
"rev" => Some(PostHead::Rev),
"r" => Some(PostHead::Rev),
_ => None,
}
}
None => None,
};
Some(PostHeader {
post_head,
post_num,
})
}
None => None,
};
let dev: Option<DevHead> = match version_match.name("dev") {
Some(_) => {
let dev_num = match version_match.name("dev_n") {
Some(v) => Some(v.as_str().parse::<u32>()?),
None => None,
};
Some(DevHead { dev_num })
}
None => None,
};
let local: Option<String> =
version_match.name("local").map(|v| v.as_str().to_string());
Ok(Self {
original: version.to_string(),
epoch,
release,
pre,
post,
dev,
local,
})
}
}
impl fmt::Display for PackageVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.original)
}
}
#[cfg(test)]
mod tests {
use crate::PackageVersion;
use anyhow::Result;
#[test]
fn test_pep440_ordering() -> Result<()> {
assert!(
PackageVersion::new(
"v1!1.0-preview-921.post-516.dev-241+yeah.this.is.the.problem.with.local.versions",
)?
>
PackageVersion::new("1.0")?
);
Ok(())
}
#[test]
fn test_pep440_equality() -> Result<()> {
assert_eq!(
PackageVersion::new("1.0a1")?,
PackageVersion::new("1.0alpha1")?
);
assert_eq!(
PackageVersion::new("1.0b")?,
PackageVersion::new("1.0beta")?
);
assert_eq!(PackageVersion::new("1.0r")?, PackageVersion::new("1.0rev")?);
assert_eq!(PackageVersion::new("1.0c")?, PackageVersion::new("1.0rc")?);
assert_eq!(PackageVersion::new("v1.0")?, PackageVersion::new("1.0")?);
Ok(())
}
#[test]
fn test_pep440() {
let versions = vec![
"1.0",
"v1.1",
"2.0",
"2013.10",
"2014.04",
"1!1.0",
"1!1.1",
"1!2.0",
"2!1.0.pre0",
"1.0.dev456",
"1.0a1",
"1.0a2.dev456",
"1.0a12.dev456",
"1.0a12",
"1.0b1.dev456",
"1.0b2",
"1.0b2.post345.dev456",
"1.0b2.post345",
"1.0rc1.dev456",
"1.0rc1",
"1.0",
"1.0+abc.5",
"1.0+abc.7",
"1.0+5",
"1.0.post456.dev34",
"1.0.post456",
"1.0.15",
"1.1.dev1",
];
for version in versions {
match PackageVersion::new(version) {
Ok(_v) => continue,
Err(e) => panic!("Oh no {}", e),
}
}
}
#[test]
fn test_pep440_negative() {
let versions = vec!["not a version"];
for version in versions {
match PackageVersion::new(version) {
Ok(v) => panic!("Oh no {}", v),
Err(_e) => continue,
}
}
}
}