use crate::BoxSlice;
use semver::Op;
use solar_data_structures::smallvec::{SmallVec, smallvec};
use solar_interface::Span;
use std::{cmp::Ordering, fmt};
pub use semver::Op as SemverOp;
#[derive(Clone, Copy)]
pub enum SemverVersionNumber {
Number(u32),
Wildcard,
}
impl From<u64> for SemverVersionNumber {
#[inline]
fn from(n: u64) -> Self {
match n.try_into() {
Ok(n) => Self::Number(n),
Err(_) => Self::Wildcard,
}
}
}
impl From<SemverVersionNumber> for u64 {
#[inline]
fn from(value: SemverVersionNumber) -> Self {
match value {
SemverVersionNumber::Number(n) => n as Self,
SemverVersionNumber::Wildcard => Self::MAX,
}
}
}
impl fmt::Display for SemverVersionNumber {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Number(n) => n.fmt(f),
Self::Wildcard => "*".fmt(f),
}
}
}
impl fmt::Debug for SemverVersionNumber {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl PartialEq for SemverVersionNumber {
#[inline]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Wildcard, _) | (_, Self::Wildcard) => true,
(Self::Number(a), Self::Number(b)) => a == b,
}
}
}
impl Eq for SemverVersionNumber {}
impl PartialOrd for SemverVersionNumber {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SemverVersionNumber {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Self::Wildcard, _) | (_, Self::Wildcard) => Ordering::Equal,
(Self::Number(a), Self::Number(b)) => a.cmp(b),
}
}
}
#[derive(Clone)]
pub struct SemverVersion {
pub span: Span,
pub major: SemverVersionNumber,
pub minor: Option<SemverVersionNumber>,
pub patch: Option<SemverVersionNumber>,
}
impl PartialEq for SemverVersion {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for SemverVersion {}
impl PartialOrd for SemverVersion {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SemverVersion {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
#[inline]
fn cmp_opt(a: &Option<SemverVersionNumber>, b: &Option<SemverVersionNumber>) -> Ordering {
match (a, b) {
(Some(a), Some(b)) => a.cmp(b),
_ => Ordering::Equal,
}
}
self.major
.cmp(&other.major)
.then_with(|| cmp_opt(&self.minor, &other.minor))
.then_with(|| cmp_opt(&self.patch, &other.patch))
}
}
impl fmt::Display for SemverVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { span: _, major, minor, patch } = *self;
write!(f, "{major}")?;
if let Some(minor) = minor {
write!(f, ".{minor}")?;
}
if let Some(patch) = patch {
if minor.is_none() {
f.write_str(".*")?;
}
write!(f, ".{patch}")?;
}
Ok(())
}
}
impl fmt::Debug for SemverVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SemverVersion")
.field("span", &self.span)
.field("version", &format_args!("{self}"))
.finish()
}
}
impl From<semver::Version> for SemverVersion {
#[inline]
fn from(version: semver::Version) -> Self {
Self {
span: Span::DUMMY,
major: version.major.into(),
minor: Some(version.minor.into()),
patch: Some(version.patch.into()),
}
}
}
impl From<SemverVersion> for semver::Version {
#[inline]
fn from(version: SemverVersion) -> Self {
Self::new(
version.major.into(),
version.minor.map(Into::into).unwrap_or(0),
version.patch.map(Into::into).unwrap_or(0),
)
}
}
impl SemverVersion {
#[inline]
pub fn into_semver(self) -> semver::Version {
self.into()
}
}
#[derive(Debug)]
pub struct SemverReq<'ast> {
pub dis: BoxSlice<'ast, SemverReqCon<'ast>>,
}
impl fmt::Display for SemverReq<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, con) in self.dis.iter().enumerate() {
if i > 0 {
f.write_str(" || ")?;
}
write!(f, "{con}")?;
}
Ok(())
}
}
impl SemverReq<'_> {
pub fn matches(&self, version: &SemverVersion) -> bool {
self.dis.iter().any(|c| c.matches(version))
}
pub fn to_semver(&self) -> SemverVersionReqCompat {
SemverVersionReqCompat { reqs: self.dis.iter().map(SemverReqCon::to_semver).collect() }
}
}
pub struct SemverVersionReqCompat {
pub reqs: Vec<semver::VersionReq>,
}
impl SemverVersionReqCompat {
pub fn matches(&self, version: &semver::Version) -> bool {
self.reqs.iter().any(|r| r.matches(version))
}
}
#[derive(Debug)]
pub struct SemverReqCon<'ast> {
pub span: Span,
pub components: BoxSlice<'ast, SemverReqComponent>,
}
impl fmt::Display for SemverReqCon<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (j, component) in self.components.iter().enumerate() {
if j > 0 {
f.write_str(" ")?;
}
write!(f, "{component}")?;
}
Ok(())
}
}
impl SemverReqCon<'_> {
pub fn to_semver(&self) -> semver::VersionReq {
semver::VersionReq {
comparators: self.components.iter().flat_map(SemverReqComponent::to_semver).collect(),
}
}
pub fn matches(&self, version: &SemverVersion) -> bool {
self.components.iter().all(|c| c.matches(version))
}
}
#[derive(Clone, Debug)]
pub struct SemverReqComponent {
pub span: Span,
pub kind: SemverReqComponentKind,
}
impl fmt::Display for SemverReqComponent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.kind.fmt(f)
}
}
impl SemverReqComponent {
pub fn to_semver(&self) -> SmallVec<[semver::Comparator; 2]> {
self.kind.to_semver()
}
pub fn matches(&self, version: &SemverVersion) -> bool {
self.kind.matches(version)
}
}
#[derive(Clone, Debug)]
pub enum SemverReqComponentKind {
Op(Option<Op>, SemverVersion),
Range(SemverVersion, SemverVersion),
}
impl fmt::Display for SemverReqComponentKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Op(op, version) => {
if let Some(op) = op {
let op = match op {
Op::Exact => "=",
Op::Greater => ">",
Op::GreaterEq => ">=",
Op::Less => "<",
Op::LessEq => "<=",
Op::Tilde => "~",
Op::Caret => "^",
Op::Wildcard => "*",
_ => "",
};
f.write_str(op)?;
}
write!(f, "{version}")
}
Self::Range(left, right) => write!(f, "{left} - {right}"),
}
}
}
impl SemverReqComponentKind {
pub fn to_semver(&self) -> SmallVec<[semver::Comparator; 2]> {
let cvt_op = |op: Option<Op>, version: &SemverVersion| semver::Comparator {
op: op.unwrap_or(Op::Exact),
major: version.major.into(),
minor: version.minor.map(Into::into),
patch: version.patch.map(Into::into),
pre: Default::default(),
};
match self {
Self::Op(op, version) => smallvec![cvt_op(*op, version)],
Self::Range(start, end) => smallvec![
cvt_op(Some(semver::Op::GreaterEq), start),
cvt_op(Some(semver::Op::LessEq), end)
],
}
}
pub fn matches(&self, version: &SemverVersion) -> bool {
match self {
Self::Op(op, other) => matches_op(op.unwrap_or(Op::Exact), version, other),
Self::Range(start, end) => {
matches_op(Op::GreaterEq, version, start) && matches_op(Op::LessEq, version, end)
}
}
}
}
fn matches_op(op: Op, a: &SemverVersion, b: &SemverVersion) -> bool {
match op {
Op::Exact => a == b,
Op::Greater => a > b,
Op::GreaterEq => a >= b,
Op::Less => a < b,
Op::LessEq => a <= b,
Op::Tilde => matches_tilde(a, b),
Op::Caret => matches_caret(a, b),
Op::Wildcard => true,
_ => false,
}
}
fn matches_tilde(a: &SemverVersion, b: &SemverVersion) -> bool {
if !matches_op(Op::GreaterEq, a, b) {
return false;
}
let mut a = a.clone();
a.patch = None;
matches_op(Op::LessEq, &a, b)
}
fn matches_caret(a: &SemverVersion, b: &SemverVersion) -> bool {
if !matches_op(Op::GreaterEq, a, b) {
return false;
}
let mut a = a.clone();
if a.major > SemverVersionNumber::Number(0) {
a.minor = None;
}
a.patch = None;
matches_op(Op::LessEq, &a, b)
}