mirror of https://github.com/sbt/sbt.git
Merge Version and ComparableVersion
This commit is contained in:
parent
a70ed0c292
commit
bda358ba9b
|
|
@ -1,205 +0,0 @@
|
|||
package coursier.core
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import coursier.core.compatibility._
|
||||
|
||||
/** Same kind of ordering as aether-util/src/main/java/org/eclipse/aether/util/version/GenericVersion.java */
|
||||
object ComparableVersion {
|
||||
|
||||
sealed trait Item extends Ordered[Item] {
|
||||
def compare(other: Item): Int =
|
||||
(this, other) match {
|
||||
case (Number(a), Number(b)) => a.compare(b)
|
||||
case (BigNumber(a), BigNumber(b)) => a.compare(b)
|
||||
case (Number(a), BigNumber(b)) => -b.compare(a)
|
||||
case (BigNumber(a), Number(b)) => a.compare(b)
|
||||
case (Qualifier(_, a), Qualifier(_, b)) => a.compare(b)
|
||||
case (Literal(a), Literal(b)) => a.compareToIgnoreCase(b)
|
||||
|
||||
case _ =>
|
||||
val rel0 = compareToEmpty
|
||||
val rel1 = other.compareToEmpty
|
||||
|
||||
if (rel0 == rel1) order.compare(other.order)
|
||||
else rel0.compare(rel1)
|
||||
}
|
||||
|
||||
def order: Int
|
||||
def isEmpty: Boolean = compareToEmpty == 0
|
||||
def compareToEmpty: Int = 1
|
||||
}
|
||||
|
||||
sealed trait Numeric extends Item
|
||||
case class Number(value: Int) extends Numeric {
|
||||
val order = 0
|
||||
override def compareToEmpty = value.compare(0)
|
||||
}
|
||||
case class BigNumber(value: BigInt) extends Numeric {
|
||||
val order = 0
|
||||
override def compareToEmpty = value.compare(0)
|
||||
}
|
||||
case class Qualifier(value: String, level: Int) extends Item {
|
||||
val order = -2
|
||||
override def compareToEmpty = level.compare(0)
|
||||
}
|
||||
case class Literal(value: String) extends Item {
|
||||
val order = -1
|
||||
override def compareToEmpty = if (value.isEmpty) 0 else 1
|
||||
}
|
||||
|
||||
case object Min extends Item {
|
||||
val order = -8
|
||||
override def compareToEmpty = -1
|
||||
}
|
||||
case object Max extends Item {
|
||||
val order = 8
|
||||
}
|
||||
|
||||
val empty = Number(0)
|
||||
|
||||
val qualifiers = Seq[Qualifier](
|
||||
Qualifier("alpha", -5),
|
||||
Qualifier("beta", -4),
|
||||
Qualifier("milestone", -3),
|
||||
Qualifier("cr", -2),
|
||||
Qualifier("rc", -2),
|
||||
Qualifier("snapshot", -1),
|
||||
Qualifier("ga", 0),
|
||||
Qualifier("final", 0),
|
||||
Qualifier("sp", 1)
|
||||
)
|
||||
|
||||
val qualifiersMap = qualifiers.map(q => q.value -> q).toMap
|
||||
|
||||
object Tokenizer {
|
||||
sealed trait Separator
|
||||
case object Dot extends Separator
|
||||
case object Hyphen extends Separator
|
||||
case object Underscore extends Separator
|
||||
case object None extends Separator
|
||||
|
||||
def apply(s: String): (Item, Stream[(Separator, Item)]) = {
|
||||
def parseItem(s: Stream[Char]): (Item, Stream[Char]) = {
|
||||
if (s.isEmpty || !s.head.letterOrDigit) (empty, s)
|
||||
else if (s.head.isDigit) {
|
||||
def digits(b: StringBuilder, s: Stream[Char]): (String, Stream[Char]) =
|
||||
if (s.isEmpty || !s.head.isDigit) (b.result(), s)
|
||||
else digits(b + s.head, s.tail)
|
||||
|
||||
val (digits0, rem) = digits(new StringBuilder, s)
|
||||
val item =
|
||||
if (digits0.length >= 10) BigNumber(BigInt(digits0))
|
||||
else Number(digits0.toInt)
|
||||
|
||||
(item, rem)
|
||||
} else {
|
||||
assert(s.head.letter)
|
||||
|
||||
def letters(b: StringBuilder, s: Stream[Char]): (String, Stream[Char]) =
|
||||
if (s.isEmpty || !s.head.letter) (b.result().toLowerCase, s)
|
||||
else letters(b + s.head, s.tail)
|
||||
|
||||
val (letters0, rem) = letters(new StringBuilder, s)
|
||||
val item =
|
||||
qualifiersMap.getOrElse(letters0, Literal(letters0))
|
||||
|
||||
(item, rem)
|
||||
}
|
||||
}
|
||||
|
||||
def parseSeparator(s: Stream[Char]): (Separator, Stream[Char]) = {
|
||||
assert(s.nonEmpty)
|
||||
|
||||
s.head match {
|
||||
case '.' => (Dot, s.tail)
|
||||
case '-' => (Hyphen, s.tail)
|
||||
case '_' => (Underscore, s.tail)
|
||||
case _ => (None, s)
|
||||
}
|
||||
}
|
||||
|
||||
def helper(s: Stream[Char]): Stream[(Separator, Item)] = {
|
||||
if (s.isEmpty) Stream()
|
||||
else {
|
||||
val (sep, rem0) = parseSeparator(s)
|
||||
val (item, rem) = parseItem(rem0)
|
||||
|
||||
(sep, item) #:: helper(rem)
|
||||
}
|
||||
}
|
||||
|
||||
val (first, rem) = parseItem(s.toStream)
|
||||
(first, helper(rem))
|
||||
}
|
||||
}
|
||||
|
||||
def parse(s: String): ComparableVersion = {
|
||||
val (first, tokens) = Tokenizer(s)
|
||||
|
||||
def isNumeric(item: Item) = item match { case _: Numeric => true; case _ => false }
|
||||
|
||||
def postProcess(prevIsNumeric: Option[Boolean], item: Item, tokens0: Stream[(Tokenizer.Separator, Item)]): Stream[Item] = {
|
||||
val tokens = {
|
||||
var _tokens = tokens0
|
||||
|
||||
if (isNumeric(item)) {
|
||||
val nextNonDotZero = _tokens.dropWhile{case (Tokenizer.Dot, n: Numeric) => n.isEmpty; case _ => false }
|
||||
if (nextNonDotZero.forall(t => t._1 == Tokenizer.Hyphen || ((t._1 == Tokenizer.Dot || t._1 == Tokenizer.None) && !isNumeric(t._2)))) { // Dot && isNumeric(t._2)
|
||||
_tokens = nextNonDotZero
|
||||
}
|
||||
}
|
||||
|
||||
_tokens
|
||||
}
|
||||
|
||||
def ifFollowedByNumberElse(ifFollowedByNumber: Item, default: Item) = {
|
||||
val followedByNumber = tokens.headOption
|
||||
.exists{ case (Tokenizer.None, num: Numeric) if !num.isEmpty => true; case _ => false }
|
||||
|
||||
if (followedByNumber) ifFollowedByNumber
|
||||
else default
|
||||
}
|
||||
|
||||
def next =
|
||||
if (tokens.isEmpty) Stream()
|
||||
else postProcess(Some(isNumeric(item)), tokens.head._2, tokens.tail)
|
||||
|
||||
item match {
|
||||
case Literal("min") => Min #:: next
|
||||
case Literal("max") => Max #:: next
|
||||
case Literal("a") =>
|
||||
ifFollowedByNumberElse(qualifiersMap("alpha"), item) #:: next
|
||||
case Literal("b") =>
|
||||
ifFollowedByNumberElse(qualifiersMap("beta"), item) #:: next
|
||||
case Literal("m") =>
|
||||
ifFollowedByNumberElse(qualifiersMap("milestone"), item) #:: next
|
||||
case _ =>
|
||||
item #:: next
|
||||
}
|
||||
}
|
||||
|
||||
ComparableVersion(postProcess(None, first, tokens).toList)
|
||||
}
|
||||
|
||||
@tailrec
|
||||
def listCompare(first: List[Item], second: List[Item]): Int = {
|
||||
if (first.isEmpty && second.isEmpty) 0
|
||||
else if (first.isEmpty) {
|
||||
assert(second.nonEmpty)
|
||||
-second.dropWhile(_.isEmpty).headOption.fold(0)(_.compareToEmpty)
|
||||
} else if (second.isEmpty) {
|
||||
assert(first.nonEmpty)
|
||||
first.dropWhile(_.isEmpty).headOption.fold(0)(_.compareToEmpty)
|
||||
} else {
|
||||
val rel = first.head.compare(second.head)
|
||||
if (rel == 0) listCompare(first.tail, second.tail)
|
||||
else rel
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case class ComparableVersion(items: List[ComparableVersion.Item]) extends Ordered[ComparableVersion] {
|
||||
def compare(other: ComparableVersion) = ComparableVersion.listCompare(items, other.items)
|
||||
def isEmpty = items.forall(_.isEmpty)
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ object Parse {
|
|||
strTo = s0.drop(commaIdx + 1)
|
||||
from <- if (strFrom.isEmpty) Some(None) else version(strFrom).map(Some(_))
|
||||
to <- if (strTo.isEmpty) Some(None) else version(strTo).map(Some(_))
|
||||
} yield VersionInterval(from.filterNot(_.cmp.isEmpty), to.filterNot(_.cmp.isEmpty), fromIncluded, toIncluded)
|
||||
} yield VersionInterval(from.filterNot(_.isEmpty), to.filterNot(_.isEmpty), fromIncluded, toIncluded)
|
||||
}
|
||||
|
||||
def versionConstraint(s: String): Option[VersionConstraint] = {
|
||||
|
|
|
|||
|
|
@ -1,108 +1,210 @@
|
|||
package coursier.core
|
||||
|
||||
case class Versions(latest: String,
|
||||
release: String,
|
||||
available: List[String],
|
||||
lastUpdated: Option[Version.DateTime])
|
||||
import scala.annotation.tailrec
|
||||
import coursier.core.compatibility._
|
||||
|
||||
/** Used internally by Resolver */
|
||||
/**
|
||||
* Used internally by Resolver.
|
||||
*
|
||||
* Same kind of ordering as aether-util/src/main/java/org/eclipse/aether/util/version/GenericVersion.java
|
||||
*/
|
||||
case class Version(repr: String) extends Ordered[Version] {
|
||||
|
||||
lazy val cmp = ComparableVersion.parse(repr)
|
||||
|
||||
def compare(other: Version): Int = {
|
||||
cmp.compare(other.cmp)
|
||||
}
|
||||
lazy val items = Version.items(repr)
|
||||
def compare(other: Version) = Version.listCompare(items, other.items)
|
||||
def isEmpty = items.forall(_.isEmpty)
|
||||
}
|
||||
|
||||
object Version {
|
||||
|
||||
case class DateTime(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int)
|
||||
sealed trait Item extends Ordered[Item] {
|
||||
def compare(other: Item): Int =
|
||||
(this, other) match {
|
||||
case (Number(a), Number(b)) => a.compare(b)
|
||||
case (BigNumber(a), BigNumber(b)) => a.compare(b)
|
||||
case (Number(a), BigNumber(b)) => -b.compare(a)
|
||||
case (BigNumber(a), Number(b)) => a.compare(b)
|
||||
case (Qualifier(_, a), Qualifier(_, b)) => a.compare(b)
|
||||
case (Literal(a), Literal(b)) => a.compareToIgnoreCase(b)
|
||||
|
||||
}
|
||||
case _ =>
|
||||
val rel0 = compareToEmpty
|
||||
val rel1 = other.compareToEmpty
|
||||
|
||||
case class VersionInterval(from: Option[Version],
|
||||
to: Option[Version],
|
||||
fromIncluded: Boolean,
|
||||
toIncluded: Boolean) {
|
||||
|
||||
def isValid: Boolean = {
|
||||
val fromToOrder =
|
||||
for {
|
||||
f <- from
|
||||
t <- to
|
||||
cmd = f.compare(t)
|
||||
} yield cmd < 0 || (cmd == 0 && fromIncluded && toIncluded)
|
||||
|
||||
fromToOrder.forall(x => x) && (from.nonEmpty || !fromIncluded) && (to.nonEmpty || !toIncluded)
|
||||
}
|
||||
|
||||
def merge(other: VersionInterval): Option[VersionInterval] = {
|
||||
val (newFrom, newFromIncluded) =
|
||||
(from, other.from) match {
|
||||
case (Some(a), Some(b)) =>
|
||||
val cmp = a.compare(b)
|
||||
if (cmp < 0) (Some(b), other.fromIncluded)
|
||||
else if (cmp > 0) (Some(a), fromIncluded)
|
||||
else (Some(a), fromIncluded && other.fromIncluded)
|
||||
|
||||
case (Some(a), None) => (Some(a), fromIncluded)
|
||||
case (None, Some(b)) => (Some(b), other.fromIncluded)
|
||||
case (None, None) => (None, false)
|
||||
if (rel0 == rel1) order.compare(other.order)
|
||||
else rel0.compare(rel1)
|
||||
}
|
||||
|
||||
val (newTo, newToIncluded) =
|
||||
(to, other.to) match {
|
||||
case (Some(a), Some(b)) =>
|
||||
val cmp = a.compare(b)
|
||||
if (cmp < 0) (Some(a), toIncluded)
|
||||
else if (cmp > 0) (Some(b), other.toIncluded)
|
||||
else (Some(a), toIncluded && other.toIncluded)
|
||||
|
||||
case (Some(a), None) => (Some(a), toIncluded)
|
||||
case (None, Some(b)) => (Some(b), other.toIncluded)
|
||||
case (None, None) => (None, false)
|
||||
}
|
||||
|
||||
Some(VersionInterval(newFrom, newTo, newFromIncluded, newToIncluded))
|
||||
.filter(_.isValid)
|
||||
def order: Int
|
||||
def isEmpty: Boolean = compareToEmpty == 0
|
||||
def compareToEmpty: Int = 1
|
||||
}
|
||||
|
||||
def constraint: VersionConstraint =
|
||||
this match {
|
||||
case VersionInterval.zero => VersionConstraint.None
|
||||
case VersionInterval(Some(version), None, true, false) => VersionConstraint.Preferred(version)
|
||||
case itv => VersionConstraint.Interval(itv)
|
||||
sealed trait Numeric extends Item
|
||||
case class Number(value: Int) extends Numeric {
|
||||
val order = 0
|
||||
override def compareToEmpty = value.compare(0)
|
||||
}
|
||||
case class BigNumber(value: BigInt) extends Numeric {
|
||||
val order = 0
|
||||
override def compareToEmpty = value.compare(0)
|
||||
}
|
||||
case class Qualifier(value: String, level: Int) extends Item {
|
||||
val order = -2
|
||||
override def compareToEmpty = level.compare(0)
|
||||
}
|
||||
case class Literal(value: String) extends Item {
|
||||
val order = -1
|
||||
override def compareToEmpty = if (value.isEmpty) 0 else 1
|
||||
}
|
||||
|
||||
case object Min extends Item {
|
||||
val order = -8
|
||||
override def compareToEmpty = -1
|
||||
}
|
||||
case object Max extends Item {
|
||||
val order = 8
|
||||
}
|
||||
|
||||
val empty = Number(0)
|
||||
|
||||
val qualifiers = Seq[Qualifier](
|
||||
Qualifier("alpha", -5),
|
||||
Qualifier("beta", -4),
|
||||
Qualifier("milestone", -3),
|
||||
Qualifier("cr", -2),
|
||||
Qualifier("rc", -2),
|
||||
Qualifier("snapshot", -1),
|
||||
Qualifier("ga", 0),
|
||||
Qualifier("final", 0),
|
||||
Qualifier("sp", 1)
|
||||
)
|
||||
|
||||
val qualifiersMap = qualifiers.map(q => q.value -> q).toMap
|
||||
|
||||
object Tokenizer {
|
||||
sealed trait Separator
|
||||
case object Dot extends Separator
|
||||
case object Hyphen extends Separator
|
||||
case object Underscore extends Separator
|
||||
case object None extends Separator
|
||||
|
||||
def apply(s: String): (Item, Stream[(Separator, Item)]) = {
|
||||
def parseItem(s: Stream[Char]): (Item, Stream[Char]) = {
|
||||
if (s.isEmpty || !s.head.letterOrDigit) (empty, s)
|
||||
else if (s.head.isDigit) {
|
||||
def digits(b: StringBuilder, s: Stream[Char]): (String, Stream[Char]) =
|
||||
if (s.isEmpty || !s.head.isDigit) (b.result(), s)
|
||||
else digits(b + s.head, s.tail)
|
||||
|
||||
val (digits0, rem) = digits(new StringBuilder, s)
|
||||
val item =
|
||||
if (digits0.length >= 10) BigNumber(BigInt(digits0))
|
||||
else Number(digits0.toInt)
|
||||
|
||||
(item, rem)
|
||||
} else {
|
||||
assert(s.head.letter)
|
||||
|
||||
def letters(b: StringBuilder, s: Stream[Char]): (String, Stream[Char]) =
|
||||
if (s.isEmpty || !s.head.letter) (b.result().toLowerCase, s)
|
||||
else letters(b + s.head, s.tail)
|
||||
|
||||
val (letters0, rem) = letters(new StringBuilder, s)
|
||||
val item =
|
||||
qualifiersMap.getOrElse(letters0, Literal(letters0))
|
||||
|
||||
(item, rem)
|
||||
}
|
||||
}
|
||||
|
||||
def parseSeparator(s: Stream[Char]): (Separator, Stream[Char]) = {
|
||||
assert(s.nonEmpty)
|
||||
|
||||
s.head match {
|
||||
case '.' => (Dot, s.tail)
|
||||
case '-' => (Hyphen, s.tail)
|
||||
case '_' => (Underscore, s.tail)
|
||||
case _ => (None, s)
|
||||
}
|
||||
}
|
||||
|
||||
def helper(s: Stream[Char]): Stream[(Separator, Item)] = {
|
||||
if (s.isEmpty) Stream()
|
||||
else {
|
||||
val (sep, rem0) = parseSeparator(s)
|
||||
val (item, rem) = parseItem(rem0)
|
||||
|
||||
(sep, item) #:: helper(rem)
|
||||
}
|
||||
}
|
||||
|
||||
val (first, rem) = parseItem(s.toStream)
|
||||
(first, helper(rem))
|
||||
}
|
||||
}
|
||||
|
||||
def items(repr: String): List[Item] = {
|
||||
val (first, tokens) = Tokenizer(repr)
|
||||
|
||||
def isNumeric(item: Item) = item match { case _: Numeric => true; case _ => false }
|
||||
|
||||
def postProcess(prevIsNumeric: Option[Boolean], item: Item, tokens0: Stream[(Tokenizer.Separator, Item)]): Stream[Item] = {
|
||||
val tokens = {
|
||||
var _tokens = tokens0
|
||||
|
||||
if (isNumeric(item)) {
|
||||
val nextNonDotZero = _tokens.dropWhile{case (Tokenizer.Dot, n: Numeric) => n.isEmpty; case _ => false }
|
||||
if (nextNonDotZero.forall(t => t._1 == Tokenizer.Hyphen || ((t._1 == Tokenizer.Dot || t._1 == Tokenizer.None) && !isNumeric(t._2)))) { // Dot && isNumeric(t._2)
|
||||
_tokens = nextNonDotZero
|
||||
}
|
||||
}
|
||||
|
||||
_tokens
|
||||
}
|
||||
|
||||
def ifFollowedByNumberElse(ifFollowedByNumber: Item, default: Item) = {
|
||||
val followedByNumber = tokens.headOption
|
||||
.exists{ case (Tokenizer.None, num: Numeric) if !num.isEmpty => true; case _ => false }
|
||||
|
||||
if (followedByNumber) ifFollowedByNumber
|
||||
else default
|
||||
}
|
||||
|
||||
def next =
|
||||
if (tokens.isEmpty) Stream()
|
||||
else postProcess(Some(isNumeric(item)), tokens.head._2, tokens.tail)
|
||||
|
||||
item match {
|
||||
case Literal("min") => Min #:: next
|
||||
case Literal("max") => Max #:: next
|
||||
case Literal("a") =>
|
||||
ifFollowedByNumberElse(qualifiersMap("alpha"), item) #:: next
|
||||
case Literal("b") =>
|
||||
ifFollowedByNumberElse(qualifiersMap("beta"), item) #:: next
|
||||
case Literal("m") =>
|
||||
ifFollowedByNumberElse(qualifiersMap("milestone"), item) #:: next
|
||||
case _ =>
|
||||
item #:: next
|
||||
}
|
||||
}
|
||||
|
||||
def repr: String = Seq(
|
||||
if (fromIncluded) "[" else "(",
|
||||
from.map(_.repr).mkString,
|
||||
",",
|
||||
to.map(_.repr).mkString,
|
||||
if (toIncluded) "]" else ")"
|
||||
).mkString
|
||||
}
|
||||
postProcess(None, first, tokens).toList
|
||||
}
|
||||
|
||||
object VersionInterval {
|
||||
val zero = VersionInterval(None, None, fromIncluded = false, toIncluded = false)
|
||||
}
|
||||
@tailrec
|
||||
def listCompare(first: List[Item], second: List[Item]): Int = {
|
||||
if (first.isEmpty && second.isEmpty) 0
|
||||
else if (first.isEmpty) {
|
||||
assert(second.nonEmpty)
|
||||
-second.dropWhile(_.isEmpty).headOption.fold(0)(_.compareToEmpty)
|
||||
} else if (second.isEmpty) {
|
||||
assert(first.nonEmpty)
|
||||
first.dropWhile(_.isEmpty).headOption.fold(0)(_.compareToEmpty)
|
||||
} else {
|
||||
val rel = first.head.compare(second.head)
|
||||
if (rel == 0) listCompare(first.tail, second.tail)
|
||||
else rel
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait VersionConstraint {
|
||||
def interval: VersionInterval
|
||||
def repr: String
|
||||
}
|
||||
object VersionConstraint {
|
||||
/** Currently treated as minimum... */
|
||||
case class Preferred(version: Version) extends VersionConstraint {
|
||||
def interval: VersionInterval = VersionInterval(Some(version), Option.empty, fromIncluded = true, toIncluded = false)
|
||||
def repr: String = version.repr
|
||||
}
|
||||
case class Interval(interval: VersionInterval) extends VersionConstraint {
|
||||
def repr: String = interval.repr
|
||||
}
|
||||
case object None extends VersionConstraint {
|
||||
val interval = VersionInterval.zero
|
||||
def repr: String = "" // Once parsed, "(,)" becomes "" because of this
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package coursier.core
|
||||
|
||||
case class Versions(latest: String,
|
||||
release: String,
|
||||
available: List[String],
|
||||
lastUpdated: Option[Versions.DateTime])
|
||||
|
||||
object Versions {
|
||||
|
||||
case class DateTime(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int)
|
||||
|
||||
}
|
||||
|
||||
case class VersionInterval(from: Option[Version],
|
||||
to: Option[Version],
|
||||
fromIncluded: Boolean,
|
||||
toIncluded: Boolean) {
|
||||
|
||||
def isValid: Boolean = {
|
||||
val fromToOrder =
|
||||
for {
|
||||
f <- from
|
||||
t <- to
|
||||
cmd = f.compare(t)
|
||||
} yield cmd < 0 || (cmd == 0 && fromIncluded && toIncluded)
|
||||
|
||||
fromToOrder.forall(x => x) && (from.nonEmpty || !fromIncluded) && (to.nonEmpty || !toIncluded)
|
||||
}
|
||||
|
||||
def merge(other: VersionInterval): Option[VersionInterval] = {
|
||||
val (newFrom, newFromIncluded) =
|
||||
(from, other.from) match {
|
||||
case (Some(a), Some(b)) =>
|
||||
val cmp = a.compare(b)
|
||||
if (cmp < 0) (Some(b), other.fromIncluded)
|
||||
else if (cmp > 0) (Some(a), fromIncluded)
|
||||
else (Some(a), fromIncluded && other.fromIncluded)
|
||||
|
||||
case (Some(a), None) => (Some(a), fromIncluded)
|
||||
case (None, Some(b)) => (Some(b), other.fromIncluded)
|
||||
case (None, None) => (None, false)
|
||||
}
|
||||
|
||||
val (newTo, newToIncluded) =
|
||||
(to, other.to) match {
|
||||
case (Some(a), Some(b)) =>
|
||||
val cmp = a.compare(b)
|
||||
if (cmp < 0) (Some(a), toIncluded)
|
||||
else if (cmp > 0) (Some(b), other.toIncluded)
|
||||
else (Some(a), toIncluded && other.toIncluded)
|
||||
|
||||
case (Some(a), None) => (Some(a), toIncluded)
|
||||
case (None, Some(b)) => (Some(b), other.toIncluded)
|
||||
case (None, None) => (None, false)
|
||||
}
|
||||
|
||||
Some(VersionInterval(newFrom, newTo, newFromIncluded, newToIncluded))
|
||||
.filter(_.isValid)
|
||||
}
|
||||
|
||||
def constraint: VersionConstraint =
|
||||
this match {
|
||||
case VersionInterval.zero => VersionConstraint.None
|
||||
case VersionInterval(Some(version), None, true, false) => VersionConstraint.Preferred(version)
|
||||
case itv => VersionConstraint.Interval(itv)
|
||||
}
|
||||
|
||||
def repr: String = Seq(
|
||||
if (fromIncluded) "[" else "(",
|
||||
from.map(_.repr).mkString,
|
||||
",",
|
||||
to.map(_.repr).mkString,
|
||||
if (toIncluded) "]" else ")"
|
||||
).mkString
|
||||
}
|
||||
|
||||
object VersionInterval {
|
||||
val zero = VersionInterval(None, None, fromIncluded = false, toIncluded = false)
|
||||
}
|
||||
|
||||
sealed trait VersionConstraint {
|
||||
def interval: VersionInterval
|
||||
def repr: String
|
||||
}
|
||||
object VersionConstraint {
|
||||
/** Currently treated as minimum... */
|
||||
case class Preferred(version: Version) extends VersionConstraint {
|
||||
def interval: VersionInterval = VersionInterval(Some(version), Option.empty, fromIncluded = true, toIncluded = false)
|
||||
def repr: String = version.repr
|
||||
}
|
||||
case class Interval(interval: VersionInterval) extends VersionConstraint {
|
||||
def repr: String = interval.repr
|
||||
}
|
||||
case object None extends VersionConstraint {
|
||||
val interval = VersionInterval.zero
|
||||
def repr: String = "" // Once parsed, "(,)" becomes "" because of this
|
||||
}
|
||||
}
|
||||
|
|
@ -236,7 +236,7 @@ object Xml {
|
|||
lastUpdatedOpt = text(xmlVersioning, "lastUpdated", "Last update date and time")
|
||||
.toOption
|
||||
.filter(s => s.length == 14 && s.forall(_.isDigit))
|
||||
.map(s => Version.DateTime(
|
||||
.map(s => Versions.DateTime(
|
||||
s.substring(0, 4).toInt,
|
||||
s.substring(4, 6).toInt,
|
||||
s.substring(6, 8).toInt,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
package coursier
|
||||
package test
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
import utest._
|
||||
|
||||
object ComparableVersionTests extends TestSuite {
|
||||
import core.ComparableVersion.parse
|
||||
object VersionTests extends TestSuite {
|
||||
import core.Version
|
||||
|
||||
def compare(first: String, second: String) = parse(first).compare(parse(second))
|
||||
def compare(first: String, second: String) = Version(first).compare(Version(second))
|
||||
|
||||
def increasing(versions: String*): Boolean =
|
||||
versions.iterator.sliding(2).withPartial(false).forall{case Seq(a, b) => compare(a, b) < 0 }
|
||||
|
|
@ -16,13 +14,13 @@ object ComparableVersionTests extends TestSuite {
|
|||
val tests = TestSuite {
|
||||
'stackOverflow{
|
||||
val s = "." * 100000
|
||||
val v = parse(s)
|
||||
val v = Version(s)
|
||||
assert(v.isEmpty)
|
||||
}
|
||||
|
||||
'empty{
|
||||
val v0 = parse("0")
|
||||
val v = parse("")
|
||||
val v0 = Version("0")
|
||||
val v = Version("")
|
||||
|
||||
assert(v0.isEmpty)
|
||||
assert(v.isEmpty)
|
||||
|
|
@ -328,6 +326,7 @@ object ComparableVersionTests extends TestSuite {
|
|||
|
||||
// 'CaseInsensitiveOrderingOfQualifiersIsLocaleIndependent
|
||||
// {
|
||||
// import java.util.Locale
|
||||
// val orig = Locale.getDefault
|
||||
// try {
|
||||
// for ( locale <- Seq(Locale.ENGLISH, new Locale( "tr" )) ) {
|
||||
Loading…
Reference in New Issue