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