use anyhow::{Error, Result};
use pubgrub::range::Range;
use semver::VersionReq;
use std::convert::From;
use std::{fmt::Display, str::FromStr};
#[macro_export]
macro_rules! semver {
($version:literal) => {
SemanticVersion::from_str($version).expect("Not a valid semantic version string.")
};
($version:expr) => {
SemanticVersion::from_str($version).expect("Not a valid semantic version string.")
};
}
pub trait RangeExt<V>
where
V: pubgrub::version::Version + From<SemanticVersion>,
{
fn subset_of(&self, other: &Range<V>) -> bool;
fn possible(&self, other: &Range<V>) -> bool;
}
impl RangeExt<SemanticVersion> for Range<SemanticVersion> {
fn subset_of(&self, other: &Self) -> bool {
Self::intersection(self, other) == *self
}
fn possible(&self, other: &Self) -> bool {
Self::intersection(self, other) != Self::none()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum SemanticVersionCompleteness {
OnlyMajor,
OnlyMinorAndMajor,
Complete,
}
pub const fn semver_completeness(comparator: &semver::Comparator) -> SemanticVersionCompleteness {
use SemanticVersionCompleteness::*;
if comparator.minor.is_some() && comparator.patch.is_some() {
return Complete;
}
if comparator.minor.is_some() && comparator.patch.is_none() {
return OnlyMinorAndMajor;
}
OnlyMajor
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default)]
pub struct SemanticVersion {
major: u64,
minor: u64,
patch: u64,
}
impl serde::Serialize for SemanticVersion {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("{}", self))
}
}
impl<'de> serde::Deserialize<'de> for SemanticVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
FromStr::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl SemanticVersion {
pub const fn new(major: u64, minor: u64, patch: u64) -> Self {
Self {
major,
minor,
patch,
}
}
pub const fn as_sum(&self) -> u64 {
self.major + self.minor + self.patch
}
pub const fn zero() -> Self {
Self::new(0, 0, 0)
}
#[must_use]
pub const fn bump_patch(self) -> Self {
Self::new(self.major, self.minor, self.patch + 1)
}
#[must_use]
pub const fn bump_minor(self) -> Self {
Self::new(self.major, self.minor + 1, 0)
}
#[must_use]
pub const fn bump_major(self) -> Self {
Self::new(self.major + 1, 0, 0)
}
#[must_use]
pub const fn is_patch_zero(&self) -> bool {
self.patch == 0
}
#[must_use]
pub const fn is_minor_zero(&self) -> bool {
self.minor == 0
}
#[must_use]
pub const fn is_major_zero(&self) -> bool {
self.major == 0
}
}
impl Display for SemanticVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
impl From<(u64, u64, u64)> for SemanticVersion {
fn from(tuple: (u64, u64, u64)) -> Self {
let (major, minor, patch) = tuple;
Self::new(major, minor, patch)
}
}
impl From<SemanticVersion> for (u64, u64, u64) {
fn from(v: SemanticVersion) -> Self {
(v.major, v.minor, v.patch)
}
}
#[allow(clippy::indexing_slicing)]
impl TryFrom<&str> for SemanticVersion {
type Error = Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let req = VersionReq::parse(s)?;
Ok(Self {
major: req.comparators[0].major,
minor: req.comparators[0].minor.unwrap_or(0),
patch: req.comparators[0].patch.unwrap_or(0),
})
}
}
#[allow(clippy::indexing_slicing)]
impl TryFrom<String> for SemanticVersion {
type Error = Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::try_from(&s)
}
}
#[allow(clippy::indexing_slicing)]
impl TryFrom<&String> for SemanticVersion {
type Error = Error;
fn try_from(s: &String) -> Result<Self, Self::Error> {
Self::try_from(s.as_str())
}
}
#[allow(clippy::indexing_slicing)]
impl FromStr for SemanticVersion {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
}
impl pubgrub::version::Version for SemanticVersion {
fn lowest() -> Self {
Self::zero()
}
fn bump(&self) -> Self {
self.bump_patch()
}
}
pub use semver;