#![doc(html_root_url = "https://docs.rs/versions/2.1.0")]
use itertools::EitherOrBoth::{Both, Left, Right};
use itertools::Itertools;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::{alpha1, alphanumeric1, char, digit1};
use nom::combinator::{map, map_res, opt, peek, recognize, value};
use nom::multi::{many1, separated_list1};
use nom::IResult;
use std::cmp::Ordering;
use std::cmp::Ordering::{Equal, Greater, Less};
use std::fmt;
mod parsers;
#[derive(Debug, Eq, Hash, Clone)]
pub struct SemVer {
pub major: u32,
pub minor: u32,
pub patch: u32,
pub pre_rel: Option<Chunks>,
pub meta: Option<Chunks>,
}
impl SemVer {
pub fn new(s: &str) -> Option<SemVer> {
match SemVer::parse(s) {
Ok(("", sv)) => Some(sv),
_ => None,
}
}
pub fn to_version(&self) -> Version {
let chunks = Chunks(vec![
Chunk(vec![Unit::Digits(self.major)]),
Chunk(vec![Unit::Digits(self.minor)]),
Chunk(vec![Unit::Digits(self.patch)]),
]);
Version {
epoch: None,
chunks,
meta: self.meta.clone(),
release: self.pre_rel.clone(),
}
}
pub fn to_mess(&self) -> Mess {
let chunks = vec![
MChunk::Digits(self.major, self.major.to_string()),
MChunk::Digits(self.minor, self.minor.to_string()),
MChunk::Digits(self.patch, self.patch.to_string()),
];
let next = self.pre_rel.as_ref().map(|pr| {
let chunks = pr.0.iter().filter_map(|c| c.mchunk()).collect();
let next = self.meta.as_ref().map(|meta| {
let chunks = meta.0.iter().filter_map(|m| m.mchunk()).collect();
(Sep::Plus, Box::new(Mess { chunks, next: None }))
});
(Sep::Hyphen, Box::new(Mess { chunks, next }))
});
Mess { chunks, next }
}
fn cmp_version(&self, other: &Version) -> Ordering {
match other.epoch {
Some(n) if n > 0 => Greater,
_ => match other.nth_lenient(0).map(|x| self.major.cmp(&x)) {
None => Greater,
Some(Greater) => Greater,
Some(Less) => Less,
Some(Equal) => match other.nth_lenient(1).map(|x| self.minor.cmp(&x)) {
None => Greater,
Some(Greater) => Greater,
Some(Less) => Less,
Some(Equal) => match other.nth_lenient(2).map(|x| self.patch.cmp(&x)) {
None => Greater,
Some(Greater) => Greater,
Some(Less) => Less,
Some(Equal) => self.pre_rel.cmp(&other.release),
},
},
},
}
}
fn cmp_mess(&self, other: &Mess) -> Ordering {
match other.nth(0).map(|x| self.major.cmp(&x)) {
None => self.to_mess().cmp(other),
Some(Greater) => Greater,
Some(Less) => Less,
Some(Equal) => match other.nth(1).map(|x| self.minor.cmp(&x)) {
None => self.to_mess().cmp(other),
Some(Greater) => Greater,
Some(Less) => Less,
Some(Equal) => match other.nth(2).map(|x| self.patch.cmp(&x)) {
Some(Greater) => Greater,
Some(Less) => Less,
Some(Equal) => self.to_mess().cmp(other),
None => match other.nth_chunk(2).and_then(|c| c.single_digit_lenient()) {
None => self.to_mess().cmp(other),
Some(p) => match self.patch.cmp(&p) {
Greater => Greater,
Less => Less,
Equal => Greater,
},
},
},
},
}
}
pub fn parse(i: &str) -> IResult<&str, SemVer> {
let (i, major) = parsers::unsigned(i)?;
let (i, _) = char('.')(i)?;
let (i, minor) = parsers::unsigned(i)?;
let (i, _) = char('.')(i)?;
let (i, patch) = parsers::unsigned(i)?;
let (i, pre_rel) = opt(Chunks::pre_rel)(i)?;
let (i, meta) = opt(Chunks::meta)(i)?;
let sv = SemVer {
major,
minor,
patch,
pre_rel,
meta,
};
Ok((i, sv))
}
}
impl PartialEq for SemVer {
fn eq(&self, other: &Self) -> bool {
self.major == other.major
&& self.minor == other.minor
&& self.patch == other.patch
&& self.pre_rel == other.pre_rel
}
}
impl PartialOrd for SemVer {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SemVer {
fn cmp(&self, other: &Self) -> Ordering {
let a = (self.major, self.minor, self.patch);
let b = (other.major, other.minor, other.patch);
match a.cmp(&b) {
Less => Less,
Greater => Greater,
Equal => match (&self.pre_rel, &other.pre_rel) {
(None, None) => Equal,
(None, _) => Greater,
(_, None) => Less,
(Some(ap), Some(bp)) => ap.cmp(&bp),
},
}
}
}
impl fmt::Display for SemVer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
if let Some(p) = &self.pre_rel {
write!(f, "-{}", p)?;
}
if let Some(m) = &self.meta {
write!(f, "+{}", m)?;
}
Ok(())
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Version {
pub epoch: Option<u32>,
pub chunks: Chunks,
pub meta: Option<Chunks>,
pub release: Option<Chunks>,
}
impl Version {
pub fn new(s: &str) -> Option<Version> {
match Version::parse(s) {
Ok(("", v)) => Some(v),
_ => None,
}
}
pub fn nth(&self, n: usize) -> Option<u32> {
self.chunks.0.get(n).and_then(Chunk::single_digit)
}
pub fn nth_lenient(&self, n: usize) -> Option<u32> {
self.chunks.0.get(n).and_then(Chunk::single_digit_lenient)
}
pub fn to_mess(&self) -> Mess {
match self.epoch {
None => self.to_mess_continued(),
Some(e) => {
let chunks = vec![MChunk::Digits(e, e.to_string())];
let next = Some((Sep::Colon, Box::new(self.to_mess_continued())));
Mess { chunks, next }
}
}
}
fn to_mess_continued(&self) -> Mess {
let chunks = self.chunks.0.iter().filter_map(|c| c.mchunk()).collect();
let next = self.release.as_ref().map(|cs| {
let chunks = cs.0.iter().filter_map(|c| c.mchunk()).collect();
(Sep::Hyphen, Box::new(Mess { chunks, next: None }))
});
Mess { chunks, next }
}
fn cmp_mess(&self, other: &Mess) -> Ordering {
match self.epoch {
Some(e) if e > 0 && other.chunks.len() == 1 => match &other.next {
None => Greater,
Some((Sep::Colon, m)) => match other.nth(0) {
None => Greater,
Some(me) => match e.cmp(&me) {
Equal => Version::cmp_mess_continued(self, &m),
ord => ord,
},
},
Some(_) => Greater,
},
Some(e) if e > 0 => Greater,
_ => Version::cmp_mess_continued(self, &other),
}
}
fn cmp_mess_continued(&self, other: &Mess) -> Ordering {
(0..)
.filter_map(
|n| match self.nth(n).and_then(|x| other.nth(n).map(|y| x.cmp(&y))) {
None => Some(self.to_mess().cmp(other)),
Some(Greater) => Some(Greater),
Some(Less) => Some(Less),
Some(Equal) => None,
},
)
.next()
.unwrap_or_else(|| self.to_mess().cmp(other))
}
pub fn parse(i: &str) -> IResult<&str, Version> {
let (i, epoch) = opt(Version::epoch)(i)?;
let (i, chunks) = Chunks::parse(i)?;
let (i, meta) = opt(Chunks::meta)(i)?;
let (i, release) = opt(Chunks::pre_rel)(i)?;
let v = Version {
epoch,
chunks,
meta,
release,
};
Ok((i, v))
}
fn epoch(i: &str) -> IResult<&str, u32> {
let (i, epoch) = parsers::unsigned(i)?;
let (i, _) = char(':')(i)?;
Ok((i, epoch))
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
let ae = self.epoch.unwrap_or(0);
let be = other.epoch.unwrap_or(0);
match ae.cmp(&be) {
Equal => match self.chunks.cmp(&other.chunks) {
Equal => self.release.cmp(&other.release),
ord => ord,
},
ord => ord,
}
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(e) = self.epoch {
write!(f, "{}:", e)?;
}
write!(f, "{}", self.chunks)?;
if let Some(m) = &self.meta {
write!(f, "+{}", m)?;
}
if let Some(r) = &self.release {
write!(f, "-{}", r)?;
}
Ok(())
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Mess {
pub chunks: Vec<MChunk>,
pub next: Option<(Sep, Box<Mess>)>,
}
impl Mess {
pub fn new(s: &str) -> Option<Mess> {
match Mess::parse(s) {
Ok(("", m)) => Some(m),
_ => None,
}
}
pub fn nth(&self, x: usize) -> Option<u32> {
self.chunks.get(x).and_then(|chunk| match chunk {
MChunk::Digits(i, _) => Some(*i),
_ => None,
})
}
fn nth_chunk(&self, x: usize) -> Option<Chunk> {
let chunk = self.chunks.get(x)?.text();
let (i, c) = Chunk::parse(chunk).ok()?;
match i {
"" => Some(c),
_ => None,
}
}
pub fn parse(i: &str) -> IResult<&str, Mess> {
let (i, chunks) = separated_list1(char('.'), MChunk::parse)(i)?;
let (i, next) = opt(Mess::next)(i)?;
let m = Mess {
chunks,
next: next.map(|(s, m)| (s, Box::new(m))),
};
Ok((i, m))
}
fn next(i: &str) -> IResult<&str, (Sep, Mess)> {
let (i, sep) = Mess::sep(i)?;
let (i, mess) = Mess::parse(i)?;
Ok((i, (sep, mess)))
}
fn sep(i: &str) -> IResult<&str, Sep> {
alt((
value(Sep::Colon, char(':')),
value(Sep::Hyphen, char('-')),
value(Sep::Plus, char('+')),
value(Sep::Underscore, char('_')),
))(i)
}
}
impl PartialOrd for Mess {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Mess {
fn cmp(&self, other: &Self) -> Ordering {
match self.chunks.cmp(&other.chunks) {
Equal => {
let an = self.next.as_ref().map(|(_, m)| m);
let bn = other.next.as_ref().map(|(_, m)| m);
an.cmp(&bn)
}
ord => ord,
}
}
}
impl fmt::Display for Mess {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.chunks.iter().join("."))?;
if let Some((sep, m)) = &self.next {
write!(f, "{}{}", sep, m)?;
}
Ok(())
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum MChunk {
Digits(u32, String),
Rev(u32, String),
Plain(String),
}
impl MChunk {
pub fn text(&self) -> &str {
match self {
MChunk::Digits(_, s) => s,
MChunk::Rev(_, s) => s,
MChunk::Plain(s) => s,
}
}
fn parse(i: &str) -> IResult<&str, MChunk> {
alt((MChunk::digits, MChunk::rev, MChunk::plain))(i)
}
fn digits(i: &str) -> IResult<&str, MChunk> {
let (i, (u, s)) = map_res(recognize(digit1), |s: &str| {
s.parse::<u32>().map(|u| (u, s))
})(i)?;
let (i, _) = alt((peek(recognize(char('.'))), peek(recognize(Mess::sep))))(i)?;
let chunk = MChunk::Digits(u, s.to_string());
Ok((i, chunk))
}
fn rev(i: &str) -> IResult<&str, MChunk> {
let (i, _) = tag("r")(i)?;
let (i, (u, s)) = map_res(recognize(digit1), |s: &str| {
s.parse::<u32>().map(|u| (u, s))
})(i)?;
let (i, _) = alt((peek(recognize(char('.'))), peek(recognize(Mess::sep))))(i)?;
let chunk = MChunk::Rev(u, format!("r{}", s));
Ok((i, chunk))
}
fn plain(i: &str) -> IResult<&str, MChunk> {
let (i, s) = alphanumeric1(i)?;
let chunk = MChunk::Plain(s.to_string());
Ok((i, chunk))
}
}
impl PartialOrd for MChunk {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MChunk {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(MChunk::Digits(a, _), MChunk::Digits(b, _)) => a.cmp(b),
(MChunk::Rev(a, _), MChunk::Rev(b, _)) => a.cmp(b),
(MChunk::Digits(_, _), MChunk::Rev(_, _)) => Greater,
(MChunk::Rev(_, _), MChunk::Digits(_, _)) => Less,
(a, b) => a.text().cmp(b.text()),
}
}
}
impl fmt::Display for MChunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MChunk::Digits(_, s) => write!(f, "{}", s),
MChunk::Rev(_, s) => write!(f, "{}", s),
MChunk::Plain(s) => write!(f, "{}", s),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Sep {
Colon,
Hyphen,
Plus,
Underscore,
}
impl fmt::Display for Sep {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let c = match self {
Sep::Colon => ':',
Sep::Hyphen => '-',
Sep::Plus => '+',
Sep::Underscore => '_',
};
write!(f, "{}", c)
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
pub enum Unit {
Digits(u32),
Letters(String),
}
impl Unit {
pub fn from_string(s: String) -> Option<Unit> {
if s.chars().all(|c| c.is_ascii_alphabetic()) {
Some(Unit::Letters(s))
} else {
None
}
}
fn parse(i: &str) -> IResult<&str, Unit> {
alt((Unit::digits, Unit::string))(i)
}
fn digits(i: &str) -> IResult<&str, Unit> {
parsers::unsigned(i).map(|(i, x)| (i, Unit::Digits(x)))
}
fn string(i: &str) -> IResult<&str, Unit> {
alpha1(i).map(|(i, s)| (i, Unit::Letters(s.to_string())))
}
fn single_zero(i: &str) -> IResult<&str, Unit> {
map_res(tag("0"), |s: &str| s.parse::<u32>().map(Unit::Digits))(i)
}
}
impl fmt::Display for Unit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Unit::Digits(ns) => write!(f, "{}", ns),
Unit::Letters(cs) => write!(f, "{}", cs),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Chunk(pub Vec<Unit>);
impl Chunk {
pub fn single_digit(&self) -> Option<u32> {
match self.0.as_slice() {
[Unit::Digits(n)] => Some(*n),
_ => None,
}
}
pub fn single_digit_lenient(&self) -> Option<u32> {
match self.0.first() {
Some(Unit::Digits(n)) => Some(*n),
_ => None,
}
}
fn mchunk(&self) -> Option<MChunk> {
match self.0.as_slice() {
[] => None,
[Unit::Digits(u)] => Some(MChunk::Digits(*u, u.to_string())),
[Unit::Letters(s), Unit::Digits(u)] if s == "r" => {
Some(MChunk::Rev(*u, format!("r{}", u)))
}
[Unit::Letters(s)] => Some(MChunk::Plain(s.clone())),
_ => Some(MChunk::Plain(format!("{}", self))),
}
}
fn is_lettered(&self) -> bool {
match self.0.as_slice() {
[Unit::Letters(_), ..] => true,
_ => false,
}
}
fn parse(i: &str) -> IResult<&str, Chunk> {
map(Chunk::units, Chunk)(i)
}
fn units(i: &str) -> IResult<&str, Vec<Unit>> {
alt((
Chunk::zero_with_letters,
map(Unit::single_zero, |u| vec![u]),
many1(Unit::parse),
))(i)
}
fn zero_with_letters(i: &str) -> IResult<&str, Vec<Unit>> {
let (i, z) = Unit::single_zero(i)?;
let (i, s) = Unit::string(i)?;
let (i, c) = opt(Chunk::units)(i)?;
let mut us = vec![z, s];
if let Some(x) = c {
us.extend(x);
}
Ok((i, us))
}
}
impl PartialOrd for Chunk {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Chunk {
fn cmp(&self, other: &Self) -> Ordering {
self.0
.iter()
.zip_longest(&other.0)
.filter_map(|eob| match eob {
Left(_) => Some(Less),
Right(_) => Some(Greater),
Both(Unit::Digits(a), Unit::Digits(b)) => match a.cmp(b) {
Equal => None,
ord => Some(ord),
},
Both(Unit::Letters(a), Unit::Letters(b)) => match a.cmp(b) {
Equal => None,
ord => Some(ord),
},
Both(Unit::Digits(_), Unit::Letters(_)) => Some(Less),
Both(Unit::Letters(_), Unit::Digits(_)) => Some(Greater),
})
.next()
.unwrap_or(Equal)
}
}
impl fmt::Display for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for u in &self.0 {
write!(f, "{}", u)?;
}
Ok(())
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Chunks(pub Vec<Chunk>);
impl Chunks {
fn parse(i: &str) -> IResult<&str, Chunks> {
let (i, cs) = separated_list1(char('.'), Chunk::parse)(i)?;
Ok((i, Chunks(cs)))
}
fn pre_rel(i: &str) -> IResult<&str, Chunks> {
let (i, _) = char('-')(i)?;
Chunks::parse(i)
}
fn meta(i: &str) -> IResult<&str, Chunks> {
let (i, _) = char('+')(i)?;
Chunks::parse(i)
}
}
impl PartialOrd for Chunks {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Chunks {
fn cmp(&self, other: &Self) -> Ordering {
self.0
.iter()
.zip_longest(&other.0)
.filter_map(|eob| match eob {
Left(c) if c.is_lettered() => Some(Less),
Right(c) if c.is_lettered() => Some(Greater),
Left(_) => Some(Greater),
Right(_) => Some(Less),
Both(a, b) => match a.cmp(b) {
Equal => None,
ord => Some(ord),
},
})
.next()
.unwrap_or(Equal)
}
}
impl fmt::Display for Chunks {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s: String = self.0.iter().map(|c| format!("{}", c)).join(".");
write!(f, "{}", s)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Versioning {
Ideal(SemVer),
General(Version),
Complex(Mess),
}
impl Versioning {
pub fn new(s: &str) -> Option<Versioning> {
SemVer::new(s)
.map(Versioning::Ideal)
.or_else(|| Version::new(s).map(Versioning::General))
.or_else(|| Mess::new(s).map(Versioning::Complex))
}
pub fn is_ideal(&self) -> bool {
match self {
Versioning::Ideal(_) => true,
_ => false,
}
}
pub fn is_general(&self) -> bool {
match self {
Versioning::General(_) => true,
_ => false,
}
}
pub fn is_complex(&self) -> bool {
match self {
Versioning::Complex(_) => true,
_ => false,
}
}
}
impl PartialOrd for Versioning {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Versioning {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Versioning::Ideal(a), Versioning::Ideal(b)) => a.cmp(b),
(Versioning::General(a), Versioning::General(b)) => a.cmp(b),
(Versioning::Complex(a), Versioning::Complex(b)) => a.cmp(b),
(Versioning::Ideal(a), Versioning::General(b)) => a.cmp_version(b),
(Versioning::General(a), Versioning::Ideal(b)) => b.cmp_version(a).reverse(),
(Versioning::Ideal(a), Versioning::Complex(b)) => a.cmp_mess(b),
(Versioning::Complex(a), Versioning::Ideal(b)) => b.cmp_mess(a).reverse(),
(Versioning::General(a), Versioning::Complex(b)) => a.cmp_mess(b),
(Versioning::Complex(a), Versioning::General(b)) => b.cmp_mess(a).reverse(),
}
}
}
impl fmt::Display for Versioning {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Versioning::Ideal(s) => write!(f, "{}", s),
Versioning::General(v) => write!(f, "{}", v),
Versioning::Complex(m) => write!(f, "{}", m),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn official_semvers() {
let goods = vec![
"0.1.0",
"1.2.3",
"1.2.3-1",
"1.2.3-alpha",
"1.2.3-alpha.2",
"1.2.3+a1b2c3.1",
"1.2.3-alpha.2+a1b2c3.1",
];
for s in goods {
assert_eq!(
Some(s.to_string()),
SemVer::new(s).map(|sv| format!("{}", sv))
)
}
}
#[test]
fn good_semvers() {
let goods = vec!["0.4.8-1", "7.42.13-4", "2.1.16102-2", "2.2.1-b05"];
for s in goods {
assert_eq!(
Some(s.to_string()),
SemVer::new(s).map(|sv| format!("{}", sv))
)
}
}
#[test]
fn semver_zeros() {
let pre_rel = Chunks(vec![Chunk(vec![
Unit::Letters("b".to_string()),
Unit::Digits(0),
Unit::Digits(5),
])]);
assert_eq!(
Some(pre_rel),
SemVer::new("2.2.1-b05").and_then(|sv| sv.pre_rel)
);
}
#[test]
fn bad_semvers() {
let bads = vec![
"1",
"1.2",
"1.2.3+a1b2bc3.1-alpha.2",
"a.b.c",
"1.01.1",
"1.2.3+a1b!2c3.1",
"",
"1.2.3 ",
];
bads.iter().for_each(|s| assert_eq!(None, SemVer::new(s)));
}
#[test]
fn semver_ord() {
let svs = vec![
"1.0.0-alpha",
"1.0.0-alpha.1",
"1.0.0-alpha.beta",
"1.0.0-beta",
"1.0.0-beta.2",
"1.0.0-beta.11",
"1.0.0-rc.1",
"1.0.0",
];
for (a, b) in svs.iter().zip(&svs[1..]) {
let x = SemVer::new(a).unwrap();
let y = SemVer::new(b).unwrap();
assert!(x < y, "{} < {}", x, y);
}
}
#[test]
fn good_versions() {
let goods = vec![
"1",
"1.2",
"1.0rc0",
"1.0rc1",
"1.1rc1",
"44.0.2403.157-1",
"0.25-2",
"8.u51-1",
"21-2",
"7.1p1-1",
"20150826-1",
"1:0.10.16-3",
"1.11.0+20200830-1",
"8.64.0.81-1",
"1:3.20-1",
];
for s in goods {
assert!(SemVer::new(s).is_none(), "Shouldn't be SemVer: {}", s);
assert_eq!(
Some(s.to_string()),
Version::new(s).map(|v| format!("{}", v))
)
}
}
#[test]
fn bad_versions() {
let bads = vec!["", "1.2 "];
bads.iter().for_each(|b| assert_eq!(None, Version::new(b)));
}
#[test]
fn version_ord() {
let vs = vec!["0.9.9.9", "1.0.0.0", "1.0.0.1", "2"];
for (a, b) in vs.iter().zip(&vs[1..]) {
cmp_versions(a, b);
}
cmp_versions("1.2-5", "1.2.3-1");
cmp_versions("1.0rc1", "1.0");
cmp_versions("1.0", "1:1.0");
cmp_versions("1.1", "1:1.0");
cmp_versions("1.1", "1:1.1");
}
fn cmp_versions(a: &str, b: &str) {
let x = Version::new(a).unwrap();
let y = Version::new(b).unwrap();
assert!(x < y, "{} < {}", x, y);
}
#[test]
fn good_messes() {
let messes = vec![
"10.2+0.93+1-1",
"003.03-3",
"002.000-7",
"20.26.1_0-2",
"1.6.0a+2014+m872b87e73dfb-1",
"0.17.0+r8+gc41db5f1-1",
"0.17.0+r157+g584760cf-1",
"1.002.3+r003",
"1.3.00.16851-1",
"5.2.458699.0906-1",
];
for s in messes {
let sv = SemVer::new(s);
let vr = Version::new(s);
assert!(sv.is_none(), "Shouldn't be SemVer: {} -> {:#?}", s, sv);
assert!(vr.is_none(), "Shouldn't be Version: {} -> {:#?}", s, vr);
assert_eq!(Some(s.to_string()), Mess::new(s).map(|v| format!("{}", v)));
}
}
#[test]
fn bad_messes() {
let bads = vec!["", "003.03-3 "];
bads.iter().for_each(|b| assert_eq!(None, Mess::new(b)));
}
#[test]
fn mess_ord() {
let messes = vec![
"10.2+0.93+1-1",
"10.2+0.93+1-2",
"10.2+0.93+2-1",
"10.2+0.94+1-1",
"10.3+0.93+1-1",
"11.2+0.93+1-1",
"12",
];
for (a, b) in messes.iter().zip(&messes[1..]) {
cmp_messes(a, b);
}
}
fn cmp_messes(a: &str, b: &str) {
let x = Mess::new(a).unwrap();
let y = Mess::new(b).unwrap();
assert!(x < y, "{} < {}", x, y);
}
#[test]
fn equality() {
let vers = vec![
"1:3.20.1-1",
"1.3.00.25560-1",
"150_28-3",
"1.0.r15.g3fc772c-5",
"0.88-2",
];
for v in vers {
let x = Versioning::new(v).unwrap();
assert_eq!(Equal, x.cmp(&x));
}
}
#[test]
fn mixed_comparisons() {
cmp_versioning("1.2.2r1-1", "1.2.3-1");
cmp_versioning("1.2.3-1", "1.2.4r1-1");
cmp_versioning("1.2.3-1", "2+0007-1");
cmp_versioning("1.2.3r1-1", "2+0007-1");
cmp_versioning("1.2-5", "1.2.3-1");
cmp_versioning("1.6.0a+2014+m872b87e73dfb-1", "1.6.0-1");
cmp_versioning("1.11.0.git.20200404-1", "1.11.0+20200830-1");
cmp_versioning("0.17.0+r8+gc41db5f1-1", "0.17.0+r157+g584760cf-1");
cmp_versioning("2.2.3", "10e");
cmp_versioning("e.2.3", "1.2.3");
cmp_versioning("0.4.8-1", "0.4.9-1");
cmp_versioning("2.1.16102-2", "2.1.17627-1");
cmp_versioning("8.64.0.81-1", "8.65.0.78-1");
cmp_versioning("1.3.00.16851-1", "1.3.00.25560-1");
cmp_versioning("1:3.20-1", "1:3.20.1-1");
cmp_versioning("5.2.458699.0906-1", "5.3.472687.1012-1");
}
fn cmp_versioning(a: &str, b: &str) {
let x = Versioning::new(a).unwrap();
let y = Versioning::new(b).unwrap();
assert!(
x < y,
"\nAttempted: {} < {}\nLesser: {:?}\nGreater: {:?}\nThinks: {:?}",
x,
y,
x,
y,
x.cmp(&y)
);
}
#[test]
fn parsing_sanity() {
assert_eq!(Ok(34), "0034".parse::<u32>())
}
}