use crate::ZvError;
use color_eyre::eyre::eyre;
use semver::Version;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use std::{
fmt,
hash::{Hash, Hasher},
str::FromStr,
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ResolvedZigVersion {
Semver(Version),
Master(Version),
}
#[derive(Debug, Clone)]
pub enum ZigVersion {
Semver(Version),
Master(Option<Version>),
Stable(Option<Version>),
Latest(Option<Version>),
}
impl ZigVersion {
pub fn placeholder_for_variant(variant: &str) -> Result<Self, ZvError> {
match variant {
"master" => Ok(ZigVersion::Master(None)),
"stable" => Ok(ZigVersion::Stable(None)),
"latest" => Ok(ZigVersion::Latest(None)),
_ => Err(ZvError::General(eyre!("Invalid variant: {}", variant))),
}
}
fn parse_normalized_version(version_str: &str) -> Result<Version, ZvError> {
let (core_version, suffix) = if let Some(hyphen_pos) = version_str.find('-') {
(&version_str[..hyphen_pos], &version_str[hyphen_pos..])
} else if let Some(plus_pos) = version_str.find('+') {
(&version_str[..plus_pos], &version_str[plus_pos..])
} else {
(version_str, "")
};
let normalized_core = match core_version.chars().filter(|&c| c == '.').count() {
0 => format!("{}.0.0", core_version),
1 => format!("{}.0", core_version),
_ => core_version.to_string(),
};
let normalized = format!("{}{}", normalized_core, suffix);
Version::parse(&normalized).map_err(ZvError::ZigVersionError)
}
pub fn version(&self) -> Option<&Version> {
match self {
ZigVersion::Semver(v) => Some(v),
ZigVersion::Master(Some(v))
| ZigVersion::Stable(Some(v))
| ZigVersion::Latest(Some(v)) => Some(v),
ZigVersion::Master(None) | ZigVersion::Stable(None) | ZigVersion::Latest(None) => None,
}
}
pub fn contains_semver(&self) -> bool {
match self {
ZigVersion::Semver(_) => true,
ZigVersion::Master(Some(_))
| ZigVersion::Stable(Some(_))
| ZigVersion::Latest(Some(_)) => true,
ZigVersion::Master(None) | ZigVersion::Stable(None) | ZigVersion::Latest(None) => false,
}
}
pub fn variant_type(&self) -> &'static str {
match self {
ZigVersion::Semver(_) => "semver",
ZigVersion::Master(_) => "master",
ZigVersion::Stable(_) => "stable",
ZigVersion::Latest(_) => "latest",
}
}
}
impl FromStr for ZigVersion {
type Err = ZvError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"master" => Self::placeholder_for_variant("master"),
"stable" => Self::placeholder_for_variant("stable"),
"latest" => Self::placeholder_for_variant("latest"),
_ => {
if let Some((prefix, version_str)) = s.split_once('@') {
let version = Self::parse_normalized_version(version_str)?;
return match prefix {
"stable" => {
if version.pre.is_empty() && version.build.is_empty() {
Ok(ZigVersion::Stable(Some(version)))
} else {
Err(ZvError::General(eyre!(
"stable@<version> only accepts stable versions. '{}' appears to be a pre-release or dev build",
version_str
)))
}
}
"master" => Ok(ZigVersion::Master(Some(version))),
"latest" => Ok(ZigVersion::Latest(Some(version))),
_ => Err(ZvError::General(eyre!(
"Invalid version prefix: {}. Supported: stable@<version>",
prefix
))),
};
}
if s.chars().next().is_some_and(|c| c.is_ascii_digit()) {
Self::parse_normalized_version(s).map(ZigVersion::Semver)
} else {
Err(ZvError::General(eyre!(
"Not a valid Zig version string: {}",
s
)))
}
}
}
}
}
impl Hash for ZigVersion {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
ZigVersion::Semver(v) => {
state.write_u8(0);
v.hash(state);
}
ZigVersion::Master(v) => {
state.write_u8(1);
v.hash(state);
}
ZigVersion::Stable(v) => {
state.write_u8(2);
v.hash(state);
}
ZigVersion::Latest(v) => {
state.write_u8(3);
v.hash(state);
}
}
}
}
impl PartialEq for ZigVersion {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(ZigVersion::Semver(a), ZigVersion::Semver(b)) => a == b,
(ZigVersion::Master(a), ZigVersion::Master(b)) => a == b,
(ZigVersion::Stable(a), ZigVersion::Stable(b)) => a == b,
(ZigVersion::Latest(a), ZigVersion::Latest(b)) => a == b,
_ => false,
}
}
}
impl Eq for ZigVersion {}
impl PartialOrd for ZigVersion {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ZigVersion {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (self, other) {
(ZigVersion::Semver(a), ZigVersion::Semver(b)) => a.cmp(b),
(ZigVersion::Master(a), ZigVersion::Master(b)) => a.cmp(b),
(ZigVersion::Stable(a), ZigVersion::Stable(b)) => a.cmp(b),
(ZigVersion::Latest(a), ZigVersion::Latest(b)) => a.cmp(b),
(ZigVersion::Semver(_), ZigVersion::Stable(_)) => Ordering::Less,
(ZigVersion::Semver(_), ZigVersion::Latest(_)) => Ordering::Less,
(ZigVersion::Semver(_), ZigVersion::Master(_)) => Ordering::Less,
(ZigVersion::Stable(_), ZigVersion::Semver(_)) => Ordering::Greater,
(ZigVersion::Stable(_), ZigVersion::Latest(_)) => Ordering::Less,
(ZigVersion::Stable(_), ZigVersion::Master(_)) => Ordering::Less,
(ZigVersion::Latest(_), ZigVersion::Semver(_)) => Ordering::Greater,
(ZigVersion::Latest(_), ZigVersion::Stable(_)) => Ordering::Greater,
(ZigVersion::Latest(_), ZigVersion::Master(_)) => Ordering::Less,
(ZigVersion::Master(_), ZigVersion::Semver(_)) => Ordering::Greater,
(ZigVersion::Master(_), ZigVersion::Stable(_)) => Ordering::Greater,
(ZigVersion::Master(_), ZigVersion::Latest(_)) => Ordering::Greater,
}
}
}
impl Serialize for ZigVersion {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let version_str = match self {
ZigVersion::Semver(version) => version.to_string(),
ZigVersion::Master(Some(version))
| ZigVersion::Stable(Some(version))
| ZigVersion::Latest(Some(version)) => version.to_string(),
ZigVersion::Master(None) | ZigVersion::Stable(None) | ZigVersion::Latest(None) => {
return Err(serde::ser::Error::custom(
"Cannot serialize unresolved version",
));
}
};
serializer.serialize_str(&version_str)
}
}
impl<'de> Deserialize<'de> for ZigVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let version_str = String::deserialize(deserializer)?;
ZigVersion::from_str(&version_str).map_err(de::Error::custom)
}
}
impl fmt::Display for ZigVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ZigVersion::Semver(v) => write!(f, "{}", v),
ZigVersion::Master(Some(v)) => write!(f, "master <{}>", v),
ZigVersion::Master(None) => write!(f, "master <version: unknown>"),
ZigVersion::Stable(Some(v)) => write!(f, "stable <{}>", v),
ZigVersion::Stable(None) => write!(f, "stable <version: unknown>"),
ZigVersion::Latest(Some(v)) => write!(f, "latest <{}>", v),
ZigVersion::Latest(None) => write!(f, "latest <version: unknown>"),
}
}
}
impl From<semver::Version> for ZigVersion {
fn from(version: semver::Version) -> Self {
ZigVersion::Semver(version)
}
}
impl From<&semver::Version> for ZigVersion {
fn from(version: &semver::Version) -> Self {
ZigVersion::Semver(version.clone())
}
}
impl fmt::Display for ResolvedZigVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ResolvedZigVersion::Semver(v) => write!(f, "{}", v),
ResolvedZigVersion::Master(v) => write!(f, "master <{}>", v),
}
}
}
impl From<Version> for ResolvedZigVersion {
fn from(version: Version) -> Self {
ResolvedZigVersion::Semver(version)
}
}
impl From<&Version> for ResolvedZigVersion {
fn from(version: &Version) -> Self {
ResolvedZigVersion::Semver(version.clone())
}
}
impl ResolvedZigVersion {
#[inline]
pub fn version(&self) -> &Version {
match self {
ResolvedZigVersion::Semver(v) => v,
ResolvedZigVersion::Master(v) => v,
}
}
#[inline]
pub fn is_master(&self) -> bool {
matches!(self, ResolvedZigVersion::Master(_))
}
#[inline]
pub fn is_semver(&self) -> bool {
matches!(self, ResolvedZigVersion::Semver(_))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resolved_zig_version_traits() {
let v1 = ResolvedZigVersion::Semver(Version::parse("1.0.0").unwrap());
let v2 = ResolvedZigVersion::Semver(Version::parse("1.0.0").unwrap());
let v3 = ResolvedZigVersion::Semver(Version::parse("2.0.0").unwrap());
let master_version = ResolvedZigVersion::Master(Version::parse("1.5.0").unwrap());
assert_eq!(v1, v2);
assert_ne!(v1, v3);
assert_ne!(v1, master_version);
assert!(v1 < v3);
assert!(v1 < master_version);
let v1_clone = v1.clone();
assert_eq!(v1, v1_clone);
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(v1.clone());
set.insert(v2.clone());
assert_eq!(set.len(), 1); }
#[test]
fn test_resolved_zig_version_display() {
let semver = ResolvedZigVersion::Semver(Version::parse("1.0.0").unwrap());
let master_version = ResolvedZigVersion::Master(Version::parse("1.5.0").unwrap());
assert_eq!(format!("{}", semver), "1.0.0");
assert_eq!(format!("{}", master_version), "master <1.5.0>");
}
#[test]
fn test_resolved_zig_version_methods() {
let semver = ResolvedZigVersion::Semver(Version::parse("1.0.0").unwrap());
let master_version = ResolvedZigVersion::Master(Version::parse("1.5.0").unwrap());
assert!(!semver.is_master());
assert!(master_version.is_master());
assert!(semver.is_semver());
assert!(!master_version.is_semver());
}
}