340 lines
11 KiB
Rust
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));
|
|
}
|
|
}
|