use crate::{Chunk, Chunks, Error, MChunk, Mess, Release, Sep, Version};
use nom::character::complete::char;
use nom::combinator::opt;
use nom::{IResult, Parser};
use std::cmp::Ordering;
use std::cmp::Ordering::{Equal, Greater, Less};
use std::hash::{Hash, Hasher};
use std::str::FromStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Eq, Clone, Default)]
pub struct SemVer {
pub major: u32,
pub minor: u32,
pub patch: u32,
pub pre_rel: Option<Release>,
pub meta: Option<String>,
}
impl SemVer {
pub fn new<S>(s: S) -> Option<SemVer>
where
S: AsRef<str>,
{
match SemVer::parse(s.as_ref()) {
Ok(("", sv)) => Some(sv),
_ => None,
}
}
pub fn to_version(&self) -> Version {
let chunks = Chunks(vec![
Chunk::Numeric(self.major),
Chunk::Numeric(self.minor),
Chunk::Numeric(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().map(|c| c.mchunk()).collect();
let next = self.meta.as_ref().map(|meta| {
let chunks = vec![MChunk::Plain(meta.clone())];
(Sep::Plus, Box::new(Mess { chunks, next: None }))
});
(Sep::Hyphen, Box::new(Mess { chunks, next }))
});
Mess { chunks, next }
}
pub(crate) fn cmp_version(&self, other: &Version) -> Ordering {
match other.epoch {
Some(n) if n > 0 => Less,
_ => 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) => match other.chunks.0.get(3) {
Some(Chunk::Alphanum(_)) => Greater,
Some(Chunk::Numeric(_)) => Less,
None => self.pre_rel.cmp(&other.release),
},
},
},
},
}
}
pub(crate) 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) = crate::parsers::unsigned(i)?;
let (i, _) = char('.')(i)?;
let (i, minor) = crate::parsers::unsigned(i)?;
let (i, _) = char('.')(i)?;
let (i, patch) = crate::parsers::unsigned(i)?;
let (i, pre_rel) = opt(Release::parse).parse(i)?;
let (i, meta) = opt(crate::parsers::meta).parse(i)?;
let sv = SemVer {
major,
minor,
patch,
pre_rel,
meta,
};
Ok((i, sv))
}
pub(crate) fn matches_tilde(&self, other: &SemVer) -> bool {
self.major == other.major && self.minor == other.minor && other.patch >= self.patch
}
pub(crate) fn matches_caret(&self, other: &SemVer) -> bool {
if self.major == 0 && other.major == 0 {
if self.minor == 0 && other.minor == 0 {
other.patch == self.patch
} else {
other.minor == self.minor && other.patch >= self.patch
}
} else {
other.major == self.major
&& (other.minor > self.minor
|| (other.minor >= self.minor && other.patch >= self.patch))
}
}
}
impl Hash for SemVer {
fn hash<H: Hasher>(&self, state: &mut H) {
self.major.hash(state);
self.minor.hash(state);
self.patch.hash(state);
self.pre_rel.hash(state);
}
}
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 std::fmt::Display for SemVer {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::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(())
}
}
impl FromStr for SemVer {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
SemVer::new(s).ok_or_else(|| Error::IllegalSemver(s.to_string()))
}
}
impl TryFrom<&str> for SemVer {
type Error = Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
SemVer::from_str(value)
}
}