diff options
Diffstat (limited to 'scalalib/src/dependency/versions/Version.scala')
-rw-r--r-- | scalalib/src/dependency/versions/Version.scala | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/scalalib/src/dependency/versions/Version.scala b/scalalib/src/dependency/versions/Version.scala new file mode 100644 index 00000000..a2719023 --- /dev/null +++ b/scalalib/src/dependency/versions/Version.scala @@ -0,0 +1,227 @@ +/* + * This file contains code originally published under the following license: + * + * Copyright (c) 2012, Roman Timushev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package mill.scalalib.dependency.versions + +import scala.util.matching.Regex +import scala.util.matching.Regex.Groups + +private[dependency] sealed trait Version { + def major: Long + + def minor: Long + + def patch: Long +} + +private[dependency] case class ValidVersion(text: String, + releasePart: List[Long], + preReleasePart: List[String], + buildPart: List[String]) + extends Version { + def major: Long = releasePart.headOption getOrElse 0 + + def minor: Long = releasePart.drop(1).headOption getOrElse 1 + + def patch: Long = releasePart.drop(2).headOption getOrElse 1 + + override def toString: String = text +} + +private[dependency] case class InvalidVersion(text: String) extends Version { + def major: Long = -1 + + def minor: Long = -1 + + def patch: Long = -1 +} + +private[dependency] object ReleaseVersion { + private val releaseKeyword: Regex = "(?i)final|release".r + + def unapply(v: Version): Option[List[Long]] = v match { + case ValidVersion(_, releasePart, Nil, Nil) => Some(releasePart) + case ValidVersion(_, releasePart, releaseKeyword() :: Nil, Nil) => + Some(releasePart) + case _ => None + } +} + +private[dependency] object PreReleaseVersion { + def unapply(v: Version): Option[(List[Long], List[String])] = v match { + case ValidVersion(_, releasePart, preReleasePart, Nil) + if preReleasePart.nonEmpty => + Some(releasePart, preReleasePart) + case _ => None + } +} + +private[dependency] object PreReleaseBuildVersion { + def unapply(v: Version): Option[(List[Long], List[String], List[String])] = + v match { + case ValidVersion(_, releasePart, preReleasePart, buildPart) + if preReleasePart.nonEmpty && buildPart.nonEmpty => + Some(releasePart, preReleasePart, buildPart) + case _ => None + } +} + +private[dependency] object SnapshotVersion { + def unapply(v: Version): Option[(List[Long], List[String], List[String])] = + v match { + case ValidVersion(_, releasePart, preReleasePart, buildPart) + if preReleasePart.lastOption.contains("SNAPSHOT") => + Some(releasePart, preReleasePart, buildPart) + case _ => None + } +} + +private[dependency] object BuildVersion { + def unapply(v: Version): Option[(List[Long], List[String])] = v match { + case ValidVersion(_, releasePart, Nil, buildPart) if buildPart.nonEmpty => + Some(releasePart, buildPart) + case _ => None + } +} + +private[dependency] object Version { + def apply(text: String): Version = synchronized { + VersionParser + .parse(text) + .fold( + (_, _, _) => InvalidVersion(text), + { case ((a, b, c), _) => ValidVersion(text, a.toList, b.toList, c.toList)} + ) + } + + implicit def versionOrdering: Ordering[Version] = VersionOrdering +} + +private[dependency] object VersionOrdering extends Ordering[Version] { + + private val subParts = "(\\d+)?(\\D+)?".r + + private def parsePart(s: String): Seq[Either[Int, String]] = + try { + subParts + .findAllIn(s) + .matchData + .flatMap { + case Groups(num, str) => + Seq(Option(num).map(_.toInt).map(Left.apply), + Option(str).map(Right.apply)) + } + .flatten + .toList + } catch { + case _: NumberFormatException => List(Right(s)) + } + + private def toOpt(x: Int): Option[Int] = if (x == 0) None else Some(x) + + private def comparePart(a: String, b: String) = { + if (a == b) None + else + (parsePart(a) zip parsePart(b)) map { + case (Left(x), Left(y)) => x compareTo y + case (Left(_), Right(_)) => -1 + case (Right(_), Left(_)) => 1 + case (Right(x), Right(y)) => x compareTo y + } find (0 != _) orElse Some(a compareTo b) + } + + private def compareNumericParts(a: List[Long], b: List[Long]): Option[Int] = + (a, b) match { + case (ah :: at, bh :: bt) => + toOpt(ah compareTo bh) orElse compareNumericParts(at, bt) + case (ah :: at, Nil) => + toOpt(ah compareTo 0L) orElse compareNumericParts(at, Nil) + case (Nil, bh :: bt) => + toOpt(0L compareTo bh) orElse compareNumericParts(Nil, bt) + case (Nil, Nil) => + None + } + + private def compareParts(a: List[String], b: List[String]): Option[Int] = + (a, b) match { + case (ah :: at, bh :: bt) => + comparePart(ah, bh) orElse compareParts(at, bt) + case (_ :: _, Nil) => + Some(1) + case (Nil, _ :: _) => + Some(-1) + case (Nil, Nil) => + None + } + + def compare(x: Version, y: Version): Int = (x, y) match { + case (InvalidVersion(a), InvalidVersion(b)) => + a compareTo b + case (InvalidVersion(_), _) => + -1 + case (_, InvalidVersion(_)) => + 1 + case (ReleaseVersion(r1), ReleaseVersion(r2)) => + compareNumericParts(r1, r2) getOrElse 0 + case (ReleaseVersion(r1), PreReleaseVersion(r2, p2)) => + compareNumericParts(r1, r2) getOrElse 1 + case (ReleaseVersion(r1), PreReleaseBuildVersion(r2, p2, b2)) => + compareNumericParts(r1, r2) getOrElse 1 + case (ReleaseVersion(r1), BuildVersion(r2, b2)) => + compareNumericParts(r1, r2) getOrElse -1 + case (PreReleaseVersion(r1, p1), ReleaseVersion(r2)) => + compareNumericParts(r1, r2) getOrElse -1 + case (PreReleaseVersion(r1, p1), PreReleaseVersion(r2, p2)) => + compareNumericParts(r1, r2) orElse compareParts(p1, p2) getOrElse 0 + case (PreReleaseVersion(r1, p1), PreReleaseBuildVersion(r2, p2, b2)) => + compareNumericParts(r1, r2) orElse compareParts(p1, p2) getOrElse -1 + case (PreReleaseVersion(r1, p1), BuildVersion(r2, b2)) => + compareNumericParts(r1, r2) getOrElse -1 + case (PreReleaseBuildVersion(r1, p1, b1), ReleaseVersion(r2)) => + compareNumericParts(r1, r2) getOrElse -1 + case (PreReleaseBuildVersion(r1, p1, b1), PreReleaseVersion(r2, p2)) => + compareNumericParts(r1, r2) orElse compareParts(p1, p2) getOrElse 1 + case (PreReleaseBuildVersion(r1, p1, b1), + PreReleaseBuildVersion(r2, p2, b2)) => + compareNumericParts(r1, r2) orElse + compareParts(p1, p2) orElse + compareParts(b1, b2) getOrElse + 0 + case (PreReleaseBuildVersion(r1, p1, b1), BuildVersion(r2, b2)) => + compareNumericParts(r1, r2) getOrElse -1 + case (BuildVersion(r1, b1), ReleaseVersion(r2)) => + compareNumericParts(r1, r2) getOrElse 1 + case (BuildVersion(r1, b1), PreReleaseVersion(r2, p2)) => + compareNumericParts(r1, r2) getOrElse 1 + case (BuildVersion(r1, b1), PreReleaseBuildVersion(r2, p2, b2)) => + compareNumericParts(r1, r2) getOrElse 1 + case (BuildVersion(r1, b1), BuildVersion(r2, b2)) => + compareNumericParts(r1, r2) orElse compareParts(b1, b2) getOrElse 0 + } + +} |