use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use std::fmt::Formatter;
use std::num::NonZero;
use std::ops::Deref;
use std::sync::LazyLock;
use std::{
borrow::Borrow,
cmp::Ordering,
hash::{Hash, Hasher},
str::FromStr,
sync::Arc,
};
use uv_cache_key::{CacheKey, CacheKeyHasher};
#[derive(Eq, Ord, PartialEq, PartialOrd, Debug, Hash, Clone, Copy)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))]
pub enum Operator {
Equal,
EqualStar,
ExactEqual,
NotEqual,
NotEqualStar,
TildeEqual,
LessThan,
LessThanEqual,
GreaterThan,
GreaterThanEqual,
}
impl Operator {
pub fn negate(self) -> Option<Self> {
Some(match self {
Self::Equal => Self::NotEqual,
Self::EqualStar => Self::NotEqualStar,
Self::ExactEqual => Self::NotEqual,
Self::NotEqual => Self::Equal,
Self::NotEqualStar => Self::EqualStar,
Self::TildeEqual => return None,
Self::LessThan => Self::GreaterThanEqual,
Self::LessThanEqual => Self::GreaterThan,
Self::GreaterThan => Self::LessThanEqual,
Self::GreaterThanEqual => Self::LessThan,
})
}
pub(crate) fn is_local_compatible(self) -> bool {
!matches!(
self,
Self::GreaterThan
| Self::GreaterThanEqual
| Self::LessThan
| Self::LessThanEqual
| Self::TildeEqual
| Self::EqualStar
| Self::NotEqualStar
)
}
pub(crate) fn to_star(self) -> Option<Self> {
match self {
Self::Equal => Some(Self::EqualStar),
Self::NotEqual => Some(Self::NotEqualStar),
_ => None,
}
}
pub fn is_star(self) -> bool {
matches!(self, Self::EqualStar | Self::NotEqualStar)
}
pub fn as_str(self) -> &'static str {
match self {
Self::Equal => "==",
Self::EqualStar => "==",
#[allow(deprecated)]
Self::ExactEqual => "===",
Self::NotEqual => "!=",
Self::NotEqualStar => "!=",
Self::TildeEqual => "~=",
Self::LessThan => "<",
Self::LessThanEqual => "<=",
Self::GreaterThan => ">",
Self::GreaterThanEqual => ">=",
}
}
}
impl FromStr for Operator {
type Err = OperatorParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let operator = match s {
"==" => Self::Equal,
"===" => {
#[cfg(feature = "tracing")]
{
tracing::warn!("Using arbitrary equality (`===`) is discouraged");
}
#[allow(deprecated)]
Self::ExactEqual
}
"!=" => Self::NotEqual,
"~=" => Self::TildeEqual,
"<" => Self::LessThan,
"<=" => Self::LessThanEqual,
">" => Self::GreaterThan,
">=" => Self::GreaterThanEqual,
other => {
return Err(OperatorParseError {
got: other.to_string(),
});
}
};
Ok(operator)
}
}
impl std::fmt::Display for Operator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let operator = self.as_str();
write!(f, "{operator}")
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OperatorParseError {
pub(crate) got: String,
}
impl std::error::Error for OperatorParseError {}
impl std::fmt::Display for OperatorParseError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"no such comparison operator {:?}, must be one of ~= == != <= >= < > ===",
self.got
)
}
}
#[derive(Clone)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))]
pub struct Version {
inner: VersionInner,
}
#[derive(Clone, Debug)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))]
enum VersionInner {
Small { small: VersionSmall },
Full { full: Arc<VersionFull> },
}
impl Version {
#[inline]
pub fn new<I, R>(release_numbers: I) -> Self
where
I: IntoIterator<Item = R>,
R: Borrow<u64>,
{
Self {
inner: VersionInner::Small {
small: VersionSmall::new(),
},
}
.with_release(release_numbers)
}
#[inline]
pub fn any_prerelease(&self) -> bool {
self.is_pre() || self.is_dev()
}
#[inline]
pub fn is_stable(&self) -> bool {
!self.is_pre() && !self.is_dev()
}
#[inline]
pub fn is_pre(&self) -> bool {
self.pre().is_some()
}
#[inline]
pub fn is_dev(&self) -> bool {
self.dev().is_some()
}
#[inline]
pub fn is_post(&self) -> bool {
self.post().is_some()
}
#[inline]
pub fn is_local(&self) -> bool {
!self.local().is_empty()
}
#[inline]
pub fn epoch(&self) -> u64 {
match self.inner {
VersionInner::Small { ref small } => small.epoch(),
VersionInner::Full { ref full } => full.epoch,
}
}
#[inline]
pub fn release(&self) -> Release<'_> {
let inner = match &self.inner {
VersionInner::Small { small } => {
match small.len {
0 => ReleaseInner::Small0([]),
1 => ReleaseInner::Small1([(small.repr >> 0o60) & 0xFFFF]),
2 => ReleaseInner::Small2([
(small.repr >> 0o60) & 0xFFFF,
(small.repr >> 0o50) & 0xFF,
]),
3 => ReleaseInner::Small3([
(small.repr >> 0o60) & 0xFFFF,
(small.repr >> 0o50) & 0xFF,
(small.repr >> 0o40) & 0xFF,
]),
4 => ReleaseInner::Small4([
(small.repr >> 0o60) & 0xFFFF,
(small.repr >> 0o50) & 0xFF,
(small.repr >> 0o40) & 0xFF,
(small.repr >> 0o30) & 0xFF,
]),
_ => unreachable!("{}", small.len),
}
}
VersionInner::Full { full } => ReleaseInner::Full(&full.release),
};
Release { inner }
}
#[inline]
pub fn pre(&self) -> Option<Prerelease> {
match self.inner {
VersionInner::Small { ref small } => small.pre(),
VersionInner::Full { ref full } => full.pre,
}
}
#[inline]
pub fn post(&self) -> Option<u64> {
match self.inner {
VersionInner::Small { ref small } => small.post(),
VersionInner::Full { ref full } => full.post,
}
}
#[inline]
pub fn dev(&self) -> Option<u64> {
match self.inner {
VersionInner::Small { ref small } => small.dev(),
VersionInner::Full { ref full } => full.dev,
}
}
#[inline]
pub fn local(&self) -> LocalVersionSlice<'_> {
match self.inner {
VersionInner::Small { ref small } => small.local_slice(),
VersionInner::Full { ref full } => full.local.as_slice(),
}
}
#[inline]
fn min(&self) -> Option<u64> {
match self.inner {
VersionInner::Small { ref small } => small.min(),
VersionInner::Full { ref full } => full.min,
}
}
#[inline]
fn max(&self) -> Option<u64> {
match self.inner {
VersionInner::Small { ref small } => small.max(),
VersionInner::Full { ref full } => full.max,
}
}
#[inline]
#[must_use]
pub fn with_release<I, R>(mut self, release_numbers: I) -> Self
where
I: IntoIterator<Item = R>,
R: Borrow<u64>,
{
self.clear_release();
for n in release_numbers {
self.push_release(*n.borrow());
}
assert!(
!self.release().is_empty(),
"release must have non-zero size"
);
self
}
#[inline]
#[must_use]
pub fn only_release_at_precision(&self, precision: usize) -> Option<Self> {
let release = self
.release()
.iter()
.copied()
.chain(std::iter::repeat(0))
.take(precision)
.collect::<Vec<_>>();
(!release.is_empty()).then(|| Self::new(release).with_epoch(self.epoch()))
}
#[inline]
fn push_release(&mut self, n: u64) {
if let VersionInner::Small { small } = &mut self.inner {
if small.push_release(n) {
return;
}
}
self.make_full().release.push(n);
}
#[inline]
fn clear_release(&mut self) {
match &mut self.inner {
VersionInner::Small { small } => small.clear_release(),
VersionInner::Full { full } => {
Arc::make_mut(full).release.clear();
}
}
}
#[inline]
#[must_use]
pub(crate) fn with_epoch(mut self, value: u64) -> Self {
if let VersionInner::Small { small } = &mut self.inner {
if small.set_epoch(value) {
return self;
}
}
self.make_full().epoch = value;
self
}
#[inline]
#[must_use]
pub fn with_pre(mut self, value: Option<Prerelease>) -> Self {
if let VersionInner::Small { small } = &mut self.inner {
if small.set_pre(value) {
return self;
}
}
self.make_full().pre = value;
self
}
#[inline]
#[must_use]
pub fn with_post(mut self, value: Option<u64>) -> Self {
if let VersionInner::Small { small } = &mut self.inner {
if small.set_post(value) {
return self;
}
}
self.make_full().post = value;
self
}
#[inline]
#[must_use]
pub(crate) fn with_dev(mut self, value: Option<u64>) -> Self {
if let VersionInner::Small { small } = &mut self.inner {
if small.set_dev(value) {
return self;
}
}
self.make_full().dev = value;
self
}
#[inline]
#[must_use]
pub(crate) fn with_local_segments(mut self, value: Vec<LocalSegment>) -> Self {
if value.is_empty() {
self.without_local()
} else {
self.make_full().local = LocalVersion::Segments(value);
self
}
}
#[inline]
#[must_use]
pub(crate) fn with_local(mut self, value: LocalVersion) -> Self {
match value {
LocalVersion::Segments(segments) => self.with_local_segments(segments),
LocalVersion::Max => {
if let VersionInner::Small { small } = &mut self.inner {
if small.set_local(LocalVersion::Max) {
return self;
}
}
self.make_full().local = value;
self
}
}
}
#[inline]
#[must_use]
pub fn without_local(mut self) -> Self {
if let VersionInner::Small { small } = &mut self.inner {
if small.set_local(LocalVersion::empty()) {
return self;
}
}
self.make_full().local = LocalVersion::empty();
self
}
#[inline]
#[must_use]
pub fn only_release(&self) -> Self {
Self::new(self.release().iter().copied())
}
#[inline]
#[must_use]
pub(crate) fn only_minor_release(&self) -> Self {
Self::new(self.release().iter().take(2).copied())
}
#[inline]
#[must_use]
pub fn only_release_trimmed(&self) -> Self {
if let Some(last_non_zero) = self.release().iter().rposition(|segment| *segment != 0) {
if last_non_zero + 1 == self.release().len()
&& self.epoch() == 0
&& self.pre().is_none()
&& self.post().is_none()
&& self.dev().is_none()
&& self.local().is_empty()
&& self.min().is_none()
&& self.max().is_none()
{
self.clone()
} else {
Self::new(self.release().iter().take(last_non_zero + 1).copied())
}
} else {
Self::new([0])
}
}
#[inline]
#[must_use]
pub fn without_trailing_zeros(self) -> Self {
let mut release = self.release().to_vec();
while let Some(0) = release.last() {
release.pop();
}
self.with_release(release)
}
pub fn bump(&mut self, bump: BumpCommand) {
let full = self.make_full();
match bump {
BumpCommand::BumpRelease { index, value } => {
full.pre = None;
full.post = None;
full.dev = None;
let old_parts = &full.release;
let len = old_parts.len().max(index + 1);
let new_release_vec = (0..len)
.map(|i| match i.cmp(&index) {
Ordering::Less => old_parts.get(i).copied().unwrap_or(0),
Ordering::Equal => {
value.unwrap_or_else(|| old_parts.get(i).copied().unwrap_or(0) + 1)
}
Ordering::Greater => 0,
})
.collect::<Vec<u64>>();
full.release = new_release_vec;
}
BumpCommand::MakeStable => {
full.pre = None;
full.post = None;
full.dev = None;
}
BumpCommand::BumpPrerelease { kind, value } => {
full.post = None;
full.dev = None;
if let Some(value) = value {
full.pre = Some(Prerelease {
kind,
number: value,
});
} else {
if let Some(prerelease) = &mut full.pre
&& prerelease.kind == kind
{
prerelease.number += 1;
return;
}
full.pre = Some(Prerelease { kind, number: 1 });
}
}
BumpCommand::BumpPost { value } => {
full.dev = None;
if let Some(value) = value {
full.post = Some(value);
} else {
if let Some(post) = &mut full.post {
*post += 1;
} else {
full.post = Some(1);
}
}
}
BumpCommand::BumpDev { value } => {
if let Some(value) = value {
full.dev = Some(value);
} else {
if let Some(dev) = &mut full.dev {
*dev += 1;
} else {
full.dev = Some(1);
}
}
}
}
}
#[inline]
#[must_use]
pub fn with_min(mut self, value: Option<u64>) -> Self {
debug_assert!(!self.is_pre(), "min is not allowed on pre-release versions");
debug_assert!(!self.is_dev(), "min is not allowed on dev versions");
if let VersionInner::Small { small } = &mut self.inner {
if small.set_min(value) {
return self;
}
}
self.make_full().min = value;
self
}
#[inline]
#[must_use]
pub fn with_max(mut self, value: Option<u64>) -> Self {
debug_assert!(
!self.is_post(),
"max is not allowed on post-release versions"
);
debug_assert!(!self.is_dev(), "max is not allowed on dev versions");
if let VersionInner::Small { small } = &mut self.inner {
if small.set_max(value) {
return self;
}
}
self.make_full().max = value;
self
}
fn make_full(&mut self) -> &mut VersionFull {
if let VersionInner::Small { ref small } = self.inner {
let full = VersionFull {
epoch: small.epoch(),
release: self.release().to_vec(),
min: small.min(),
max: small.max(),
pre: small.pre(),
post: small.post(),
dev: small.dev(),
local: small.local(),
};
*self = Self {
inner: VersionInner::Full {
full: Arc::new(full),
},
};
}
match &mut self.inner {
VersionInner::Full { full } => Arc::make_mut(full),
VersionInner::Small { .. } => unreachable!(),
}
}
#[cold]
#[inline(never)]
fn cmp_slow(&self, other: &Self) -> Ordering {
match self.epoch().cmp(&other.epoch()) {
Ordering::Less => {
return Ordering::Less;
}
Ordering::Equal => {}
Ordering::Greater => {
return Ordering::Greater;
}
}
match compare_release(&self.release(), &other.release()) {
Ordering::Less => {
return Ordering::Less;
}
Ordering::Equal => {}
Ordering::Greater => {
return Ordering::Greater;
}
}
sortable_tuple(self).cmp(&sortable_tuple(other))
}
}
impl<'de> Deserialize<'de> for Version {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct Visitor;
impl de::Visitor<'_> for Visitor {
type Value = Version;
fn expecting(&self, f: &mut Formatter) -> std::fmt::Result {
f.write_str("a string")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
Version::from_str(v).map_err(de::Error::custom)
}
}
deserializer.deserialize_str(Visitor)
}
}
impl Serialize for Version {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.epoch() != 0 {
write!(f, "{}!", self.epoch())?;
}
let release = self.release();
let mut release_iter = release.iter();
if let Some(first) = release_iter.next() {
write!(f, "{first}")?;
for n in release_iter {
write!(f, ".{n}")?;
}
}
if let Some(Prerelease { kind, number }) = self.pre() {
write!(f, "{kind}{number}")?;
}
if let Some(post) = self.post() {
write!(f, ".post{post}")?;
}
if let Some(dev) = self.dev() {
write!(f, ".dev{dev}")?;
}
if !self.local().is_empty() {
match self.local() {
LocalVersionSlice::Segments(_) => {
write!(f, "+{}", self.local())?;
}
LocalVersionSlice::Max => {
write!(f, "+")?;
}
}
}
Ok(())
}
}
impl std::fmt::Debug for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{self}\"")
}
}
impl PartialEq<Self> for Version {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for Version {}
impl Hash for Version {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.epoch().hash(state);
for i in self.release().iter().rev().skip_while(|x| **x == 0) {
i.hash(state);
}
self.pre().hash(state);
self.dev().hash(state);
self.post().hash(state);
self.local().hash(state);
}
}
impl CacheKey for Version {
fn cache_key(&self, state: &mut CacheKeyHasher) {
self.epoch().cache_key(state);
let release = self.release();
release.len().cache_key(state);
for segment in release.iter() {
segment.cache_key(state);
}
if let Some(pre) = self.pre() {
1u8.cache_key(state);
match pre.kind {
PrereleaseKind::Alpha => 0u8.cache_key(state),
PrereleaseKind::Beta => 1u8.cache_key(state),
PrereleaseKind::Rc => 2u8.cache_key(state),
}
pre.number.cache_key(state);
} else {
0u8.cache_key(state);
}
if let Some(post) = self.post() {
1u8.cache_key(state);
post.cache_key(state);
} else {
0u8.cache_key(state);
}
if let Some(dev) = self.dev() {
1u8.cache_key(state);
dev.cache_key(state);
} else {
0u8.cache_key(state);
}
self.local().cache_key(state);
}
}
impl PartialOrd<Self> for Version {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Version {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
match (&self.inner, &other.inner) {
(VersionInner::Small { small: small1 }, VersionInner::Small { small: small2 }) => {
small1.repr.cmp(&small2.repr)
}
_ => self.cmp_slow(other),
}
}
}
impl FromStr for Version {
type Err = VersionParseError;
fn from_str(version: &str) -> Result<Self, Self::Err> {
Parser::new(version.as_bytes()).parse()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum BumpCommand {
BumpRelease {
index: usize,
value: Option<u64>,
},
BumpPrerelease {
kind: PrereleaseKind,
value: Option<u64>,
},
MakeStable,
BumpPost {
value: Option<u64>,
},
BumpDev {
value: Option<u64>,
},
}
#[derive(Clone, Debug)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))]
struct VersionSmall {
len: u8,
repr: u64,
_force_niche: NonZero<u8>,
}
impl VersionSmall {
const SUFFIX_MIN: u64 = 0;
const SUFFIX_DEV: u64 = 1;
const SUFFIX_PRE_ALPHA: u64 = 2;
const SUFFIX_PRE_BETA: u64 = 3;
const SUFFIX_PRE_RC: u64 = 4;
const SUFFIX_NONE: u64 = 5;
const SUFFIX_LOCAL: u64 = 6;
const SUFFIX_POST: u64 = 7;
const SUFFIX_MAX: u64 = 8;
const SUFFIX_RELEASE_MASK: u64 = 0xFFFF_FFFF_FF00_0000;
const SUFFIX_VERSION_MASK: u64 = 0x000F_FFFF;
const SUFFIX_VERSION_BIT_LEN: u64 = 20;
const SUFFIX_KIND_MASK: u64 = 0b1111;
#[inline]
fn new() -> Self {
Self {
_force_niche: NonZero::<u8>::MIN,
repr: Self::SUFFIX_NONE << Self::SUFFIX_VERSION_BIT_LEN,
len: 0,
}
}
#[inline]
#[expect(clippy::unused_self)]
fn epoch(&self) -> u64 {
0
}
#[inline]
#[expect(clippy::unused_self)]
fn set_epoch(&mut self, value: u64) -> bool {
if value != 0 {
return false;
}
true
}
#[inline]
fn clear_release(&mut self) {
self.repr &= !Self::SUFFIX_RELEASE_MASK;
self.len = 0;
}
#[inline]
fn push_release(&mut self, n: u64) -> bool {
if self.len == 0 {
if n > u64::from(u16::MAX) {
return false;
}
self.repr |= n << 48;
self.len = 1;
true
} else {
if n > u64::from(u8::MAX) {
return false;
}
if self.len >= 4 {
return false;
}
let shift = 48 - (usize::from(self.len) * 8);
self.repr |= n << shift;
self.len += 1;
true
}
}
#[inline]
fn post(&self) -> Option<u64> {
if self.suffix_kind() == Self::SUFFIX_POST {
Some(self.suffix_version())
} else {
None
}
}
#[inline]
fn set_post(&mut self, value: Option<u64>) -> bool {
let suffix_kind = self.suffix_kind();
if !(suffix_kind == Self::SUFFIX_NONE || suffix_kind == Self::SUFFIX_POST) {
return value.is_none();
}
match value {
None => {
self.set_suffix_kind(Self::SUFFIX_NONE);
}
Some(number) => {
if number > Self::SUFFIX_VERSION_MASK {
return false;
}
self.set_suffix_kind(Self::SUFFIX_POST);
self.set_suffix_version(number);
}
}
true
}
#[inline]
fn pre(&self) -> Option<Prerelease> {
let (kind, number) = (self.suffix_kind(), self.suffix_version());
if kind == Self::SUFFIX_PRE_ALPHA {
Some(Prerelease {
kind: PrereleaseKind::Alpha,
number,
})
} else if kind == Self::SUFFIX_PRE_BETA {
Some(Prerelease {
kind: PrereleaseKind::Beta,
number,
})
} else if kind == Self::SUFFIX_PRE_RC {
Some(Prerelease {
kind: PrereleaseKind::Rc,
number,
})
} else {
None
}
}
#[inline]
fn set_pre(&mut self, value: Option<Prerelease>) -> bool {
let suffix_kind = self.suffix_kind();
if !(suffix_kind == Self::SUFFIX_NONE
|| suffix_kind == Self::SUFFIX_PRE_ALPHA
|| suffix_kind == Self::SUFFIX_PRE_BETA
|| suffix_kind == Self::SUFFIX_PRE_RC)
{
return value.is_none();
}
match value {
None => {
self.set_suffix_kind(Self::SUFFIX_NONE);
}
Some(Prerelease { kind, number }) => {
if number > Self::SUFFIX_VERSION_MASK {
return false;
}
match kind {
PrereleaseKind::Alpha => {
self.set_suffix_kind(Self::SUFFIX_PRE_ALPHA);
}
PrereleaseKind::Beta => {
self.set_suffix_kind(Self::SUFFIX_PRE_BETA);
}
PrereleaseKind::Rc => {
self.set_suffix_kind(Self::SUFFIX_PRE_RC);
}
}
self.set_suffix_version(number);
}
}
true
}
#[inline]
fn dev(&self) -> Option<u64> {
if self.suffix_kind() == Self::SUFFIX_DEV {
Some(self.suffix_version())
} else {
None
}
}
#[inline]
fn set_dev(&mut self, value: Option<u64>) -> bool {
let suffix_kind = self.suffix_kind();
if !(suffix_kind == Self::SUFFIX_NONE || suffix_kind == Self::SUFFIX_DEV) {
return value.is_none();
}
match value {
None => {
self.set_suffix_kind(Self::SUFFIX_NONE);
}
Some(number) => {
if number > Self::SUFFIX_VERSION_MASK {
return false;
}
self.set_suffix_kind(Self::SUFFIX_DEV);
self.set_suffix_version(number);
}
}
true
}
#[inline]
fn min(&self) -> Option<u64> {
if self.suffix_kind() == Self::SUFFIX_MIN {
Some(self.suffix_version())
} else {
None
}
}
#[inline]
fn set_min(&mut self, value: Option<u64>) -> bool {
let suffix_kind = self.suffix_kind();
if !(suffix_kind == Self::SUFFIX_NONE || suffix_kind == Self::SUFFIX_MIN) {
return value.is_none();
}
match value {
None => {
self.set_suffix_kind(Self::SUFFIX_NONE);
}
Some(number) => {
if number > Self::SUFFIX_VERSION_MASK {
return false;
}
self.set_suffix_kind(Self::SUFFIX_MIN);
self.set_suffix_version(number);
}
}
true
}
#[inline]
fn max(&self) -> Option<u64> {
if self.suffix_kind() == Self::SUFFIX_MAX {
Some(self.suffix_version())
} else {
None
}
}
#[inline]
fn set_max(&mut self, value: Option<u64>) -> bool {
let suffix_kind = self.suffix_kind();
if !(suffix_kind == Self::SUFFIX_NONE || suffix_kind == Self::SUFFIX_MAX) {
return value.is_none();
}
match value {
None => {
self.set_suffix_kind(Self::SUFFIX_NONE);
}
Some(number) => {
if number > Self::SUFFIX_VERSION_MASK {
return false;
}
self.set_suffix_kind(Self::SUFFIX_MAX);
self.set_suffix_version(number);
}
}
true
}
#[inline]
fn local(&self) -> LocalVersion {
if self.suffix_kind() == Self::SUFFIX_LOCAL {
LocalVersion::Max
} else {
LocalVersion::empty()
}
}
#[inline]
fn local_slice(&self) -> LocalVersionSlice<'_> {
if self.suffix_kind() == Self::SUFFIX_LOCAL {
LocalVersionSlice::Max
} else {
LocalVersionSlice::empty()
}
}
#[inline]
fn set_local(&mut self, value: LocalVersion) -> bool {
let suffix_kind = self.suffix_kind();
if !(suffix_kind == Self::SUFFIX_NONE || suffix_kind == Self::SUFFIX_LOCAL) {
return value.is_empty();
}
match value {
LocalVersion::Max => {
self.set_suffix_kind(Self::SUFFIX_LOCAL);
true
}
LocalVersion::Segments(segments) if segments.is_empty() => {
self.set_suffix_kind(Self::SUFFIX_NONE);
true
}
LocalVersion::Segments(_) => false,
}
}
#[inline]
fn suffix_kind(&self) -> u64 {
let kind = (self.repr >> Self::SUFFIX_VERSION_BIT_LEN) & Self::SUFFIX_KIND_MASK;
debug_assert!(kind <= Self::SUFFIX_MAX);
kind
}
#[inline]
fn set_suffix_kind(&mut self, kind: u64) {
debug_assert!(kind <= Self::SUFFIX_MAX);
self.repr &= !(Self::SUFFIX_KIND_MASK << Self::SUFFIX_VERSION_BIT_LEN);
self.repr |= kind << Self::SUFFIX_VERSION_BIT_LEN;
if kind == Self::SUFFIX_NONE || kind == Self::SUFFIX_LOCAL {
self.set_suffix_version(0);
}
}
#[inline]
fn suffix_version(&self) -> u64 {
self.repr & Self::SUFFIX_VERSION_MASK
}
#[inline]
fn set_suffix_version(&mut self, value: u64) {
debug_assert!(value <= Self::SUFFIX_VERSION_MASK);
self.repr &= !Self::SUFFIX_VERSION_MASK;
self.repr |= value;
}
}
#[derive(Clone, Debug)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))]
struct VersionFull {
epoch: u64,
release: Vec<u64>,
pre: Option<Prerelease>,
post: Option<u64>,
dev: Option<u64>,
local: LocalVersion,
min: Option<u64>,
max: Option<u64>,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct VersionPattern {
version: Version,
wildcard: bool,
}
impl VersionPattern {
#[inline]
pub fn verbatim(version: Version) -> Self {
Self {
version,
wildcard: false,
}
}
#[inline]
pub fn wildcard(version: Version) -> Self {
Self {
version,
wildcard: true,
}
}
#[inline]
pub fn version(&self) -> &Version {
&self.version
}
#[inline]
pub(crate) fn into_version(self) -> Version {
self.version
}
#[inline]
pub(crate) fn is_wildcard(&self) -> bool {
self.wildcard
}
}
impl FromStr for VersionPattern {
type Err = VersionPatternParseError;
fn from_str(version: &str) -> Result<Self, VersionPatternParseError> {
Parser::new(version.as_bytes()).parse_pattern()
}
}
pub struct Release<'a> {
inner: ReleaseInner<'a>,
}
enum ReleaseInner<'a> {
Small0([u64; 0]),
Small1([u64; 1]),
Small2([u64; 2]),
Small3([u64; 3]),
Small4([u64; 4]),
Full(&'a [u64]),
}
impl Deref for Release<'_> {
type Target = [u64];
fn deref(&self) -> &Self::Target {
match &self.inner {
ReleaseInner::Small0(v) => v,
ReleaseInner::Small1(v) => v,
ReleaseInner::Small2(v) => v,
ReleaseInner::Small3(v) => v,
ReleaseInner::Small4(v) => v,
ReleaseInner::Full(v) => v,
}
}
}
#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, Ord, PartialOrd)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))]
pub struct Prerelease {
pub kind: PrereleaseKind,
pub number: u64,
}
#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, Ord, PartialOrd)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))]
pub enum PrereleaseKind {
Alpha,
Beta,
Rc,
}
impl std::fmt::Display for PrereleaseKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Alpha => write!(f, "a"),
Self::Beta => write!(f, "b"),
Self::Rc => write!(f, "rc"),
}
}
}
impl std::fmt::Display for Prerelease {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.kind, self.number)
}
}
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))]
pub enum LocalVersion {
Segments(Vec<LocalSegment>),
Max,
}
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub enum LocalVersionSlice<'a> {
Segments(&'a [LocalSegment]),
Max,
}
impl LocalVersion {
fn empty() -> Self {
Self::Segments(Vec::new())
}
fn is_empty(&self) -> bool {
match self {
Self::Segments(segments) => segments.is_empty(),
Self::Max => false,
}
}
fn as_slice(&self) -> LocalVersionSlice<'_> {
match self {
Self::Segments(segments) => LocalVersionSlice::Segments(segments),
Self::Max => LocalVersionSlice::Max,
}
}
}
impl std::fmt::Display for LocalVersionSlice<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Segments(segments) => {
for (i, segment) in segments.iter().enumerate() {
if i > 0 {
write!(f, ".")?;
}
write!(f, "{segment}")?;
}
Ok(())
}
Self::Max => write!(f, "[max]"),
}
}
}
impl CacheKey for LocalVersionSlice<'_> {
fn cache_key(&self, state: &mut CacheKeyHasher) {
match self {
Self::Segments(segments) => {
0u8.cache_key(state);
segments.len().cache_key(state);
for segment in *segments {
segment.cache_key(state);
}
}
Self::Max => {
1u8.cache_key(state);
}
}
}
}
impl PartialOrd for LocalVersionSlice<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for LocalVersionSlice<'_> {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(LocalVersionSlice::Segments(lv1), LocalVersionSlice::Segments(lv2)) => lv1.cmp(lv2),
(LocalVersionSlice::Segments(_), LocalVersionSlice::Max) => Ordering::Less,
(LocalVersionSlice::Max, LocalVersionSlice::Segments(_)) => Ordering::Greater,
(LocalVersionSlice::Max, LocalVersionSlice::Max) => Ordering::Equal,
}
}
}
impl LocalVersionSlice<'_> {
const fn empty() -> Self {
Self::Segments(&[])
}
pub fn is_empty(&self) -> bool {
matches!(self, &Self::Segments(&[]))
}
}
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))]
pub enum LocalSegment {
String(String),
Number(u64),
}
impl std::fmt::Display for LocalSegment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::String(string) => write!(f, "{string}"),
Self::Number(number) => write!(f, "{number}"),
}
}
}
impl CacheKey for LocalSegment {
fn cache_key(&self, state: &mut CacheKeyHasher) {
match self {
Self::String(string) => {
0u8.cache_key(state);
string.cache_key(state);
}
Self::Number(number) => {
1u8.cache_key(state);
number.cache_key(state);
}
}
}
}
impl PartialOrd for LocalSegment {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for LocalSegment {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Self::Number(n1), Self::Number(n2)) => n1.cmp(n2),
(Self::String(s1), Self::String(s2)) => s1.cmp(s2),
(Self::Number(_), Self::String(_)) => Ordering::Greater,
(Self::String(_), Self::Number(_)) => Ordering::Less,
}
}
}
#[derive(Debug)]
struct Parser<'a> {
v: &'a [u8],
i: usize,
epoch: u64,
release: ReleaseNumbers,
pre: Option<Prerelease>,
post: Option<u64>,
dev: Option<u64>,
local: Vec<LocalSegment>,
wildcard: bool,
}
impl<'a> Parser<'a> {
#[expect(clippy::byte_char_slices)]
const SEPARATOR: ByteSet = ByteSet::new(&[b'.', b'_', b'-']);
fn new(version: &'a [u8]) -> Self {
Parser {
v: version,
i: 0,
epoch: 0,
release: ReleaseNumbers::new(),
pre: None,
post: None,
dev: None,
local: vec![],
wildcard: false,
}
}
fn parse(self) -> Result<Version, VersionParseError> {
match self.parse_pattern() {
Ok(vpat) => {
if vpat.is_wildcard() {
Err(ErrorKind::Wildcard.into())
} else {
Ok(vpat.into_version())
}
}
Err(err) => match *err.kind {
PatternErrorKind::Version(err) => Err(err),
PatternErrorKind::WildcardNotTrailing => Err(ErrorKind::Wildcard.into()),
},
}
}
fn parse_pattern(mut self) -> Result<VersionPattern, VersionPatternParseError> {
if let Some(vpat) = self.parse_fast() {
return Ok(vpat);
}
self.bump_while(|byte| byte.is_ascii_whitespace());
self.bump_if("v");
self.parse_epoch_and_initial_release()?;
self.parse_rest_of_release()?;
if self.parse_wildcard()? {
return Ok(self.into_pattern());
}
self.parse_pre()?;
self.parse_post()?;
self.parse_dev()?;
self.parse_local()?;
self.bump_while(|byte| byte.is_ascii_whitespace());
if !self.is_done() {
let version = String::from_utf8_lossy(&self.v[..self.i]).into_owned();
let remaining = String::from_utf8_lossy(&self.v[self.i..]).into_owned();
return Err(ErrorKind::UnexpectedEnd { version, remaining }.into());
}
Ok(self.into_pattern())
}
fn parse_fast(&self) -> Option<VersionPattern> {
let (mut prev_digit, mut cur, mut release, mut len) = (false, 0u8, [0u8; 4], 0u8);
for &byte in self.v {
if byte == b'.' {
if !prev_digit {
return None;
}
prev_digit = false;
*release.get_mut(usize::from(len))? = cur;
len += 1;
cur = 0;
} else {
let digit = byte.checked_sub(b'0')?;
if digit > 9 {
return None;
}
prev_digit = true;
cur = cur.checked_mul(10)?.checked_add(digit)?;
}
}
if !prev_digit {
return None;
}
*release.get_mut(usize::from(len))? = cur;
len += 1;
let small = VersionSmall {
_force_niche: NonZero::<u8>::MIN,
repr: (u64::from(release[0]) << 48)
| (u64::from(release[1]) << 40)
| (u64::from(release[2]) << 32)
| (u64::from(release[3]) << 24)
| (VersionSmall::SUFFIX_NONE << VersionSmall::SUFFIX_VERSION_BIT_LEN),
len,
};
let inner = VersionInner::Small { small };
let version = Version { inner };
Some(VersionPattern {
version,
wildcard: false,
})
}
fn parse_epoch_and_initial_release(&mut self) -> Result<(), VersionPatternParseError> {
let first_number = self.parse_number()?.ok_or(ErrorKind::NoLeadingNumber)?;
let first_release_number = if self.bump_if("!") {
self.epoch = first_number;
self.parse_number()?
.ok_or(ErrorKind::NoLeadingReleaseNumber)?
} else {
first_number
};
self.release.push(first_release_number);
Ok(())
}
fn parse_rest_of_release(&mut self) -> Result<(), VersionPatternParseError> {
while self.bump_if(".") {
let Some(n) = self.parse_number()? else {
self.unbump();
break;
};
self.release.push(n);
}
Ok(())
}
fn parse_wildcard(&mut self) -> Result<bool, VersionPatternParseError> {
if !self.bump_if(".*") {
return Ok(false);
}
if !self.is_done() {
return Err(PatternErrorKind::WildcardNotTrailing.into());
}
self.wildcard = true;
Ok(true)
}
fn parse_pre(&mut self) -> Result<(), VersionPatternParseError> {
const SPELLINGS: StringSet =
StringSet::new(&["alpha", "beta", "preview", "pre", "rc", "a", "b", "c"]);
const MAP: &[PrereleaseKind] = &[
PrereleaseKind::Alpha,
PrereleaseKind::Beta,
PrereleaseKind::Rc,
PrereleaseKind::Rc,
PrereleaseKind::Rc,
PrereleaseKind::Alpha,
PrereleaseKind::Beta,
PrereleaseKind::Rc,
];
let oldpos = self.i;
self.bump_if_byte_set(&Parser::SEPARATOR);
let Some(spelling) = self.bump_if_string_set(&SPELLINGS) else {
self.reset(oldpos);
return Ok(());
};
let kind = MAP[spelling];
self.bump_if_byte_set(&Parser::SEPARATOR);
let number = self.parse_number()?.unwrap_or(0);
self.pre = Some(Prerelease { kind, number });
Ok(())
}
fn parse_post(&mut self) -> Result<(), VersionPatternParseError> {
const SPELLINGS: StringSet = StringSet::new(&["post", "rev", "r"]);
let oldpos = self.i;
if self.bump_if("-") {
if let Some(n) = self.parse_number()? {
self.post = Some(n);
return Ok(());
}
self.reset(oldpos);
}
self.bump_if_byte_set(&Parser::SEPARATOR);
if self.bump_if_string_set(&SPELLINGS).is_none() {
self.reset(oldpos);
return Ok(());
}
self.bump_if_byte_set(&Parser::SEPARATOR);
self.post = Some(self.parse_number()?.unwrap_or(0));
Ok(())
}
fn parse_dev(&mut self) -> Result<(), VersionPatternParseError> {
let oldpos = self.i;
self.bump_if_byte_set(&Parser::SEPARATOR);
if !self.bump_if("dev") {
self.reset(oldpos);
return Ok(());
}
self.bump_if_byte_set(&Parser::SEPARATOR);
self.dev = Some(self.parse_number()?.unwrap_or(0));
Ok(())
}
fn parse_local(&mut self) -> Result<(), VersionPatternParseError> {
if !self.bump_if("+") {
return Ok(());
}
let mut precursor = '+';
loop {
let first = self.bump_while(|byte| byte.is_ascii_alphanumeric());
if first.is_empty() {
return Err(ErrorKind::LocalEmpty { precursor }.into());
}
self.local.push(if let Ok(number) = parse_u64(first) {
LocalSegment::Number(number)
} else {
let string = String::from_utf8(first.to_ascii_lowercase())
.expect("ASCII alphanumerics are always valid UTF-8");
LocalSegment::String(string)
});
let Some(byte) = self.bump_if_byte_set(&Parser::SEPARATOR) else {
break;
};
precursor = char::from(byte);
}
Ok(())
}
fn parse_number(&mut self) -> Result<Option<u64>, VersionPatternParseError> {
let digits = self.bump_while(|ch| ch.is_ascii_digit());
if digits.is_empty() {
return Ok(None);
}
let n = parse_u64(digits)?;
if n == u64::MAX {
return Err(ErrorKind::NumberTooBig {
bytes: digits.to_vec(),
}
.into());
}
Ok(Some(n))
}
fn into_pattern(self) -> VersionPattern {
assert!(
self.release.len() > 0,
"version with no release numbers is invalid"
);
let version = Version::new(self.release.as_slice())
.with_epoch(self.epoch)
.with_pre(self.pre)
.with_post(self.post)
.with_dev(self.dev)
.with_local(LocalVersion::Segments(self.local));
VersionPattern {
version,
wildcard: self.wildcard,
}
}
fn bump_while(&mut self, mut predicate: impl FnMut(u8) -> bool) -> &'a [u8] {
let start = self.i;
while !self.is_done() && predicate(self.byte()) {
self.i = self.i.saturating_add(1);
}
&self.v[start..self.i]
}
fn bump_if(&mut self, string: &str) -> bool {
if self.is_done() {
return false;
}
if starts_with_ignore_ascii_case(string.as_bytes(), &self.v[self.i..]) {
self.i = self
.i
.checked_add(string.len())
.expect("valid offset because of prefix");
true
} else {
false
}
}
fn bump_if_string_set(&mut self, set: &StringSet) -> Option<usize> {
let index = set.starts_with(&self.v[self.i..])?;
let found = &set.strings[index];
self.i = self
.i
.checked_add(found.len())
.expect("valid offset because of prefix");
Some(index)
}
fn bump_if_byte_set(&mut self, set: &ByteSet) -> Option<u8> {
let found = set.starts_with(&self.v[self.i..])?;
self.i = self
.i
.checked_add(1)
.expect("valid offset because of prefix");
Some(found)
}
fn unbump(&mut self) {
self.i = self.i.checked_sub(1).expect("not at beginning of input");
}
fn reset(&mut self, offset: usize) {
assert!(offset <= self.v.len());
self.i = offset;
}
fn byte(&self) -> u8 {
self.v[self.i]
}
fn is_done(&self) -> bool {
self.i >= self.v.len()
}
}
#[derive(Debug)]
enum ReleaseNumbers {
Inline { numbers: [u64; 4], len: usize },
Vec(Vec<u64>),
}
impl ReleaseNumbers {
fn new() -> Self {
Self::Inline {
numbers: [0; 4],
len: 0,
}
}
fn push(&mut self, n: u64) {
match *self {
Self::Inline {
ref mut numbers,
ref mut len,
} => {
assert!(*len <= 4);
if *len == 4 {
let mut numbers = numbers.to_vec();
numbers.push(n);
*self = Self::Vec(numbers.clone());
} else {
numbers[*len] = n;
*len += 1;
}
}
Self::Vec(ref mut numbers) => {
numbers.push(n);
}
}
}
fn len(&self) -> usize {
self.as_slice().len()
}
fn as_slice(&self) -> &[u64] {
match self {
Self::Inline { numbers, len } => &numbers[..*len],
Self::Vec(vec) => vec,
}
}
}
struct StringSet {
first_byte: ByteSet,
strings: &'static [&'static str],
}
impl StringSet {
const fn new(strings: &'static [&'static str]) -> Self {
assert!(
strings.len() <= 20,
"only a small number of strings are supported"
);
let (mut firsts, mut firsts_len) = ([0u8; 20], 0);
let mut i = 0;
while i < strings.len() {
assert!(
!strings[i].is_empty(),
"every string in set should be non-empty",
);
firsts[firsts_len] = strings[i].as_bytes()[0];
firsts_len += 1;
i += 1;
}
let first_byte = ByteSet::new(&firsts);
Self {
first_byte,
strings,
}
}
fn starts_with(&self, haystack: &[u8]) -> Option<usize> {
let first_byte = self.first_byte.starts_with(haystack)?;
for (i, &string) in self.strings.iter().enumerate() {
let bytes = string.as_bytes();
if bytes[0].eq_ignore_ascii_case(&first_byte)
&& starts_with_ignore_ascii_case(bytes, haystack)
{
return Some(i);
}
}
None
}
}
struct ByteSet {
set: [bool; 256],
}
impl ByteSet {
const fn new(bytes: &[u8]) -> Self {
let mut set = [false; 256];
let mut i = 0;
while i < bytes.len() {
set[bytes[i].to_ascii_uppercase() as usize] = true;
set[bytes[i].to_ascii_lowercase() as usize] = true;
i += 1;
}
Self { set }
}
fn starts_with(&self, haystack: &[u8]) -> Option<u8> {
let byte = *haystack.first()?;
if self.contains(byte) {
Some(byte)
} else {
None
}
}
fn contains(&self, byte: u8) -> bool {
self.set[usize::from(byte)]
}
}
impl std::fmt::Debug for ByteSet {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let mut set = f.debug_set();
for byte in 0..=255 {
if self.contains(byte) {
set.entry(&char::from(byte));
}
}
set.finish()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VersionParseError {
kind: Box<ErrorKind>,
}
impl std::error::Error for VersionParseError {}
impl std::fmt::Display for VersionParseError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self.kind {
ErrorKind::Wildcard => write!(f, "wildcards are not allowed in a version"),
ErrorKind::InvalidDigit { got } if got.is_ascii() => {
write!(f, "expected ASCII digit, but found {:?}", char::from(got))
}
ErrorKind::InvalidDigit { got } => {
write!(
f,
"expected ASCII digit, but found non-ASCII byte \\x{got:02X}"
)
}
ErrorKind::NumberTooBig { ref bytes } => {
let string = match std::str::from_utf8(bytes) {
Ok(v) => v,
Err(err) => {
std::str::from_utf8(&bytes[..err.valid_up_to()]).expect("valid UTF-8")
}
};
write!(
f,
"expected number less than or equal to {}, \
but number found in {string:?} exceeds it",
u64::MAX - 1,
)
}
ErrorKind::NoLeadingNumber => {
write!(
f,
"expected version to start with a number, \
but no leading ASCII digits were found"
)
}
ErrorKind::NoLeadingReleaseNumber => {
write!(
f,
"expected version to have a non-empty release component after an epoch, \
but no ASCII digits after the epoch were found"
)
}
ErrorKind::LocalEmpty { precursor } => {
write!(
f,
"found a `{precursor}` indicating the start of a local \
component in a version, but did not find any alphanumeric \
ASCII segment following the `{precursor}`",
)
}
ErrorKind::UnexpectedEnd {
ref version,
ref remaining,
} => {
write!(
f,
"after parsing `{version}`, found `{remaining}`, \
which is not part of a valid version",
)
}
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum ErrorKind {
Wildcard,
InvalidDigit {
got: u8,
},
NumberTooBig {
bytes: Vec<u8>,
},
NoLeadingNumber,
NoLeadingReleaseNumber,
LocalEmpty {
precursor: char,
},
UnexpectedEnd {
version: String,
remaining: String,
},
}
impl From<ErrorKind> for VersionParseError {
fn from(kind: ErrorKind) -> Self {
Self {
kind: Box::new(kind),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VersionPatternParseError {
kind: Box<PatternErrorKind>,
}
impl std::error::Error for VersionPatternParseError {}
impl std::fmt::Display for VersionPatternParseError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self.kind {
PatternErrorKind::Version(ref err) => err.fmt(f),
PatternErrorKind::WildcardNotTrailing => {
write!(f, "wildcards in versions must be at the end")
}
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum PatternErrorKind {
Version(VersionParseError),
WildcardNotTrailing,
}
impl From<PatternErrorKind> for VersionPatternParseError {
fn from(kind: PatternErrorKind) -> Self {
Self {
kind: Box::new(kind),
}
}
}
impl From<ErrorKind> for VersionPatternParseError {
fn from(kind: ErrorKind) -> Self {
Self::from(VersionParseError::from(kind))
}
}
impl From<VersionParseError> for VersionPatternParseError {
fn from(err: VersionParseError) -> Self {
Self {
kind: Box::new(PatternErrorKind::Version(err)),
}
}
}
pub(crate) fn compare_release(this: &[u64], other: &[u64]) -> Ordering {
if this.len() == other.len() {
return this.cmp(other);
}
for (this, other) in this.iter().chain(std::iter::repeat(&0)).zip(
other
.iter()
.chain(std::iter::repeat(&0))
.take(this.len().max(other.len())),
) {
match this.cmp(other) {
Ordering::Less => {
return Ordering::Less;
}
Ordering::Equal => {}
Ordering::Greater => {
return Ordering::Greater;
}
}
}
Ordering::Equal
}
fn sortable_tuple(version: &Version) -> (u64, u64, Option<u64>, u64, LocalVersionSlice<'_>) {
let post = if version.max().is_some() {
Some(u64::MAX)
} else {
version.post()
};
match (version.pre(), post, version.dev(), version.min()) {
(_pre, post, _dev, Some(n)) => (0, 0, post, n, version.local()),
(None, None, Some(n), None) => (1, 0, None, n, version.local()),
(
Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: n,
}),
post,
dev,
None,
) => (2, n, post, dev.unwrap_or(u64::MAX), version.local()),
(
Some(Prerelease {
kind: PrereleaseKind::Beta,
number: n,
}),
post,
dev,
None,
) => (3, n, post, dev.unwrap_or(u64::MAX), version.local()),
(
Some(Prerelease {
kind: PrereleaseKind::Rc,
number: n,
}),
post,
dev,
None,
) => (4, n, post, dev.unwrap_or(u64::MAX), version.local()),
(None, None, None, None) => (5, 0, None, 0, version.local()),
(None, Some(post), dev, None) => {
(6, 0, Some(post), dev.unwrap_or(u64::MAX), version.local())
}
}
}
fn starts_with_ignore_ascii_case(needle: &[u8], haystack: &[u8]) -> bool {
needle.len() <= haystack.len()
&& std::iter::zip(needle, haystack).all(|(b1, b2)| b1.eq_ignore_ascii_case(b2))
}
fn parse_u64(bytes: &[u8]) -> Result<u64, VersionParseError> {
let mut n: u64 = 0;
for &byte in bytes {
let digit = match byte.checked_sub(b'0') {
None => return Err(ErrorKind::InvalidDigit { got: byte }.into()),
Some(digit) if digit > 9 => return Err(ErrorKind::InvalidDigit { got: byte }.into()),
Some(digit) => {
debug_assert!((0..=9).contains(&digit));
u64::from(digit)
}
};
n = n
.checked_mul(10)
.and_then(|n| n.checked_add(digit))
.ok_or_else(|| ErrorKind::NumberTooBig {
bytes: bytes.to_vec(),
})?;
}
Ok(n)
}
pub static MIN_VERSION: LazyLock<Version> =
LazyLock::new(|| Version::from_str("0a0.dev0").unwrap());
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::VersionSpecifier;
use super::*;
#[test]
fn test_packaging_versions() {
let versions = [
("1.0.dev456", Version::new([1, 0]).with_dev(Some(456))),
(
"1.0a1",
Version::new([1, 0]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 1,
})),
),
(
"1.0a2.dev456",
Version::new([1, 0])
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 2,
}))
.with_dev(Some(456)),
),
(
"1.0a12.dev456",
Version::new([1, 0])
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 12,
}))
.with_dev(Some(456)),
),
(
"1.0a12",
Version::new([1, 0]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 12,
})),
),
(
"1.0b1.dev456",
Version::new([1, 0])
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Beta,
number: 1,
}))
.with_dev(Some(456)),
),
(
"1.0b2",
Version::new([1, 0]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Beta,
number: 2,
})),
),
(
"1.0b2.post345.dev456",
Version::new([1, 0])
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Beta,
number: 2,
}))
.with_dev(Some(456))
.with_post(Some(345)),
),
(
"1.0b2.post345",
Version::new([1, 0])
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Beta,
number: 2,
}))
.with_post(Some(345)),
),
(
"1.0b2-346",
Version::new([1, 0])
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Beta,
number: 2,
}))
.with_post(Some(346)),
),
(
"1.0c1.dev456",
Version::new([1, 0])
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Rc,
number: 1,
}))
.with_dev(Some(456)),
),
(
"1.0c1",
Version::new([1, 0]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Rc,
number: 1,
})),
),
(
"1.0rc2",
Version::new([1, 0]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Rc,
number: 2,
})),
),
(
"1.0c3",
Version::new([1, 0]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Rc,
number: 3,
})),
),
("1.0", Version::new([1, 0])),
(
"1.0.post456.dev34",
Version::new([1, 0]).with_post(Some(456)).with_dev(Some(34)),
),
("1.0.post456", Version::new([1, 0]).with_post(Some(456))),
("1.1.dev1", Version::new([1, 1]).with_dev(Some(1))),
(
"1.2+123abc",
Version::new([1, 2])
.with_local_segments(vec![LocalSegment::String("123abc".to_string())]),
),
(
"1.2+123abc456",
Version::new([1, 2])
.with_local_segments(vec![LocalSegment::String("123abc456".to_string())]),
),
(
"1.2+abc",
Version::new([1, 2])
.with_local_segments(vec![LocalSegment::String("abc".to_string())]),
),
(
"1.2+abc123",
Version::new([1, 2])
.with_local_segments(vec![LocalSegment::String("abc123".to_string())]),
),
(
"1.2+abc123def",
Version::new([1, 2])
.with_local_segments(vec![LocalSegment::String("abc123def".to_string())]),
),
(
"1.2+1234.abc",
Version::new([1, 2]).with_local_segments(vec![
LocalSegment::Number(1234),
LocalSegment::String("abc".to_string()),
]),
),
(
"1.2+123456",
Version::new([1, 2]).with_local_segments(vec![LocalSegment::Number(123_456)]),
),
(
"1.2.r32+123456",
Version::new([1, 2])
.with_post(Some(32))
.with_local_segments(vec![LocalSegment::Number(123_456)]),
),
(
"1.2.rev33+123456",
Version::new([1, 2])
.with_post(Some(33))
.with_local_segments(vec![LocalSegment::Number(123_456)]),
),
(
"1!1.0.dev456",
Version::new([1, 0]).with_epoch(1).with_dev(Some(456)),
),
(
"1!1.0a1",
Version::new([1, 0])
.with_epoch(1)
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 1,
})),
),
(
"1!1.0a2.dev456",
Version::new([1, 0])
.with_epoch(1)
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 2,
}))
.with_dev(Some(456)),
),
(
"1!1.0a12.dev456",
Version::new([1, 0])
.with_epoch(1)
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 12,
}))
.with_dev(Some(456)),
),
(
"1!1.0a12",
Version::new([1, 0])
.with_epoch(1)
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 12,
})),
),
(
"1!1.0b1.dev456",
Version::new([1, 0])
.with_epoch(1)
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Beta,
number: 1,
}))
.with_dev(Some(456)),
),
(
"1!1.0b2",
Version::new([1, 0])
.with_epoch(1)
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Beta,
number: 2,
})),
),
(
"1!1.0b2.post345.dev456",
Version::new([1, 0])
.with_epoch(1)
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Beta,
number: 2,
}))
.with_post(Some(345))
.with_dev(Some(456)),
),
(
"1!1.0b2.post345",
Version::new([1, 0])
.with_epoch(1)
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Beta,
number: 2,
}))
.with_post(Some(345)),
),
(
"1!1.0b2-346",
Version::new([1, 0])
.with_epoch(1)
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Beta,
number: 2,
}))
.with_post(Some(346)),
),
(
"1!1.0c1.dev456",
Version::new([1, 0])
.with_epoch(1)
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Rc,
number: 1,
}))
.with_dev(Some(456)),
),
(
"1!1.0c1",
Version::new([1, 0])
.with_epoch(1)
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Rc,
number: 1,
})),
),
(
"1!1.0rc2",
Version::new([1, 0])
.with_epoch(1)
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Rc,
number: 2,
})),
),
(
"1!1.0c3",
Version::new([1, 0])
.with_epoch(1)
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Rc,
number: 3,
})),
),
("1!1.0", Version::new([1, 0]).with_epoch(1)),
(
"1!1.0.post456.dev34",
Version::new([1, 0])
.with_epoch(1)
.with_post(Some(456))
.with_dev(Some(34)),
),
(
"1!1.0.post456",
Version::new([1, 0]).with_epoch(1).with_post(Some(456)),
),
(
"1!1.1.dev1",
Version::new([1, 1]).with_epoch(1).with_dev(Some(1)),
),
(
"1!1.2+123abc",
Version::new([1, 2])
.with_epoch(1)
.with_local_segments(vec![LocalSegment::String("123abc".to_string())]),
),
(
"1!1.2+123abc456",
Version::new([1, 2])
.with_epoch(1)
.with_local_segments(vec![LocalSegment::String("123abc456".to_string())]),
),
(
"1!1.2+abc",
Version::new([1, 2])
.with_epoch(1)
.with_local_segments(vec![LocalSegment::String("abc".to_string())]),
),
(
"1!1.2+abc123",
Version::new([1, 2])
.with_epoch(1)
.with_local_segments(vec![LocalSegment::String("abc123".to_string())]),
),
(
"1!1.2+abc123def",
Version::new([1, 2])
.with_epoch(1)
.with_local_segments(vec![LocalSegment::String("abc123def".to_string())]),
),
(
"1!1.2+1234.abc",
Version::new([1, 2]).with_epoch(1).with_local_segments(vec![
LocalSegment::Number(1234),
LocalSegment::String("abc".to_string()),
]),
),
(
"1!1.2+123456",
Version::new([1, 2])
.with_epoch(1)
.with_local_segments(vec![LocalSegment::Number(123_456)]),
),
(
"1!1.2.r32+123456",
Version::new([1, 2])
.with_epoch(1)
.with_post(Some(32))
.with_local_segments(vec![LocalSegment::Number(123_456)]),
),
(
"1!1.2.rev33+123456",
Version::new([1, 2])
.with_epoch(1)
.with_post(Some(33))
.with_local_segments(vec![LocalSegment::Number(123_456)]),
),
(
"98765!1.2.rev33+123456",
Version::new([1, 2])
.with_epoch(98765)
.with_post(Some(33))
.with_local_segments(vec![LocalSegment::Number(123_456)]),
),
];
for (string, structured) in versions {
match Version::from_str(string) {
Err(err) => {
unreachable!(
"expected {string:?} to parse as {structured:?}, but got {err:?}",
structured = structured.as_bloated_debug(),
)
}
Ok(v) => assert!(
v == structured,
"for {string:?}, expected {structured:?} but got {v:?}",
structured = structured.as_bloated_debug(),
v = v.as_bloated_debug(),
),
}
let spec = format!("=={string}");
match VersionSpecifier::from_str(&spec) {
Err(err) => {
unreachable!(
"expected version in {spec:?} to parse as {structured:?}, but got {err:?}",
structured = structured.as_bloated_debug(),
)
}
Ok(v) => assert!(
v.version() == &structured,
"for {string:?}, expected {structured:?} but got {v:?}",
structured = structured.as_bloated_debug(),
v = v.version.as_bloated_debug(),
),
}
}
}
#[test]
fn test_packaging_failures() {
let versions = [
"1.0+a+",
"1.0++",
"1.0+_foobar",
"1.0+foo&asd",
"1.0+1+1",
"french toast",
"==french toast",
];
for version in versions {
assert!(Version::from_str(version).is_err());
assert!(VersionSpecifier::from_str(&format!("=={version}")).is_err());
}
}
#[test]
fn test_equality_and_normalization() {
let versions = [
("1.0dev", "1.0.dev0"),
("1.0.dev", "1.0.dev0"),
("1.0dev1", "1.0.dev1"),
("1.0dev", "1.0.dev0"),
("1.0-dev", "1.0.dev0"),
("1.0-dev1", "1.0.dev1"),
("1.0DEV", "1.0.dev0"),
("1.0.DEV", "1.0.dev0"),
("1.0DEV1", "1.0.dev1"),
("1.0DEV", "1.0.dev0"),
("1.0.DEV1", "1.0.dev1"),
("1.0-DEV", "1.0.dev0"),
("1.0-DEV1", "1.0.dev1"),
("1.0a", "1.0a0"),
("1.0.a", "1.0a0"),
("1.0.a1", "1.0a1"),
("1.0-a", "1.0a0"),
("1.0-a1", "1.0a1"),
("1.0alpha", "1.0a0"),
("1.0.alpha", "1.0a0"),
("1.0.alpha1", "1.0a1"),
("1.0-alpha", "1.0a0"),
("1.0-alpha1", "1.0a1"),
("1.0A", "1.0a0"),
("1.0.A", "1.0a0"),
("1.0.A1", "1.0a1"),
("1.0-A", "1.0a0"),
("1.0-A1", "1.0a1"),
("1.0ALPHA", "1.0a0"),
("1.0.ALPHA", "1.0a0"),
("1.0.ALPHA1", "1.0a1"),
("1.0-ALPHA", "1.0a0"),
("1.0-ALPHA1", "1.0a1"),
("1.0b", "1.0b0"),
("1.0.b", "1.0b0"),
("1.0.b1", "1.0b1"),
("1.0-b", "1.0b0"),
("1.0-b1", "1.0b1"),
("1.0beta", "1.0b0"),
("1.0.beta", "1.0b0"),
("1.0.beta1", "1.0b1"),
("1.0-beta", "1.0b0"),
("1.0-beta1", "1.0b1"),
("1.0B", "1.0b0"),
("1.0.B", "1.0b0"),
("1.0.B1", "1.0b1"),
("1.0-B", "1.0b0"),
("1.0-B1", "1.0b1"),
("1.0BETA", "1.0b0"),
("1.0.BETA", "1.0b0"),
("1.0.BETA1", "1.0b1"),
("1.0-BETA", "1.0b0"),
("1.0-BETA1", "1.0b1"),
("1.0c", "1.0rc0"),
("1.0.c", "1.0rc0"),
("1.0.c1", "1.0rc1"),
("1.0-c", "1.0rc0"),
("1.0-c1", "1.0rc1"),
("1.0rc", "1.0rc0"),
("1.0.rc", "1.0rc0"),
("1.0.rc1", "1.0rc1"),
("1.0-rc", "1.0rc0"),
("1.0-rc1", "1.0rc1"),
("1.0C", "1.0rc0"),
("1.0.C", "1.0rc0"),
("1.0.C1", "1.0rc1"),
("1.0-C", "1.0rc0"),
("1.0-C1", "1.0rc1"),
("1.0RC", "1.0rc0"),
("1.0.RC", "1.0rc0"),
("1.0.RC1", "1.0rc1"),
("1.0-RC", "1.0rc0"),
("1.0-RC1", "1.0rc1"),
("1.0post", "1.0.post0"),
("1.0.post", "1.0.post0"),
("1.0post1", "1.0.post1"),
("1.0post", "1.0.post0"),
("1.0-post", "1.0.post0"),
("1.0-post1", "1.0.post1"),
("1.0POST", "1.0.post0"),
("1.0.POST", "1.0.post0"),
("1.0POST1", "1.0.post1"),
("1.0POST", "1.0.post0"),
("1.0r", "1.0.post0"),
("1.0rev", "1.0.post0"),
("1.0.POST1", "1.0.post1"),
("1.0.r1", "1.0.post1"),
("1.0.rev1", "1.0.post1"),
("1.0-POST", "1.0.post0"),
("1.0-POST1", "1.0.post1"),
("1.0-5", "1.0.post5"),
("1.0-r5", "1.0.post5"),
("1.0-rev5", "1.0.post5"),
("1.0+AbC", "1.0+abc"),
("1.01", "1.1"),
("1.0a05", "1.0a5"),
("1.0b07", "1.0b7"),
("1.0c056", "1.0rc56"),
("1.0rc09", "1.0rc9"),
("1.0.post000", "1.0.post0"),
("1.1.dev09000", "1.1.dev9000"),
("00!1.2", "1.2"),
("0100!0.0", "100!0.0"),
("v1.0", "1.0"),
(" v1.0\t\n", "1.0"),
];
for (version_str, normalized_str) in versions {
let version = Version::from_str(version_str).unwrap();
let normalized = Version::from_str(normalized_str).unwrap();
assert_eq!(version, normalized, "{version_str} {normalized_str}");
assert_eq!(
version.to_string(),
normalized.to_string(),
"{version_str} {normalized_str}"
);
}
}
#[test]
fn test_equality_and_normalization2() {
let versions = [
("1.0.dev456", "1.0.dev456"),
("1.0a1", "1.0a1"),
("1.0a2.dev456", "1.0a2.dev456"),
("1.0a12.dev456", "1.0a12.dev456"),
("1.0a12", "1.0a12"),
("1.0b1.dev456", "1.0b1.dev456"),
("1.0b2", "1.0b2"),
("1.0b2.post345.dev456", "1.0b2.post345.dev456"),
("1.0b2.post345", "1.0b2.post345"),
("1.0rc1.dev456", "1.0rc1.dev456"),
("1.0rc1", "1.0rc1"),
("1.0", "1.0"),
("1.0.post456.dev34", "1.0.post456.dev34"),
("1.0.post456", "1.0.post456"),
("1.0.1", "1.0.1"),
("0!1.0.2", "1.0.2"),
("1.0.3+7", "1.0.3+7"),
("0!1.0.4+8.0", "1.0.4+8.0"),
("1.0.5+9.5", "1.0.5+9.5"),
("1.2+1234.abc", "1.2+1234.abc"),
("1.2+123456", "1.2+123456"),
("1.2+123abc", "1.2+123abc"),
("1.2+123abc456", "1.2+123abc456"),
("1.2+abc", "1.2+abc"),
("1.2+abc123", "1.2+abc123"),
("1.2+abc123def", "1.2+abc123def"),
("1.1.dev1", "1.1.dev1"),
("7!1.0.dev456", "7!1.0.dev456"),
("7!1.0a1", "7!1.0a1"),
("7!1.0a2.dev456", "7!1.0a2.dev456"),
("7!1.0a12.dev456", "7!1.0a12.dev456"),
("7!1.0a12", "7!1.0a12"),
("7!1.0b1.dev456", "7!1.0b1.dev456"),
("7!1.0b2", "7!1.0b2"),
("7!1.0b2.post345.dev456", "7!1.0b2.post345.dev456"),
("7!1.0b2.post345", "7!1.0b2.post345"),
("7!1.0rc1.dev456", "7!1.0rc1.dev456"),
("7!1.0rc1", "7!1.0rc1"),
("7!1.0", "7!1.0"),
("7!1.0.post456.dev34", "7!1.0.post456.dev34"),
("7!1.0.post456", "7!1.0.post456"),
("7!1.0.1", "7!1.0.1"),
("7!1.0.2", "7!1.0.2"),
("7!1.0.3+7", "7!1.0.3+7"),
("7!1.0.4+8.0", "7!1.0.4+8.0"),
("7!1.0.5+9.5", "7!1.0.5+9.5"),
("7!1.1.dev1", "7!1.1.dev1"),
];
for (version_str, normalized_str) in versions {
let version = Version::from_str(version_str).unwrap();
let normalized = Version::from_str(normalized_str).unwrap();
assert_eq!(version, normalized, "{version_str} {normalized_str}");
assert_eq!(
version.to_string(),
normalized_str,
"{version_str} {normalized_str}"
);
assert_eq!(
version.to_string(),
normalized.to_string(),
"{version_str} {normalized_str}"
);
}
}
#[test]
fn test_star_fixed_version() {
let result = Version::from_str("0.9.1.*");
assert_eq!(result.unwrap_err(), ErrorKind::Wildcard.into());
}
#[test]
fn test_invalid_word() {
let result = Version::from_str("blergh");
assert_eq!(result.unwrap_err(), ErrorKind::NoLeadingNumber.into());
}
#[test]
fn test_from_version_star() {
let p = |s: &str| -> Result<VersionPattern, _> { s.parse() };
assert!(!p("1.2.3").unwrap().is_wildcard());
assert!(p("1.2.3.*").unwrap().is_wildcard());
assert_eq!(
p("1.2.*.4.*").unwrap_err(),
PatternErrorKind::WildcardNotTrailing.into(),
);
assert_eq!(
p("1.0-dev1.*").unwrap_err(),
ErrorKind::UnexpectedEnd {
version: "1.0-dev1".to_string(),
remaining: ".*".to_string()
}
.into(),
);
assert_eq!(
p("1.0a1.*").unwrap_err(),
ErrorKind::UnexpectedEnd {
version: "1.0a1".to_string(),
remaining: ".*".to_string()
}
.into(),
);
assert_eq!(
p("1.0.post1.*").unwrap_err(),
ErrorKind::UnexpectedEnd {
version: "1.0.post1".to_string(),
remaining: ".*".to_string()
}
.into(),
);
assert_eq!(
p("1.0+lolwat.*").unwrap_err(),
ErrorKind::LocalEmpty { precursor: '.' }.into(),
);
}
#[test]
fn parse_version_valid() {
let p = |s: &str| match Parser::new(s.as_bytes()).parse() {
Ok(v) => v,
Err(err) => unreachable!("expected valid version, but got error: {err:?}"),
};
assert_eq!(p("5"), Version::new([5]));
assert_eq!(p("5.6"), Version::new([5, 6]));
assert_eq!(p("5.6.7"), Version::new([5, 6, 7]));
assert_eq!(p("512.623.734"), Version::new([512, 623, 734]));
assert_eq!(p("1.2.3.4"), Version::new([1, 2, 3, 4]));
assert_eq!(p("1.2.3.4.5"), Version::new([1, 2, 3, 4, 5]));
assert_eq!(p("4!5"), Version::new([5]).with_epoch(4));
assert_eq!(p("4!5.6"), Version::new([5, 6]).with_epoch(4));
assert_eq!(
p("5a1"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 1
}))
);
assert_eq!(
p("5alpha1"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 1
}))
);
assert_eq!(
p("5b1"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Beta,
number: 1
}))
);
assert_eq!(
p("5beta1"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Beta,
number: 1
}))
);
assert_eq!(
p("5rc1"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Rc,
number: 1
}))
);
assert_eq!(
p("5c1"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Rc,
number: 1
}))
);
assert_eq!(
p("5preview1"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Rc,
number: 1
}))
);
assert_eq!(
p("5pre1"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Rc,
number: 1
}))
);
assert_eq!(
p("5.6.7pre1"),
Version::new([5, 6, 7]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Rc,
number: 1
}))
);
assert_eq!(
p("5alpha789"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 789
}))
);
assert_eq!(
p("5.alpha789"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 789
}))
);
assert_eq!(
p("5-alpha789"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 789
}))
);
assert_eq!(
p("5_alpha789"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 789
}))
);
assert_eq!(
p("5alpha.789"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 789
}))
);
assert_eq!(
p("5alpha-789"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 789
}))
);
assert_eq!(
p("5alpha_789"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 789
}))
);
assert_eq!(
p("5ALPHA789"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 789
}))
);
assert_eq!(
p("5aLpHa789"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 789
}))
);
assert_eq!(
p("5alpha"),
Version::new([5]).with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 0
}))
);
assert_eq!(p("5post2"), Version::new([5]).with_post(Some(2)));
assert_eq!(p("5rev2"), Version::new([5]).with_post(Some(2)));
assert_eq!(p("5r2"), Version::new([5]).with_post(Some(2)));
assert_eq!(p("5.post2"), Version::new([5]).with_post(Some(2)));
assert_eq!(p("5-post2"), Version::new([5]).with_post(Some(2)));
assert_eq!(p("5_post2"), Version::new([5]).with_post(Some(2)));
assert_eq!(p("5.post.2"), Version::new([5]).with_post(Some(2)));
assert_eq!(p("5.post-2"), Version::new([5]).with_post(Some(2)));
assert_eq!(p("5.post_2"), Version::new([5]).with_post(Some(2)));
assert_eq!(
p("5.6.7.post_2"),
Version::new([5, 6, 7]).with_post(Some(2))
);
assert_eq!(p("5-2"), Version::new([5]).with_post(Some(2)));
assert_eq!(p("5.6.7-2"), Version::new([5, 6, 7]).with_post(Some(2)));
assert_eq!(p("5POST2"), Version::new([5]).with_post(Some(2)));
assert_eq!(p("5PoSt2"), Version::new([5]).with_post(Some(2)));
assert_eq!(p("5post"), Version::new([5]).with_post(Some(0)));
assert_eq!(p("5dev2"), Version::new([5]).with_dev(Some(2)));
assert_eq!(p("5.dev2"), Version::new([5]).with_dev(Some(2)));
assert_eq!(p("5-dev2"), Version::new([5]).with_dev(Some(2)));
assert_eq!(p("5_dev2"), Version::new([5]).with_dev(Some(2)));
assert_eq!(p("5.dev.2"), Version::new([5]).with_dev(Some(2)));
assert_eq!(p("5.dev-2"), Version::new([5]).with_dev(Some(2)));
assert_eq!(p("5.dev_2"), Version::new([5]).with_dev(Some(2)));
assert_eq!(p("5.6.7.dev_2"), Version::new([5, 6, 7]).with_dev(Some(2)));
assert_eq!(p("5DEV2"), Version::new([5]).with_dev(Some(2)));
assert_eq!(p("5dEv2"), Version::new([5]).with_dev(Some(2)));
assert_eq!(p("5DeV2"), Version::new([5]).with_dev(Some(2)));
assert_eq!(p("5dev"), Version::new([5]).with_dev(Some(0)));
assert_eq!(
p("5+2"),
Version::new([5]).with_local_segments(vec![LocalSegment::Number(2)])
);
assert_eq!(
p("5+a"),
Version::new([5]).with_local_segments(vec![LocalSegment::String("a".to_string())])
);
assert_eq!(
p("5+abc.123"),
Version::new([5]).with_local_segments(vec![
LocalSegment::String("abc".to_string()),
LocalSegment::Number(123),
])
);
assert_eq!(
p("5+123.abc"),
Version::new([5]).with_local_segments(vec![
LocalSegment::Number(123),
LocalSegment::String("abc".to_string()),
])
);
assert_eq!(
p("5+18446744073709551615.abc"),
Version::new([5]).with_local_segments(vec![
LocalSegment::Number(18_446_744_073_709_551_615),
LocalSegment::String("abc".to_string()),
])
);
assert_eq!(
p("5+18446744073709551616.abc"),
Version::new([5]).with_local_segments(vec![
LocalSegment::String("18446744073709551616".to_string()),
LocalSegment::String("abc".to_string()),
])
);
assert_eq!(
p("5+ABC.123"),
Version::new([5]).with_local_segments(vec![
LocalSegment::String("abc".to_string()),
LocalSegment::Number(123),
])
);
assert_eq!(
p("5+ABC-123.4_5_xyz-MNO"),
Version::new([5]).with_local_segments(vec![
LocalSegment::String("abc".to_string()),
LocalSegment::Number(123),
LocalSegment::Number(4),
LocalSegment::Number(5),
LocalSegment::String("xyz".to_string()),
LocalSegment::String("mno".to_string()),
])
);
assert_eq!(
p("5.6.7+abc-00123"),
Version::new([5, 6, 7]).with_local_segments(vec![
LocalSegment::String("abc".to_string()),
LocalSegment::Number(123),
])
);
assert_eq!(
p("5.6.7+abc-foo00123"),
Version::new([5, 6, 7]).with_local_segments(vec![
LocalSegment::String("abc".to_string()),
LocalSegment::String("foo00123".to_string()),
])
);
assert_eq!(
p("5.6.7+abc-00123a"),
Version::new([5, 6, 7]).with_local_segments(vec![
LocalSegment::String("abc".to_string()),
LocalSegment::String("00123a".to_string()),
])
);
assert_eq!(
p("5a2post3"),
Version::new([5])
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 2
}))
.with_post(Some(3))
);
assert_eq!(
p("5.a-2_post-3"),
Version::new([5])
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 2
}))
.with_post(Some(3))
);
assert_eq!(
p("5a2-3"),
Version::new([5])
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 2
}))
.with_post(Some(3))
);
assert_eq!(p("v5"), Version::new([5]));
assert_eq!(p("V5"), Version::new([5]));
assert_eq!(p("v5.6.7"), Version::new([5, 6, 7]));
assert_eq!(p(" v5 "), Version::new([5]));
assert_eq!(p(" 5 "), Version::new([5]));
assert_eq!(
p(" 5.6.7+abc.123.xyz "),
Version::new([5, 6, 7]).with_local_segments(vec![
LocalSegment::String("abc".to_string()),
LocalSegment::Number(123),
LocalSegment::String("xyz".to_string())
])
);
assert_eq!(p(" \n5\n \t"), Version::new([5]));
assert!(Parser::new("1.min0".as_bytes()).parse().is_err());
}
#[test]
fn parse_version_invalid() {
let p = |s: &str| match Parser::new(s.as_bytes()).parse() {
Err(err) => err,
Ok(v) => unreachable!(
"expected version parser error, but got: {v:?}",
v = v.as_bloated_debug()
),
};
assert_eq!(p(""), ErrorKind::NoLeadingNumber.into());
assert_eq!(p("a"), ErrorKind::NoLeadingNumber.into());
assert_eq!(p("v 5"), ErrorKind::NoLeadingNumber.into());
assert_eq!(p("V 5"), ErrorKind::NoLeadingNumber.into());
assert_eq!(p("x 5"), ErrorKind::NoLeadingNumber.into());
assert_eq!(
p("18446744073709551616"),
ErrorKind::NumberTooBig {
bytes: b"18446744073709551616".to_vec()
}
.into()
);
assert_eq!(p("5!"), ErrorKind::NoLeadingReleaseNumber.into());
assert_eq!(
p("5.6./"),
ErrorKind::UnexpectedEnd {
version: "5.6".to_string(),
remaining: "./".to_string()
}
.into()
);
assert_eq!(
p("5.6.-alpha2"),
ErrorKind::UnexpectedEnd {
version: "5.6".to_string(),
remaining: ".-alpha2".to_string()
}
.into()
);
assert_eq!(
p("1.2.3a18446744073709551616"),
ErrorKind::NumberTooBig {
bytes: b"18446744073709551616".to_vec()
}
.into()
);
assert_eq!(p("5+"), ErrorKind::LocalEmpty { precursor: '+' }.into());
assert_eq!(p("5+ "), ErrorKind::LocalEmpty { precursor: '+' }.into());
assert_eq!(p("5+abc."), ErrorKind::LocalEmpty { precursor: '.' }.into());
assert_eq!(p("5+abc-"), ErrorKind::LocalEmpty { precursor: '-' }.into());
assert_eq!(p("5+abc_"), ErrorKind::LocalEmpty { precursor: '_' }.into());
assert_eq!(
p("5+abc. "),
ErrorKind::LocalEmpty { precursor: '.' }.into()
);
assert_eq!(
p("5.6-"),
ErrorKind::UnexpectedEnd {
version: "5.6".to_string(),
remaining: "-".to_string()
}
.into()
);
}
#[test]
fn parse_version_pattern_valid() {
let p = |s: &str| match Parser::new(s.as_bytes()).parse_pattern() {
Ok(v) => v,
Err(err) => unreachable!("expected valid version, but got error: {err:?}"),
};
assert_eq!(p("5.*"), VersionPattern::wildcard(Version::new([5])));
assert_eq!(p("5.6.*"), VersionPattern::wildcard(Version::new([5, 6])));
assert_eq!(
p("2!5.6.*"),
VersionPattern::wildcard(Version::new([5, 6]).with_epoch(2))
);
}
#[test]
fn parse_version_pattern_invalid() {
let p = |s: &str| match Parser::new(s.as_bytes()).parse_pattern() {
Err(err) => err,
Ok(vpat) => unreachable!("expected version pattern parser error, but got: {vpat:?}"),
};
assert_eq!(p("*"), ErrorKind::NoLeadingNumber.into());
assert_eq!(p("2!*"), ErrorKind::NoLeadingReleaseNumber.into());
}
#[test]
fn ordering() {
let versions = &[
"1.dev0",
"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 (i, v1) in versions.iter().enumerate() {
for v2 in &versions[i + 1..] {
let less = v1.parse::<Version>().unwrap();
let greater = v2.parse::<Version>().unwrap();
assert_eq!(
less.cmp(&greater),
Ordering::Less,
"less: {:?}\ngreater: {:?}",
less.as_bloated_debug(),
greater.as_bloated_debug()
);
}
}
}
#[test]
fn local_sentinel_version() {
let sentinel = Version::new([1, 0]).with_local(LocalVersion::Max);
let versions = &["1.0.post0", "1.1"];
for greater in versions {
let greater = greater.parse::<Version>().unwrap();
assert_eq!(
sentinel.cmp(&greater),
Ordering::Less,
"less: {:?}\ngreater: {:?}",
greater.as_bloated_debug(),
sentinel.as_bloated_debug(),
);
}
let versions = &["1.0", "1.0.a0", "1.0+local"];
for less in versions {
let less = less.parse::<Version>().unwrap();
assert_eq!(
sentinel.cmp(&less),
Ordering::Greater,
"less: {:?}\ngreater: {:?}",
sentinel.as_bloated_debug(),
less.as_bloated_debug()
);
}
}
#[test]
fn min_version() {
let less = Version::new([1, 0]).with_min(Some(0));
let versions = &[
"1.dev0",
"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 greater in versions {
let greater = greater.parse::<Version>().unwrap();
assert_eq!(
less.cmp(&greater),
Ordering::Less,
"less: {:?}\ngreater: {:?}",
less.as_bloated_debug(),
greater.as_bloated_debug()
);
}
}
#[test]
fn max_version() {
let greater = Version::new([1, 0]).with_max(Some(0));
let versions = &[
"1.dev0",
"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",
];
for less in versions {
let less = less.parse::<Version>().unwrap();
assert_eq!(
less.cmp(&greater),
Ordering::Less,
"less: {:?}\ngreater: {:?}",
less.as_bloated_debug(),
greater.as_bloated_debug()
);
}
let greater = Version::new([1, 0])
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 1,
}))
.with_max(Some(0));
let versions = &["1.0a1", "1.0a1+local", "1.0a1.post1"];
for less in versions {
let less = less.parse::<Version>().unwrap();
assert_eq!(
less.cmp(&greater),
Ordering::Less,
"less: {:?}\ngreater: {:?}",
less.as_bloated_debug(),
greater.as_bloated_debug()
);
}
let less = Version::new([1, 0])
.with_pre(Some(Prerelease {
kind: PrereleaseKind::Alpha,
number: 1,
}))
.with_max(Some(0));
let versions = &["1.0b1", "1.0b1+local", "1.0b1.post1", "1.0"];
for greater in versions {
let greater = greater.parse::<Version>().unwrap();
assert_eq!(
less.cmp(&greater),
Ordering::Less,
"less: {:?}\ngreater: {:?}",
less.as_bloated_debug(),
greater.as_bloated_debug()
);
}
}
#[test]
fn parse_number_u64() {
let p = |s: &str| parse_u64(s.as_bytes());
assert_eq!(p("0"), Ok(0));
assert_eq!(p("00"), Ok(0));
assert_eq!(p("1"), Ok(1));
assert_eq!(p("01"), Ok(1));
assert_eq!(p("9"), Ok(9));
assert_eq!(p("10"), Ok(10));
assert_eq!(p("18446744073709551615"), Ok(18_446_744_073_709_551_615));
assert_eq!(p("018446744073709551615"), Ok(18_446_744_073_709_551_615));
assert_eq!(
p("000000018446744073709551615"),
Ok(18_446_744_073_709_551_615)
);
assert_eq!(p("10a"), Err(ErrorKind::InvalidDigit { got: b'a' }.into()));
assert_eq!(p("10["), Err(ErrorKind::InvalidDigit { got: b'[' }.into()));
assert_eq!(p("10/"), Err(ErrorKind::InvalidDigit { got: b'/' }.into()));
assert_eq!(
p("18446744073709551616"),
Err(ErrorKind::NumberTooBig {
bytes: b"18446744073709551616".to_vec()
}
.into())
);
assert_eq!(
p("18446744073799551615abc"),
Err(ErrorKind::NumberTooBig {
bytes: b"18446744073799551615abc".to_vec()
}
.into())
);
assert_eq!(
parse_u64(b"18446744073799551615\xFF"),
Err(ErrorKind::NumberTooBig {
bytes: b"18446744073799551615\xFF".to_vec()
}
.into())
);
}
impl Version {
pub(crate) fn as_bloated_debug(&self) -> impl std::fmt::Debug + '_ {
std::fmt::from_fn(|f| {
f.debug_struct("Version")
.field("epoch", &self.epoch())
.field("release", &&*self.release())
.field("pre", &self.pre())
.field("post", &self.post())
.field("dev", &self.dev())
.field("local", &self.local())
.field("min", &self.min())
.field("max", &self.max())
.finish()
})
}
}
#[test]
fn preserve_trailing_zeros() {
let v1: Version = "1.2.0".parse().unwrap();
assert_eq!(&*v1.release(), &[1, 2, 0]);
assert_eq!(v1.to_string(), "1.2.0");
let v2: Version = "1.2".parse().unwrap();
assert_eq!(&*v2.release(), &[1, 2]);
assert_eq!(v2.to_string(), "1.2");
}
#[test]
fn only_release_at_precision_preserves_epoch_and_discards_suffixes() {
let version = "1!2.3rc1.post2.dev3+local"
.parse::<Version>()
.expect("valid version");
assert_eq!(
version
.only_release_at_precision(4)
.expect("non-zero precision")
.to_string(),
"1!2.3.0.0"
);
assert_eq!(version.only_release_at_precision(0), None);
}
#[test]
fn only_release_trimmed_discards_non_release_segments() {
for version in ["1.2a1", "1.2.post1", "1!1.2", "1.2+local", "1.2.dev1"] {
let version = version.parse::<Version>().unwrap();
assert_eq!(version.only_release_trimmed(), Version::new([1, 2]));
}
assert_eq!(
Version::new([1, 2])
.with_min(Some(0))
.only_release_trimmed(),
Version::new([1, 2])
);
assert_eq!(
Version::new([1, 2])
.with_max(Some(0))
.only_release_trimmed(),
Version::new([1, 2])
);
assert_eq!(
Version::new([1, 2, 0]).only_release_trimmed(),
Version::new([1, 2])
);
assert_eq!(
Version::new([1, 2]).only_release_trimmed(),
Version::new([1, 2])
);
}
#[test]
fn type_size() {
assert_eq!(size_of::<VersionSmall>(), size_of::<usize>() * 2);
assert_eq!(size_of::<Version>(), size_of::<usize>() * 2);
}
#[test]
fn bump_major() {
let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease {
index: 0,
value: None,
});
assert_eq!(version.to_string().as_str(), "1");
let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease {
index: 0,
value: None,
});
assert_eq!(version.to_string().as_str(), "2.0");
let mut version = "0.1.2".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease {
index: 0,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.0.0");
let mut version = "1.2.3".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease {
index: 0,
value: None,
});
assert_eq!(version.to_string().as_str(), "2.0.0");
let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease {
index: 0,
value: None,
});
assert_eq!(version.to_string().as_str(), "2.0.0.0");
let mut version = "5!1.7.3.5b2.post345.dev456+local"
.parse::<Version>()
.unwrap();
version.bump(BumpCommand::BumpRelease {
index: 0,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!2.0.0.0+local");
version.bump(BumpCommand::BumpRelease {
index: 0,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!3.0.0.0+local");
}
#[test]
fn bump_minor() {
let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease {
index: 1,
value: None,
});
assert_eq!(version.to_string().as_str(), "0.1");
let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease {
index: 1,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.6");
let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease {
index: 1,
value: None,
});
assert_eq!(version.to_string().as_str(), "5.4.0");
let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease {
index: 1,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.3.0.0");
let mut version = "5!1.7.3.5b2.post345.dev456+local"
.parse::<Version>()
.unwrap();
version.bump(BumpCommand::BumpRelease {
index: 1,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!1.8.0.0+local");
version.bump(BumpCommand::BumpRelease {
index: 1,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!1.9.0.0+local");
}
#[test]
fn bump_patch() {
let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease {
index: 2,
value: None,
});
assert_eq!(version.to_string().as_str(), "0.0.1");
let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease {
index: 2,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.5.1");
let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease {
index: 2,
value: None,
});
assert_eq!(version.to_string().as_str(), "5.3.7");
let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease {
index: 2,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.2.4.0");
let mut version = "5!1.7.3.5b2.post345.dev456+local"
.parse::<Version>()
.unwrap();
version.bump(BumpCommand::BumpRelease {
index: 2,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!1.7.4.0+local");
version.bump(BumpCommand::BumpRelease {
index: 2,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!1.7.5.0+local");
}
#[test]
fn bump_alpha() {
let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Alpha,
value: None,
});
assert_eq!(version.to_string().as_str(), "0a1");
let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Alpha,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.5a1");
let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Alpha,
value: None,
});
assert_eq!(version.to_string().as_str(), "5.3.6a1");
let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Alpha,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.2.3.4a1");
let mut version = "5!1.7.3.5b2.post345.dev456+local"
.parse::<Version>()
.unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Alpha,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!1.7.3.5a1+local");
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Alpha,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!1.7.3.5a2+local");
}
#[test]
fn bump_beta() {
let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Beta,
value: None,
});
assert_eq!(version.to_string().as_str(), "0b1");
let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Beta,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.5b1");
let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Beta,
value: None,
});
assert_eq!(version.to_string().as_str(), "5.3.6b1");
let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Beta,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.2.3.4b1");
let mut version = "5!1.7.3.5a2.post345.dev456+local"
.parse::<Version>()
.unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Beta,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!1.7.3.5b1+local");
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Beta,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2+local");
}
#[test]
fn bump_rc() {
let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Rc,
value: None,
});
assert_eq!(version.to_string().as_str(), "0rc1");
let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Rc,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.5rc1");
let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Rc,
value: None,
});
assert_eq!(version.to_string().as_str(), "5.3.6rc1");
let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Rc,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.2.3.4rc1");
let mut version = "5!1.7.3.5b2.post345.dev456+local"
.parse::<Version>()
.unwrap();
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Rc,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!1.7.3.5rc1+local");
version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Rc,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!1.7.3.5rc2+local");
}
#[test]
fn bump_post() {
let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPost { value: None });
assert_eq!(version.to_string().as_str(), "0.post1");
let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPost { value: None });
assert_eq!(version.to_string().as_str(), "1.5.post1");
let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPost { value: None });
assert_eq!(version.to_string().as_str(), "5.3.6.post1");
let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPost { value: None });
assert_eq!(version.to_string().as_str(), "1.2.3.4.post1");
let mut version = "5!1.7.3.5b2.dev123+local".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPost { value: None });
assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2.post1+local");
version.bump(BumpCommand::BumpPost { value: None });
assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2.post2+local");
}
#[test]
fn bump_dev() {
let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpDev { value: None });
assert_eq!(version.to_string().as_str(), "0.dev1");
let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpDev { value: None });
assert_eq!(version.to_string().as_str(), "1.5.dev1");
let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpDev { value: None });
assert_eq!(version.to_string().as_str(), "5.3.6.dev1");
let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpDev { value: None });
assert_eq!(version.to_string().as_str(), "1.2.3.4.dev1");
let mut version = "5!1.7.3.5b2.post345+local".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpDev { value: None });
assert_eq!(
version.to_string().as_str(),
"5!1.7.3.5b2.post345.dev1+local"
);
version.bump(BumpCommand::BumpDev { value: None });
assert_eq!(
version.to_string().as_str(),
"5!1.7.3.5b2.post345.dev2+local"
);
}
#[test]
fn make_stable() {
let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::MakeStable);
assert_eq!(version.to_string().as_str(), "0");
let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::MakeStable);
assert_eq!(version.to_string().as_str(), "1.5");
let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::MakeStable);
assert_eq!(version.to_string().as_str(), "5.3.6");
let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::MakeStable);
assert_eq!(version.to_string().as_str(), "1.2.3.4");
let mut version = "5!1.7.3.5b2.post345+local".parse::<Version>().unwrap();
version.bump(BumpCommand::MakeStable);
assert_eq!(version.to_string().as_str(), "5!1.7.3.5+local");
version.bump(BumpCommand::MakeStable);
assert_eq!(version.to_string().as_str(), "5!1.7.3.5+local");
}
}