-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
SemVer.scala
120 lines (100 loc) · 3.89 KB
/
SemVer.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package lgbt.princess.v
package semver
import java.lang.{StringBuilder => JStringBuilder}
import lgbt.princess.v.semver.Identifiers._
import scala.collection.mutable.{StringBuilder => SStringBuilder}
/**
* A SemVer version.
*
* @param core the version core
* @param preRelease the pre-release identifiers, if any
* @param build the build identifiers, if any
*/
final case class SemVer(core: Core, preRelease: Option[PreRelease], build: Option[Build]) extends Ordered[SemVer] {
import SemVer._
def compare(that: SemVer): Int = ordering.compare(this, that)
override def toString: String = {
val sb = new JStringBuilder()
sb.append(core)
appendPrefixed(sb, '-', preRelease)
appendPrefixed(sb, '+', build)
sb.toString
}
}
object SemVer {
implicit val ordering: Ordering[SemVer] = (x, y) => {
val res1 = x.core compare y.core
if (res1 != 0) res1
else java.lang.Boolean.compare(x.preRelease.isEmpty, y.preRelease.isEmpty)
}
private def appendPrefixed(sb: JStringBuilder, prefix: Char, identifiers: Option[Identifiers]): Unit = {
if (identifiers.isDefined) {
sb.append(prefix)
identifiers.get.values.addString(new SStringBuilder(sb), ".")
}
}
/** @return a release SemVer version with no build identifiers. */
def apply(core: Core): SemVer = apply(core, None, None)
/**
* @return a pre-release SemVer version with the given pre-release
* identifiers and no build identifiers.
*/
def apply(core: Core, preRelease: PreRelease): SemVer = apply(core, Some(preRelease), None)
/** @return a release SemVer version with the given build identifiers. */
def apply(core: Core, build: Build): SemVer = apply(core, None, Some(build))
/**
* @return a pre-release SemVer version with the given pre-release and
* build identifiers.
*/
def apply(core: Core, preRelease: PreRelease, build: Build): SemVer =
apply(core, Some(preRelease), Some(build))
private[this] def splitVersion(version: String): (String, Option[String], Option[String]) = {
val plusSplit = version.split("""\+""", 2)
val dashSplit = plusSplit(0).split("-", 2)
val core = dashSplit(0)
val preRelease = if (dashSplit.length == 2) Some(dashSplit(1)) else None
val build = if (plusSplit.length == 2) Some(plusSplit(1)) else None
(core, preRelease, build)
}
/**
* Parses a string representation of a SemVer version.
*
* @param version the string representation of a version
* @return an [[scala.Option Option]] containing the SemVer version represented
* by the string, if it represented a valid SemVer version
*/
def parse(version: String): Option[SemVer] = {
def parseIdentifiers[I <: Identifiers](factory: Factory[I])(identifiers: Option[String]): Option[Option[I]] =
identifiers match {
case None => Some(None)
case Some(str) => factory.parse(str).map(Some(_))
}
val (coreStr, preReleaseStr, buildStr) = splitVersion(version)
for {
core <- Core parse coreStr
preRelease <- parseIdentifiers(PreRelease)(preReleaseStr)
build <- parseIdentifiers(Build)(buildStr)
} yield apply(core, preRelease, build)
}
/**
* Parses a string representation of a SemVer version.
*
* @param version the string representation of a version
* @return the SemVer version represented by the string
* @throws VersionFormatException if the string did not represent
* a valid SemVer version
*/
@throws[VersionFormatException]
def unsafeParse(version: String): SemVer = {
val (core, preRelease, build) = splitVersion(version)
try {
apply(
Core unsafeParse core,
preRelease map PreRelease.unsafeParse,
build map Build.unsafeParse,
)
} catch {
case e: IllegalArgumentException => throw new VersionFormatException(version, "SemVer version", e)
}
}
}