version-rs/src/prerelease.rs

340 lines
11 KiB
Rust

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use {
crate::{error::Error, MAX_U12},
epoch::DateTime,
std::{cmp, convert::Into, fmt, num::NonZeroU16, str},
};
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
/// A specification for non-production releases
pub enum PreRelease {
Alpha(Option<NonZeroU16>),
Beta(Option<NonZeroU16>),
RC(Option<NonZeroU16>),
Git {
hash: [char; 7],
datetime: DateTime,
},
/// `PreRelease::None` is equivalent to a normal release
None,
}
impl PreRelease {
pub fn is_prerelease(&self) -> bool {
!matches!(self, Self::None)
}
pub fn is_alpha(&self) -> bool {
matches!(self, Self::Alpha(_))
}
pub fn is_beta(&self) -> bool {
matches!(self, Self::Beta(_))
}
pub fn is_rc(&self) -> bool {
matches!(self, Self::RC(_))
}
pub fn is_gitrev(&self) -> bool {
matches!(
self,
Self::Git {
hash: _,
datetime: _
}
)
}
pub fn number(self) -> Option<u16> {
match self {
Self::Alpha(n) | Self::Beta(n) | Self::RC(n) => n.map(Into::into),
_ => None,
}
}
/// Increments the numerical portion of the prerelease spec
/// # Errors
/// Returns `Error::Range` if the operation would overflow
pub fn increment(&mut self) -> Result<(), Error> {
match self {
Self::Alpha(n) | Self::Beta(n) | Self::RC(n) => match n {
Some(num) => {
if u16::from(*num) >= MAX_U12 {
return Err(Error::Range);
}
*num = num.saturating_add(1);
}
None => *n = Some(NonZeroU16::new(2).unwrap()),
},
_ => {}
}
Ok(())
}
/// Promotes the prerelease spec to a higher type eg. alpha->beta or beta->rc
/// If the spec is already `Self::RC(_)` then it becomes a regular release ie.
/// `PreRelease::None`
pub fn promote(&mut self) {
match self {
Self::Alpha(_) => *self = Self::Beta(None),
Self::Beta(_) => *self = Self::RC(None),
Self::RC(_) => *self = Self::None,
_ => {}
}
}
}
impl fmt::Display for PreRelease {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Alpha(Some(v)) => write!(f, "_alpha{v}"),
Self::Alpha(None) => write!(f, "_alpha"),
Self::Beta(Some(v)) => write!(f, "_beta{v}"),
Self::Beta(None) => write!(f, "_beta"),
Self::RC(Some(v)) => write!(f, "_rc{v}"),
Self::RC(None) => write!(f, "_rc"),
Self::Git { hash, datetime } => {
write!(f, "_git_")?;
for c in hash {
write!(f, "{c}")?;
}
write!(f, ".{}", datetime.timestamp())
}
Self::None => Ok(()),
}
}
}
impl str::FromStr for PreRelease {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let max = unsafe { NonZeroU16::new_unchecked(1025) };
match s {
"Alpha" | "alpha" => Ok(Self::Alpha(None)),
"Beta" | "beta" => Ok(Self::Beta(None)),
"RC" | "Rc" | "rc" => Ok(Self::RC(None)),
s if s.starts_with("Alpha") => {
let suf = s.strip_prefix("Alpha").unwrap();
match suf.parse() {
Ok(v) if v < max => Ok(Self::Alpha(Some(v))),
_ => Err(Error::ParsePreRelease),
}
}
s if s.starts_with("alpha") => {
let suf = s.strip_prefix("alpha").unwrap();
match suf.parse() {
Ok(v) if v < max => Ok(Self::Alpha(Some(v))),
_ => Err(Error::ParsePreRelease),
}
}
s if s.starts_with("Beta") => {
let suf = s.strip_prefix("Beta").unwrap();
match suf.parse() {
Ok(v) if v < max => Ok(Self::Beta(Some(v))),
_ => Err(Error::ParsePreRelease),
}
}
s if s.starts_with("beta") => {
let suf = s.strip_prefix("beta").unwrap();
match suf.parse() {
Ok(v) if v < max => Ok(Self::Beta(Some(v))),
_ => Err(Error::ParsePreRelease),
}
}
s if s.starts_with("RC") => {
let suf = s.strip_prefix("RC").unwrap();
match suf.parse() {
Ok(v) if v < max => Ok(Self::RC(Some(v))),
_ => Err(Error::ParsePreRelease),
}
}
s if s.starts_with("Rc") => {
let suf = s.strip_prefix("Rc").unwrap();
match suf.parse() {
Ok(v) if v < max => Ok(Self::RC(Some(v))),
_ => Err(Error::ParsePreRelease),
}
}
s if s.starts_with("rc") => {
let suf = s.strip_prefix("rc").unwrap();
match suf.parse() {
Ok(v) if v < max => Ok(Self::RC(Some(v))),
_ => Err(Error::ParsePreRelease),
}
}
s if s.starts_with("git_") => {
let s = s.strip_prefix("git_").unwrap();
let Some((h, ts)) = s.split_once('.') else {
return Err(Error::ParseGitRev);
};
if h.len() != 7 {
return Err(Error::ParseGitRev);
}
let mut hash = ['x'; 7];
for (idx, c) in h.chars().enumerate() {
hash[idx] = c;
}
let ts: i64 = ts.parse()?;
let datetime = DateTime::from_timestamp(ts);
Ok(Self::Git { hash, datetime })
}
_ => Err(Error::ParsePreRelease),
}
}
}
impl From<PreRelease> for u128 {
fn from(value: PreRelease) -> Self {
let mask: u16 = 0o3_777;
match value {
PreRelease::Git { hash: _, datetime } => {
0o4_000 << 64 | u128::try_from(datetime.timestamp()).unwrap()
}
PreRelease::Alpha(Some(v)) => {
let v = u16::from(v) & mask;
u128::from(v | 0o10_000) << 64
}
PreRelease::Alpha(None) => 0o10_000 << 64,
PreRelease::Beta(Some(v)) => {
let v = u16::from(v) & mask;
u128::from(v | 0o20_000) << 64
}
PreRelease::Beta(None) => 0o20_000 << 64,
PreRelease::RC(Some(v)) => {
let v = u16::from(v) & mask;
u128::from(v | 0o40_000) << 64
}
PreRelease::RC(None) => 0o40_000 << 64,
PreRelease::None => 0o100_000 << 64,
}
}
}
impl PartialEq for PreRelease {
fn eq(&self, other: &Self) -> bool {
let _one = NonZeroU16::new(1).unwrap();
match (self, other) {
(PreRelease::Alpha(Some(n)), PreRelease::Alpha(None))
| (PreRelease::Alpha(None), PreRelease::Alpha(Some(n)))
| (PreRelease::Beta(Some(n)), PreRelease::Beta(None))
| (PreRelease::Beta(None), PreRelease::Beta(Some(n)))
| (PreRelease::RC(Some(n)), PreRelease::RC(None))
| (PreRelease::RC(None), PreRelease::RC(Some(n))) => {
let n: u16 = (*n).into();
n == 1
}
_ => {
let a: u128 = (*self).into();
let b: u128 = (*other).into();
a == b
}
}
}
}
impl Eq for PreRelease {}
impl cmp::Ord for PreRelease {
fn cmp(&self, other: &Self) -> cmp::Ordering {
let a: u128 = (*self).into();
let b: u128 = (*other).into();
a.cmp(&b)
}
}
impl PartialOrd for PreRelease {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_alpha() {
let alpha: PreRelease = "alpha".parse().unwrap();
let alpha1: PreRelease = "Alpha1".parse().unwrap();
let alpha2: PreRelease = "alpha2".parse().unwrap();
let one = NonZeroU16::new(1).unwrap();
let two = NonZeroU16::new(2).unwrap();
assert_eq!(alpha, PreRelease::Alpha(None));
assert_eq!(alpha1, PreRelease::Alpha(Some(one)));
assert_eq!(alpha2, PreRelease::Alpha(Some(two)));
}
#[test]
fn parse_beta() {
assert_eq!(
"beta".parse::<PreRelease>().unwrap(),
PreRelease::Beta(None),
);
assert_eq!(
"beta1".parse::<PreRelease>().unwrap(),
PreRelease::Beta(Some(NonZeroU16::new(1).unwrap()))
);
assert_eq!(
"Beta2".parse::<PreRelease>().unwrap(),
PreRelease::Beta(Some(NonZeroU16::new(2).unwrap()))
);
assert_eq!("Beta2a".parse::<PreRelease>(), Err(Error::ParsePreRelease));
}
#[test]
fn parse_rc() {
assert_eq!(
"rc3".parse::<PreRelease>().unwrap(),
PreRelease::RC(Some(NonZeroU16::new(3).unwrap()))
);
assert_eq!("Rc".parse::<PreRelease>().unwrap(), PreRelease::RC(None));
assert_eq!("RC".parse::<PreRelease>().unwrap(), PreRelease::RC(None));
}
#[test]
fn equality() {
assert_eq!(
PreRelease::Alpha(None),
PreRelease::Alpha(Some(NonZeroU16::new(1).unwrap())),
);
assert_eq!(
PreRelease::Beta(None),
PreRelease::Beta(Some(NonZeroU16::new(1).unwrap()))
);
assert_eq!(
PreRelease::RC(None),
PreRelease::RC(Some(NonZeroU16::new(1).unwrap()))
);
}
#[test]
fn ordering() {
assert!(
PreRelease::Alpha(Some(NonZeroU16::new(2).unwrap()))
> PreRelease::Alpha(Some(NonZeroU16::new(1).unwrap()))
);
assert!(PreRelease::Alpha(Some(NonZeroU16::new(2).unwrap())) > PreRelease::Alpha(None));
assert!(
PreRelease::Beta(Some(NonZeroU16::new(2).unwrap()))
> PreRelease::Beta(Some(NonZeroU16::new(1).unwrap()))
);
assert!(PreRelease::RC(Some(NonZeroU16::new(2).unwrap())) > PreRelease::RC(None));
assert!(
PreRelease::RC(Some(NonZeroU16::new(2).unwrap()))
> PreRelease::RC(Some(NonZeroU16::new(1).unwrap()))
);
assert!(PreRelease::RC(Some(NonZeroU16::new(2).unwrap())) > PreRelease::RC(None));
assert!(PreRelease::None > PreRelease::RC(None));
assert!(PreRelease::None > PreRelease::Beta(None));
assert!(PreRelease::None > PreRelease::Alpha(None));
assert!(PreRelease::RC(None) > PreRelease::Beta(None));
assert!(PreRelease::RC(None) > PreRelease::Alpha(None));
assert!(PreRelease::Beta(None) > PreRelease::Alpha(None));
}
}