Skip to content

Instantly share code, notes, and snippets.

@huntc huntc/SemVer.scala
Last active Mar 22, 2019

Embed
What would you like to do?
Semantic version case class
import scala.util.Try
type PreRelease = Either[String, Int]
object SemVer {
val pattern = """^(\d+)\.(\d+)\.(\d+)(-(([1-9a-zA-Z][0-9a-zA-Z-]*)(\.([1-9a-zA-Z][0-9a-zA-Z-]*))))?$""".r
def apply(s: String): SemVer =
s match {
case pattern(major, minor, patch, _, _, preRelease1, _, preRelease2) =>
val majorCapture = major.toInt
val minorCapture = minor.toInt
val patchCapture = patch.toInt
preRelease1 match {
case preRelease1Capture: Any =>
preRelease2 match {
case preRelease2Capture: Any =>
SemVer(
majorCapture,
minorCapture,
patchCapture,
Some(toPreRelease(preRelease1Capture) -> Some(toPreRelease(preRelease2Capture))))
case _ =>
SemVer(
majorCapture,
minorCapture,
patchCapture,
Some(toPreRelease(preRelease1Capture) -> None))
}
case _ =>
SemVer(
majorCapture,
minorCapture,
patchCapture,
None)
}
}
private def toPreRelease(s: String): PreRelease =
Try(Right(s.toInt)).getOrElse(Left(s))
private def comparePreReleases(preRelease: PreRelease, thatPreRelease: PreRelease): Int =
(preRelease, thatPreRelease) match {
case (Left(value), Left(thatValue)) =>
value compare thatValue
case (Left(_), Right(_)) =>
-1
case (Right(value), Right(thatValue)) =>
value compare thatValue
case (Right(_), Left(_)) =>
1
}
}
case class SemVer(major: Int, minor: Int, patch: Int, preRelease: Option[(PreRelease, Option[PreRelease])]) extends Ordered[SemVer] {
import SemVer._
def compare(that: SemVer): Int = {
import scala.math.Ordered.orderingToOrdered
val result = (major, minor, patch) compare (that.major, that.minor, that.patch)
if (result == 0)
(preRelease, that.preRelease) match {
case (Some(p), Some(thatP)) =>
(p, thatP) match {
case ((preRelease1, Some(preRelease2)), (thatPreRelease1, Some(thatPreRelease2))) =>
val preRelease1Result = comparePreReleases(preRelease1, thatPreRelease1)
if (preRelease1Result == 0) comparePreReleases(preRelease2, thatPreRelease2) else preRelease1Result
case ((preRelease1, None), (thatPreRelease1, Some(thatPreRelease2))) =>
val preRelease1Result = comparePreReleases(preRelease1, thatPreRelease1)
if (preRelease1Result == 0) 1 else preRelease1Result
case ((preRelease1, Some(preRelease2)), (thatPreRelease1, None)) =>
val preRelease1Result = comparePreReleases(preRelease1, thatPreRelease1)
if (preRelease1Result == 0) -1 else preRelease1Result
case ((preRelease1, None), (thatPreRelease1, None)) =>
comparePreReleases(preRelease1, thatPreRelease1)
}
case (Some(_), None) =>
-1
case (None, Some(_)) =>
1
case (None, None) =>
0
}
else
result
}
}
// Examples
assert((SemVer("1.1.1") :: SemVer("1.2.0") :: Nil).sorted == List(SemVer(1,1,1,None), SemVer(1,2,0,None)))
assert((SemVer("3.1.1") :: SemVer("1.2.0") :: Nil).sorted == List(SemVer(1,2,0,None), SemVer(3,1,1,None)))
assert((SemVer("1.1.1") :: SemVer("1.1.1") :: Nil).sorted == List(SemVer(1,1,1,None), SemVer(1,1,1,None)))
assert((SemVer("1.1.1") :: SemVer("1.1.1-beta.1") :: Nil).sorted == List(SemVer(1,1,1,Some((Left("beta"),Some(Right(1))))), SemVer(1,1,1,None)))
assert((SemVer("1.1.1-beta.2") :: SemVer("1.1.1-beta.1") :: Nil).sorted == List(SemVer(1,1,1,Some((Left("beta"),Some(Right(1))))), SemVer(1,1,1,Some((Left("beta"),Some(Right(2)))))))
assert((SemVer("1.1.1-beta.2") :: SemVer("1.1.1-alpha.1") :: Nil).sorted == List(SemVer(1,1,1,Some((Left("alpha"),Some(Right(1))))), SemVer(1,1,1,Some((Left("beta"),Some(Right(2)))))))
@staypufd

This comment has been minimized.

Copy link

staypufd commented Mar 14, 2019

What license does this code have?

@huntc

This comment has been minimized.

Copy link
Owner Author

huntc commented Mar 15, 2019

Let’s make it Apache2

@staypufd

This comment has been minimized.

Copy link

staypufd commented Mar 15, 2019

Thanks - I really appreciate it. Makes my life so much easier knowing I can use this. I'm not a Scala expert and this is so much cleaner and uses the idioms of Scala that are still not natural to me yet. Thanks again.

@staypufd

This comment has been minimized.

Copy link

staypufd commented Mar 22, 2019

Here is an update to the RegEx to be a bit more compliant with SemanticVersion 2.0 spec. https://semver.org/

https://jex.im/regulex/#!flags=&re=%5E(%5Cd%2B)%5C.(%5Cd%2B)%5C.(%5Cd%2B)(%5B-%2B%5D*((%5B1-9a-zA-Z%5D%5B0-9a-zA-Z-%5D*)(%5C.(%5B0-9a-zA-Z%5D%5B0-9a-zA-Z-%2B%5D*))*))%3F%24

image

^(\d+)\.(\d+)\.(\d+)([-+]*(([1-9a-zA-Z][0-9a-zA-Z-]*)(\.([0-9a-zA-Z][0-9a-zA-Z-+]*))*))?$

Examples of what it parses correctly:

// 1.2.3-Beta-123.0123, 1.2.3-Alpha-4.3, 123.22.1, 123.22.1+Gamma-044.22, 123.22.1+Theta+044.22, 1.9.0++Beta.1.22+ABC, 1.2.3-Beta-0123.145+ABC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.