Skip to content

Commit

Permalink
Badge showing notable Scala support/Artifact versions
Browse files Browse the repository at this point in the history
  • Loading branch information
rtyley committed May 3, 2021
1 parent 04ea056 commit e49f52d
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import fastparse._
* It can be either a [[ScalaVersion]] or a [[Scala3Version]]
*/
sealed trait LanguageVersion {
val version: BinaryVersion

/**
* When indexing, all dotty versions are regrouped under the 'dotty' keyword.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ object ScalaTargetType {

implicit val ordering: Ordering[ScalaTargetType] = Ordering.by(All.indexOf(_))

def ofName(name: String): Option[ScalaTargetType] =
All.find(_.getClass.getSimpleName.stripSuffix("$").equalsIgnoreCase(name))
}

sealed trait ScalaTargetType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.scalatest.prop.TableDrivenPropertyChecks
class ScalaTargetTests
extends FunSpec
with Matchers
with OptionValues
with TableDrivenPropertyChecks {
it("should be ordered") {
val js067 = PatchBinary(0, 6, 7)
Expand Down Expand Up @@ -51,4 +52,9 @@ class ScalaTargetTests
ScalaTarget.parse(input) should contain(target)
}
}

it("should parse a string to yield a ScalaTargetType") {
ScalaTargetType.ofName("Js").value shouldBe Js
ScalaTargetType.ofName("Jvm").value shouldBe Jvm
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package ch.epfl.scala.index.server

import ch.epfl.scala.index.model.{Release, SemanticVersion}
import ch.epfl.scala.index.model.release._
import ch.epfl.scala.index.server.ArtifactScalaVersionSupport.ScalaSupport

import scala.collection.immutable.{SortedMap, SortedSet}

case class ArtifactScalaVersionSupport(
scalaTargetsForAllArtifactVersions: Map[SemanticVersion, Seq[ScalaTarget]]
) {

val allTargets: Set[ScalaTarget] = scalaTargetsForAllArtifactVersions.values.flatten.toSet

val latestScalaVersionsOfAvailableBinaryVersions: Set[LanguageVersion] =
allTargets.map(_.languageVersion).groupBy(_.family).view.mapValues(_.max).values.toSet

val latestArtifactVersionByLanguageVersion: Map[LanguageVersion, SemanticVersion] = (for {
(artifactVersion, scalaTargets) <- scalaTargetsForAllArtifactVersions.toSeq
scalaTarget <- scalaTargets if latestScalaVersionsOfAvailableBinaryVersions.contains(scalaTarget.languageVersion)
} yield scalaTarget.languageVersion -> artifactVersion).groupMap(_._1)(_._2).view.mapValues(_.max).toMap

val scalaSupportByLatestSupportingArtifactVersion: SortedMap[SemanticVersion, ScalaSupport] =
SortedMap.from(latestArtifactVersionByLanguageVersion.groupMap(_._2)(_._1).view.map {
case (artifactVersion, notableLanguageVersions) =>
val notableLanguageVersionsSet = notableLanguageVersions.toSet
artifactVersion -> ScalaSupport(
scalaTargetsForAllArtifactVersions(artifactVersion).filter(st => notableLanguageVersionsSet(st.languageVersion)).toSet
)
})(SemanticVersion.ordering.reverse)

val summaryOfLatestArtifactsSupportingScalaVersions: String = (for (
(artifactVersion, scalaSupport) <- scalaSupportByLatestSupportingArtifactVersion
) yield s"$artifactVersion (${scalaSupport.summary})").mkString(", ")
}

object ArtifactScalaVersionSupport {
def forSpecifiedArtifactAndTargetType(
allAvailableReleases: Seq[Release],
specificArtifact: String,
specificTargetType: ScalaTargetType): ArtifactScalaVersionSupport = {

val scalaTargetsByArtifactVersion = (for {
release <- allAvailableReleases if release.isValid
ref = release.reference if ref.artifact == specificArtifact
scalaTarget <- ref.target if scalaTarget.targetType == specificTargetType
} yield ref.version -> scalaTarget).groupMap(_._1)(_._2)

ArtifactScalaVersionSupport(scalaTargetsByArtifactVersion)
}

case class ScalaSupport(
scalaTargets: Set[ScalaTarget]
) {
val scalaTargetsByLanguageVersion: Map[LanguageVersion, Set[ScalaTarget]] = scalaTargets.groupBy(_.languageVersion)

val languageVersions: SortedSet[LanguageVersion] =
SortedSet.from(scalaTargetsByLanguageVersion.keySet)(LanguageVersion.ordering.reverse)

val platformEditionsSupportedForAllLanguageVersions: Set[PlatformEdition] =
scalaTargetsByLanguageVersion.values.map(_.collect {
case st: ScalaTargetWithPlatformBinaryVersion => st.platformEdition
}).reduce(_ & _)

val platformBinaryVersionsByTargetType: SortedMap[ScalaTargetType, SortedSet[BinaryVersion]] =
SortedMap.from(platformEditionsSupportedForAllLanguageVersions.groupMap(_.targetType)(_.version).view.mapValues(
SortedSet.from(_)(BinaryVersion.ordering.reverse))
)

val targetsSummary: Option[String] = Option.when(platformBinaryVersionsByTargetType.nonEmpty)(
" - " + platformBinaryVersionsByTargetType.map {
case (targetType, platformBinaryVersions) => s"$targetType ${platformBinaryVersions.mkString("+")}"
}.mkString(", ")
)

val summary: String = s"Scala ${languageVersions.mkString(", ")}${targetsSummary.mkString}"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ package ch.epfl.scala.index
package server
package routes

import model._
import release._
import akka.http.scaladsl._
import server.Directives._
import model.StatusCodes._
import model.headers._
import model.headers.CacheDirectives._
import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.model.headers.CacheDirectives._
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.server.Directives._
import ch.epfl.scala.index.model._
import ch.epfl.scala.index.model.release._
import ch.epfl.scala.index.search.DataRepository

import scala.collection.immutable.{SortedMap, SortedSet}

class Badges(dataRepository: DataRepository) {

private val shields = parameters(
Expand Down Expand Up @@ -103,6 +104,33 @@ class Badges(dataRepository: DataRepository) {
}
}

def latestByScalaVersion(
organization: String,
repository: String,
artifact: String
) = {
parameter("targetType".?) { targetTypeString =>
shields { (color, style, logo, logoWidth) =>
val targetType = targetTypeString.flatMap(ScalaTargetType.ofName).getOrElse(Jvm)
onSuccess {
dataRepository.getProjectReleases(Project.Reference(organization, repository))
} { allAvailableReleases =>
val notableScalaSupport =
ArtifactScalaVersionSupport.forSpecifiedArtifactAndTargetType(allAvailableReleases, artifact, targetType)

shieldsSvg(
artifact,
notableScalaSupport.summaryOfLatestArtifactsSupportingScalaVersions,
color,
style,
logo,
logoWidth
)
}
}
}
}

val routes =
get(
concat(
Expand All @@ -116,6 +144,9 @@ class Badges(dataRepository: DataRepository) {
)
)
},
pathPrefix(Segment / Segment/ Segment / "latest-by-scala-version.svg") { (organization, repository, artifact) =>
latestByScalaVersion(organization, repository, artifact)
},
path("count.svg")(
parameter("q")(query =>
shieldsSubject((color, style, logo, logoWidth, subject) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package ch.epfl.scala.index.server

import ch.epfl.scala.index.model.release.ScalaVersion._
import ch.epfl.scala.index.model.release.{LanguageVersion, PreReleaseBinary, Scala3Version, ScalaJvm}
import ch.epfl.scala.index.model.{Milestone, ReleaseCandidate, SemanticVersion}
import org.apache.commons.lang3.StringUtils.countMatches
import org.scalatest.{FunSpec, Matchers}

class ArtifactScalaVersionSupportTest
extends FunSpec
with Matchers {

val `3.0.0-M3`: LanguageVersion = Scala3Version(PreReleaseBinary(3, 0, Some(0), Milestone(3)))
val `3.0.0-RC2`: LanguageVersion = Scala3Version(PreReleaseBinary(3, 0, Some(0), ReleaseCandidate(2)))
val `3.0.0-RC3`: LanguageVersion = Scala3Version(PreReleaseBinary(3, 0, Some(0), ReleaseCandidate(3)))

it("should provide a concise summary of Scala support by the artifact") {
ArtifactScalaVersionSupport(Map(
SemanticVersion(7, 0, 0) -> Seq(ScalaJvm(`2.11`)),
SemanticVersion(7, 1, 0) -> Seq(ScalaJvm(`2.11`), ScalaJvm(`2.12`)),
SemanticVersion(7, 2, 0) -> Seq(ScalaJvm(`2.12`), ScalaJvm(`2.13`))
)).summaryOfLatestArtifactsSupportingScalaVersions shouldBe "7.2.0 (Scala 2.13, 2.12), 7.1.0 (Scala 2.11)"
}

it("should concisely convey which versions of Scala are supported, most recent Scala version first") {
ArtifactScalaVersionSupport(Map(
SemanticVersion(7, 2, 0) -> Seq(
ScalaJvm(`2.12`),
ScalaJvm(`2.13`),
ScalaJvm(`3.0.0-RC3`)
)
)).summaryOfLatestArtifactsSupportingScalaVersions should include("Scala 3.0.0-RC3, 2.13, 2.12")
}

it("should convey the latest artifact version available for each Scala language version") {
val summary = ArtifactScalaVersionSupport(Map(
SemanticVersion(7, 0, 0) -> Seq(ScalaJvm(`2.11`)),
SemanticVersion(7, 1, 0) -> Seq(ScalaJvm(`2.11`)),
SemanticVersion(7, 2, 0) -> Seq(ScalaJvm(`2.12`)),
SemanticVersion(7, 3, 0) -> Seq(ScalaJvm(`2.12`))
)).summaryOfLatestArtifactsSupportingScalaVersions

// these artifact versions are not the latest available support for any Scala language version, so uninteresting:
summary should not(include("7.0.0") or include("7.2.0"))

summary should include("7.1.0 (Scala 2.11)")
summary should include("7.3.0 (Scala 2.12)")
}

it("should, for brevity, not mention a Scala language version more than once, even if it occurs in multiple artifact versions being mentioned") {
val summary = ArtifactScalaVersionSupport(Map(
SemanticVersion(7, 1, 0) -> Seq(ScalaJvm(`2.11`), ScalaJvm(`2.12`)),
SemanticVersion(7, 2, 0) -> Seq(ScalaJvm(`2.12`))
)).summaryOfLatestArtifactsSupportingScalaVersions

// it happens that two artifact versions that support Scala 2.12 will be mentioned...
assert(summary.contains("7.1.0") && summary.contains("7.2.0"))

// ...but for brevity no Scala language version (eg Scala 2.12 in this case) should be mentioned more than once...
countMatches(summary, "2.12") shouldBe 1

// ...specifically it should be listed against the *latest* artifact that supports that Scala language version
summary should include("7.2.0 (Scala 2.12)")
}

it("should, for brevity, only mention the *latest* Scala language versions available for any given Scala binary version family") {
ArtifactScalaVersionSupport(Map(
SemanticVersion(7, 1, 0) -> Seq(ScalaJvm(`2.13`), ScalaJvm(`3.0.0-M3`)),
SemanticVersion(7, 1, 1) -> Seq(ScalaJvm(`2.13`), ScalaJvm(`3.0.0-RC2`)),
SemanticVersion(7, 1, 2) -> Seq(ScalaJvm(`2.13`), ScalaJvm(`3.0.0-RC3`))
)).summaryOfLatestArtifactsSupportingScalaVersions shouldBe "7.1.2 (Scala 3.0.0-RC3, 2.13)"
}

}

0 comments on commit e49f52d

Please sign in to comment.