diff --git a/core/js/src/main/scala/coursier/core/compatibility/package.scala b/core/js/src/main/scala/coursier/core/compatibility/package.scala index 18c848656..f5ae4978d 100644 --- a/core/js/src/main/scala/coursier/core/compatibility/package.scala +++ b/core/js/src/main/scala/coursier/core/compatibility/package.scala @@ -113,4 +113,7 @@ package object compatibility { links.result() } + + def regexLookbehind: String = ":" + } diff --git a/core/jvm/src/main/scala/coursier/core/compatibility/package.scala b/core/jvm/src/main/scala/coursier/core/compatibility/package.scala index 87146078f..c105e6dae 100644 --- a/core/jvm/src/main/scala/coursier/core/compatibility/package.scala +++ b/core/jvm/src/main/scala/coursier/core/compatibility/package.scala @@ -79,4 +79,6 @@ package object compatibility { .toVector .map(_.attr("href")) + def regexLookbehind: String = "<=" + } diff --git a/core/shared/src/main/scala/coursier/core/Parse.scala b/core/shared/src/main/scala/coursier/core/Parse.scala index 41ea81054..d543a3a2a 100644 --- a/core/shared/src/main/scala/coursier/core/Parse.scala +++ b/core/shared/src/main/scala/coursier/core/Parse.scala @@ -1,6 +1,7 @@ package coursier.core import java.util.regex.Pattern.quote + import coursier.core.compatibility._ object Parse { @@ -63,13 +64,28 @@ object Parse { } yield itv } + private val multiVersionIntervalSplit = ("(?" + regexLookbehind + "[" + quote("])") + "]),(?=[" + quote("([") + "])").r + + def multiVersionInterval(s: String): Option[VersionInterval] = { + + // TODO Use a full-fledged (fastparsed-based) parser for this and versionInterval above + + val openCount = s.count(c => c == '[' || c == '(') + val closeCount = s.count(c => c == ']' || c == ')') + + if (openCount == closeCount && openCount >= 1) + versionInterval(multiVersionIntervalSplit.split(s).last) + else + None + } + def versionConstraint(s: String): Option[VersionConstraint] = { def noConstraint = if (s.isEmpty) Some(VersionConstraint.all) else None noConstraint .orElse(ivyLatestSubRevisionInterval(s).map(VersionConstraint.interval)) .orElse(version(s).map(VersionConstraint.preferred)) - .orElse(versionInterval(s).map(VersionConstraint.interval)) + .orElse(versionInterval(s).orElse(multiVersionInterval(s)).map(VersionConstraint.interval)) } val fallbackConfigRegex = { diff --git a/core/shared/src/main/scala/coursier/ivy/IvyRepository.scala b/core/shared/src/main/scala/coursier/ivy/IvyRepository.scala index 41e207000..eea6fadc2 100644 --- a/core/shared/src/main/scala/coursier/ivy/IvyRepository.scala +++ b/core/shared/src/main/scala/coursier/ivy/IvyRepository.scala @@ -135,6 +135,7 @@ final case class IvyRepository( findNoInverval(module, version, fetch) case Some(revisionListingPattern) => Parse.versionInterval(version) + .orElse(Parse.multiVersionInterval(version)) .orElse(Parse.ivyLatestSubRevisionInterval(version)) .filter(_.isValid) match { case None => diff --git a/core/shared/src/main/scala/coursier/maven/MavenRepository.scala b/core/shared/src/main/scala/coursier/maven/MavenRepository.scala index bdc0bea74..cd2ec7a78 100644 --- a/core/shared/src/main/scala/coursier/maven/MavenRepository.scala +++ b/core/shared/src/main/scala/coursier/maven/MavenRepository.scala @@ -421,6 +421,7 @@ final case class MavenRepository( ): EitherT[F, String, (Artifact.Source, Project)] = { Parse.versionInterval(version) + .orElse(Parse.multiVersionInterval(version)) .orElse(Parse.ivyLatestSubRevisionInterval(version)) .filter(_.isValid) match { case None => diff --git a/core/shared/src/main/scala/coursier/maven/Pom.scala b/core/shared/src/main/scala/coursier/maven/Pom.scala index 9c1d5fab8..bacde4f6f 100644 --- a/core/shared/src/main/scala/coursier/maven/Pom.scala +++ b/core/shared/src/main/scala/coursier/maven/Pom.scala @@ -91,7 +91,9 @@ object Pom { ) val jdk = text(node, "jdk", "").toOption.flatMap { s => - Parse.versionInterval(s).map(-\/(_)) + Parse.versionInterval(s) + .orElse(Parse.multiVersionInterval(s)) + .map(-\/(_)) .orElse(Parse.version(s).map(v => \/-(Seq(v)))) } diff --git a/tests/metadata b/tests/metadata index 1c0303590..af15241bf 160000 --- a/tests/metadata +++ b/tests/metadata @@ -1 +1 @@ -Subproject commit 1c030359018e600bc4d33c562a4ab616b6570c63 +Subproject commit af15241bf354b67e825036fbe224b7a0954fa0f3 diff --git a/tests/shared/src/test/resources/resolutions/org.webjars.bower/dgrid/1.0.0 b/tests/shared/src/test/resources/resolutions/org.webjars.bower/dgrid/1.0.0 new file mode 100644 index 000000000..138ccbee6 --- /dev/null +++ b/tests/shared/src/test/resources/resolutions/org.webjars.bower/dgrid/1.0.0 @@ -0,0 +1,3 @@ +org.webjars.bower:dgrid:1.0.0:compile +org.webjars.bower:dojo:1.12.2:compile +org.webjars.bower:dstore:1.1.1:compile \ No newline at end of file diff --git a/tests/shared/src/test/scala/coursier/test/CentralTests.scala b/tests/shared/src/test/scala/coursier/test/CentralTests.scala index befd21918..10fcde8d3 100644 --- a/tests/shared/src/test/scala/coursier/test/CentralTests.scala +++ b/tests/shared/src/test/scala/coursier/test/CentralTests.scala @@ -740,6 +740,13 @@ abstract class CentralTests extends TestSuite { resolutionCheck(mod, ver) } } + + 'multiVersionRanges - { + val mod = Module("org.webjars.bower", "dgrid") + val ver = "1.0.0" + + * - resolutionCheck(mod, ver) + } } } diff --git a/tests/shared/src/test/scala/coursier/test/VersionIntervalTests.scala b/tests/shared/src/test/scala/coursier/test/VersionIntervalTests.scala index d8ada55ad..94893d10e 100644 --- a/tests/shared/src/test/scala/coursier/test/VersionIntervalTests.scala +++ b/tests/shared/src/test/scala/coursier/test/VersionIntervalTests.scala @@ -295,6 +295,28 @@ object VersionIntervalTests extends TestSuite { assert(itv.isEmpty) } } + + 'multiRange - { + * - { + val itv = Parse.multiVersionInterval("[1.0,2.0)") + assert(itv == Some(VersionInterval(Some(Version("1.0")), Some(Version("2.0")), fromIncluded = true, toIncluded = false))) + } + + * - { + val itv = Parse.multiVersionInterval("[1.0,2.0),[3.0,4.0)") + assert(itv == Some(VersionInterval(Some(Version("3.0")), Some(Version("4.0")), fromIncluded = true, toIncluded = false))) + } + + * - { + val itv = Parse.multiVersionInterval("[1.0,2.0),[3.0,4.0),[5.0,6.0)") + assert(itv == Some(VersionInterval(Some(Version("5.0")), Some(Version("6.0")), fromIncluded = true, toIncluded = false))) + } + + * - { + val itv = Parse.multiVersionInterval("(1.0,2.0),[3.0,4.0),(5.0,6.0)") + assert(itv == Some(VersionInterval(Some(Version("5.0")), Some(Version("6.0")), fromIncluded = false, toIncluded = false))) + } + } } 'constraint{