All Downloads are FREE. Search and download functionalities are using the official Maven repository.

just.semver.SemVer.scala Maven / Gradle / Ivy

The newest version!
package just.semver

import just.Common.*
//import just.decver.DecVer
import just.semver.AdditionalInfo.{BuildMetaInfo, PreRelease}
import just.semver.SemVer.{Major, Minor, Patch}
import just.semver.matcher.SemVerMatchers

import scala.util.matching.Regex

/** @author Kevin Lee
  * @since 2018-10-21
  */
final case class SemVer(
  major: Major,
  minor: Minor,
  patch: Patch,
  pre: Option[PreRelease],
  buildMetadata: Option[BuildMetaInfo]
) extends Ordered[SemVer]
    derives CanEqual {

  override def compare(that: SemVer): Int = {
    (
      this.major.value.compareTo(that.major.value),
      this.minor.value.compareTo(that.minor.value),
      this.patch.value.compareTo(that.patch.value)
    ) match {
      case (0, 0, 0) =>
        (this.pre, that.pre) match {
          case (Some(thisPre), Some(thatPre)) =>
            thisPre.identifier.compareElems(thatPre.identifier)

          case (Some(_), None) =>
            -1
          case (None, Some(_)) =>
            1
          case (None, None) =>
            0
        }
      case (0, 0, pt) =>
        pt
      case (0, mn, _) =>
        mn
      case (mj, _, _) =>
        mj
    }
  }

}

object SemVer {

  type Major = Major.Major
  object Major {
    opaque type Major = Int
    def apply(major: Int): Major         = major
    def unapply(major: Major): Some[Int] = Some(major)

    given majorCanEqual: CanEqual[Major, Major] = CanEqual.derived

    extension (major0: Major) {
      def value: Int = major0
      def major: Int = major0
    }
  }

  type Minor = Minor.Minor
  object Minor {
    opaque type Minor = Int
    def apply(minor: Int): Minor         = minor
    def unapply(minor: Minor): Some[Int] = Some(minor)

    given minorCanEqual: CanEqual[Minor, Minor] = CanEqual.derived

    extension (minor0: Minor) {
      def value: Int = minor0
      def minor: Int = minor0
    }
  }

  type Patch = Patch.Patch
  object Patch {
    opaque type Patch = Int
    def apply(patch: Int): Patch         = patch
    def unapply(patch: Patch): Some[Int] = Some(patch)

    given patchCanEqual: CanEqual[Patch, Patch] = CanEqual.derived

    extension (patch0: Patch) {
      def value: Int = patch0
      def patch: Int = patch0
    }
  }

  val major0: Major = Major(0)
  val minor0: Minor = Minor(0)
  val patch0: Patch = Patch(0)

  val semVerRegex: Regex = """(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z\d-\.]+)?)?(?:\+([a-zA-Z\d-\.]+)?)?""".r

  extension (semVer: SemVer) {
    inline def majorMinorPatch: (SemVer.Major, SemVer.Minor, SemVer.Patch) =
      (semVer.major, semVer.minor, semVer.patch)

    def renderMajorMinorPatch: String =
      s"${semVer.major.value.toString}.${semVer.minor.value.toString}.${semVer.patch.value.toString}"

    def render: String = semVer match {
      case SemVer(major, minor, patch, pre, buildMetadata) =>
        val versionString        = s"${major.value.toString}.${minor.value.toString}.${patch.value.toString}"
        val additionalInfoString =
          (pre, buildMetadata) match {
            case (Some(p), Some(m)) =>
              s"-${PreRelease.render(p)}+${BuildMetaInfo.render(m)}"
            case (Some(p), None) =>
              s"-${PreRelease.render(p)}"
            case (None, Some(m)) =>
              s"+${BuildMetaInfo.render(m)}"
            case (None, None) =>
              ""
          }
        versionString + additionalInfoString
    }

    def matches(semVerMatchers: SemVerMatchers): Boolean = semVerMatchers.matches(semVer)

    def unsafeMatches(semVerMatchers: String): Boolean =
      SemVerMatchers
        .unsafeParse(semVerMatchers)
        .matches(semVer)

//    def toDecVer: DecVer = DecVer.fromSemVer(semVer)

  }

  def unsafeParse(version: String): SemVer =
    parse(version) match {
      case Right(semVer) =>
        semVer
      case Left(error) =>
        sys.error(ParseError.render(error))
    }

  def parseUnsafe(version: String): SemVer = unsafeParse(version)

  def parse(version: String): Either[ParseError, SemVer] = version match {
    case semVerRegex(major, minor, patch, pre, meta) =>
      val preRelease = AdditionalInfo.parsePreRelease(pre)
      val metaInfo   = AdditionalInfo.parseBuildMetaInfo(meta)
      (preRelease, metaInfo) match {
        case (Left(preError), Left(metaError)) =>
          Left(
            ParseError.combine(
              ParseError.fromAdditionalInfoParserError(preError),
              ParseError.fromAdditionalInfoParserError(metaError)
            )
          )
        case (Left(preError), _) =>
          Left(ParseError.preReleaseParseError(ParseError.fromAdditionalInfoParserError(preError)))
        case (_, Left(metaError)) =>
          Left(ParseError.buildMetadataParseError(ParseError.fromAdditionalInfoParserError(metaError)))
        case (Right(preR), Right(metaI)) =>
          Right(
            SemVer(
              Major(major.toInt),
              Minor(minor.toInt),
              Patch(patch.toInt),
              preR,
              metaI
            )
          )
      }

    case _ =>
      Left(ParseError.invalidVersionStringError(version))
  }

  def semVer(major: Major, minor: Minor, patch: Patch): SemVer =
    SemVer(major, minor, patch, None, None)

  def withMajor(major: Major): SemVer =
    SemVer(major, minor0, patch0, None, None)

  def withMinor(minor: Minor): SemVer =
    SemVer(major0, minor, patch0, None, None)

  def withPatch(patch: Patch): SemVer =
    SemVer(major0, minor0, patch, None, None)

  def increaseMajor(semVer: SemVer): SemVer =
    semVer.copy(major = Major(semVer.major.value + 1))

  def increaseMinor(semVer: SemVer): SemVer =
    semVer.copy(minor = Minor(semVer.minor.value + 1))

  def increasePatch(semVer: SemVer): SemVer =
    semVer.copy(patch = Patch(semVer.patch.value + 1))

//  def fromDecVer(decVer: DecVer): SemVer =
//    SemVer.semVer(SemVer.Major(decVer.major.value), SemVer.Minor(decVer.minor.value), patch0)

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy