sbt / sbt-release

A release plugin for sbt
Apache License 2.0
643 stars 163 forks source link

Semantic versioning #86

Open davidhoyt opened 9 years ago

davidhoyt commented 9 years ago

Perhaps future releases could use semantic versioning (http://semver.org/) so I can include a pre-release modifier such as "1.0.0-rc.1-SNAPSHOT". Here's a potential implementation:

import scala.util.control.Exception._

object SemanticVersion {
  sealed trait Bump {
    def bump: SemanticVersion => SemanticVersion
  }

  object Bump {
    object Major extends Bump { def bump = _.bumpMajor }
    object Minor extends Bump { def bump = _.bumpMinor }
    object Bugfix extends Bump { def bump = _.bumpBugfix }
    object Next extends Bump { def bump = _.bump }

    val default = Next
  }

  val SemanticVersionR = """(\d+)\.(\d+)\.(\d+)(\-[.\w]+)?(\-.+)?""".r

  def apply(s: String): Option[SemanticVersion] = {
    allCatch opt {
      val SemanticVersionR(maj, min, mic, qual1, qual2) = s
      val optQual1 = Option(qual1)
      val optQual2 = Option(qual2)
      val isQual1Snapshot = optQual1.exists(_.toUpperCase.endsWith("SNAPSHOT"))
      val prerelease = if (!isQual1Snapshot) optQual1 else None
      val qual =
        if (isQual1Snapshot)
          optQual1
        else if (optQual2.isDefined)
          optQual2
        else
          None

      SemanticVersion(maj.toInt, Option(min).map(_.toInt), Option(mic).map(_.toInt), prerelease, qual.filterNot(_.isEmpty))
    }
  }

  def versionFormatError =
    sys.error(s"Unrecognized version. Please ensure it is compatible with the semantic versioning specification (http://semver.org/) and this pattern: ${SemanticVersionR.pattern.toString}")
}

case class SemanticVersion(major: Int, minor: Option[Int], bugfix: Option[Int], prerelease: Option[String], qualifier: Option[String]) {
  def bump = {
    val maybeBumpedBugfix = bugfix.map(m => copy(bugfix = Some(m + 1)))
    val maybeBumpedMinor = minor.map(m => copy(minor = Some(m + 1)))
    lazy val bumpedMajor = copy(major = major + 1)

    maybeBumpedBugfix.orElse(maybeBumpedMinor).getOrElse(bumpedMajor)
  }

  def bumpMajor = copy(major = major + 1, minor = minor.map(_ => 0), bugfix = bugfix.map(_ => 0))
  def bumpMinor = copy(minor = minor.map(_ + 1), bugfix = bugfix.map(_ => 0))
  def bumpBugfix = copy(bugfix = bugfix.map(_ + 1))

  def bump(bumpType: SemanticVersion.Bump): SemanticVersion = bumpType.bump(this)

  def withoutQualifier = copy(qualifier = None)
  def asSnapshot = copy(qualifier = Some("-SNAPSHOT"))

  def string = "" + major + get(minor) + get(bugfix) + prerelease.getOrElse("") + qualifier.getOrElse("")

  private def get(part: Option[Int]) = part.map("." + _).getOrElse("")
}
pedrorijo91 commented 8 years ago

+1