sbt-gs-semver/src/main/scala/gs/SemVer.scala
2026-07-21 02:37:02 +00:00

131 lines
3.5 KiB
Scala

package gs
import scala.util.matching.Regex
import sjsonnew.*
import sjsonnew.BasicJsonProtocol.*
/** Simplified representation of SemVer. Only supports major, minor and patch
* versions. Not intended to be used as a generalized SemVer implementation.
*
* @param major
* The major version.
* @param minor
* The minor version.
* @param patch
* The patch version.
*/
case class SemVer(
major: Int,
minor: Int,
patch: Int
) {
/** Render this SemVer as: `Major.Minor.Patch`
*
* @return
* The `Major.Minor.Patch` representation of this SemVer.
*/
override def toString(): String =
s"$major.$minor.$patch"
/** Render this SemVer as: `Major.Minor.Patch`, and append a suffix,
* `-SNAPSHOT` if a snapshot rendition is requested.
*
* @param isSnapshot
* Requests a snapshot rendition if true. Uses the `-SNAPSHOT` pre-release
* suffix.
* @return
* The string rendition of this version.
*/
def render(buildType: BuildType): String =
buildType match
case BuildType.Release => s"$major.$minor.$patch"
case BuildType.Snapshot => s"$major.$minor.$patch-SNAPSHOT"
/** @return
* Copy of this SemVer with the major version incremented by 1.
*/
def incrementMajor(): SemVer =
copy(
major = this.major + 1,
minor = 0,
patch = 0
)
/** @return
* Copy of this SemVer with the minor version incremented by 1.
*/
def incrementMinor(): SemVer =
copy(
minor = this.minor + 1,
patch = 0
)
/** @return
* Copy of this SemVer with the patch version incremented by 1.
*/
def incrementPatch(): SemVer = copy(patch = this.patch + 1)
def applyReleaseType(releaseType: ReleaseType): SemVer =
releaseType match
case ReleaseType.Major => incrementMajor()
case ReleaseType.Minor => incrementMinor()
case ReleaseType.Patch => incrementPatch()
}
object SemVer:
/** Version 0.1.0 -- the default version.
*/
val DefaultVersion: SemVer = SemVer(0, 1, 0)
given CanEqual[SemVer, SemVer] = CanEqual.derived
// Required for SBT serialization.
given JsonFormat[SemVer] = new JsonFormat[SemVer] {
override def write[J](
obj: SemVer,
builder: Builder[J]
): Unit =
builder.beginObject()
builder.addField("major", obj.major)
builder.addField("minor", obj.minor)
builder.addField("patch", obj.patch)
builder.endObject()
override def read[J](
jsOpt: Option[J],
unbuilder: Unbuilder[J]
): SemVer =
jsOpt match
case Some(js) =>
unbuilder.beginObject(js)
val major = unbuilder.readField[Int]("major")
val minor = unbuilder.readField[Int]("minor")
val patch = unbuilder.readField[Int]("patch")
unbuilder.endObject()
SemVer(major, minor, patch)
case None =>
deserializationError("Expected JsObject but found None")
}
private val SemVerPattern: Regex =
"^(0|(?:[1-9][0-9]*))\\.(0|(?:[1-9][0-9]*))\\.(0|(?:[1-9][0-9]*))$".r
/** Attempt to parse the input string as a [[SemVer]].
*
* This does not handle or produce snapshot versions.
*
* @param candidate
* The candidate string to parse.
* @return
* The parsed [[SemVer]], or `None` if parsing failed.
*/
def parse(candidate: String): Option[SemVer] =
candidate match
case SemVerPattern(major, minor, patch) =>
Some(SemVer(major.toInt, minor.toInt, patch.toInt))
case _ =>
None