From 04799ce8a2d0678eb0b62e0ed52918a531affc35 Mon Sep 17 00:00:00 2001 From: Matthew de Detrich Date: Tue, 22 Mar 2022 15:33:01 +0100 Subject: [PATCH] Make ReadinessLevel's configurable (#34) --- README.md | 92 ++++++++++++++++--- .../ParadoxProjectInfoPlugin.scala | 14 +-- .../ParadoxProjectInfoPluginKeys.scala | 2 + .../projectinfo/ProjectInfoDirective.scala | 9 +- .../lightbend/paradox/projectinfo/model.scala | 54 +++-------- .../paradox/projectinfo/ProjectInfoSpec.scala | 28 +++--- ...Spec.scala => SampleReadinessLevels.scala} | 33 ++++--- 7 files changed, 141 insertions(+), 91 deletions(-) rename src/test/scala/com/lightbend/paradox/projectinfo/{ReadinessLevelSpec.scala => SampleReadinessLevels.scala} (55%) diff --git a/README.md b/README.md index a32cc74..97bcad3 100644 --- a/README.md +++ b/README.md @@ -53,19 +53,6 @@ project-info { url: "https://gitter.im/akka/alpakka-kafka" } ] - levels: [ - { - readiness: Supported - since: "2018-11-22" - since-version: "0.22" - } - { - readiness: Incubating - since: "2018-08-22" - since-version: "0.16" - note: "Community rocks this module" - } - ] } } ``` @@ -83,6 +70,85 @@ The quick brown fox... ``` [Example](https://github.com/lightbend/sbt-paradox-project-info/blob/master/src/sbt-test/project-info/happy-path/src/main/paradox/index.md) +### Readiness Levels + +Readiness levels is a convenient way to show the support for different projects. In order to use readiness levels you +first need to specify what they mean and how to render them, i.e. + +```sbt +import com.lightbend.paradox.projectinfo._ +import com.lightbend.paradox.projectinfo.ParadoxProjectInfoPluginKeys._ + +readinessLevels ++= Map( + "Supported" -> new ReadinessLevel { + val name = "This project is supported" + }, + "NotSupported" -> new ReadinessLevel { + val name = "This project is not supported" + } +) +``` + +Then in your `project/project-info.conf` you can specify these readiness levels using the `levels` config path, i.e. + +```hocon +project-info { + # version is overridden from the `projectInfoVersion` key (which defaults to sbt's project version) + version: "current" + scala-versions: ["2.12", "2.13"] + jdk-versions: ["OpenJDK 8"] + core { + title: "The core project" + // if undefined, sbt's crossScalaVersions are used + scala-versions: ${project-info.scala-versions} + jdk-versions: ${project-info.jdk-versions} + jpms-name: "alpakka.core" + snapshots: { + text: "Snapshots are available" + url: "snapshots.html" + new-tab: false + } + issues: { + url: "https://github.com/lightbend/sbt-paradox-project-info/issues" + text: "Github issues" + } + release-notes: { + url: "https://github.com/lightbend/sbt-paradox-project-info/releases" + text: "Github releases" + } + api-docs: [ + { + text: "Scaladoc" + url: "https://developer.lightbend.com/docs/api/alpakka/"${project-info.version}"/akka/stream/alpakka/index.html" + } + ] + forums: [ + { + text: "Lightbend Discuss" + url: "https://discuss.lightbend.com/c/akka/" + } + { + text: "akka/alpakka-kafka Gitter channel" + url: "https://gitter.im/akka/alpakka-kafka" + } + ] + levels: [ + { + readiness: Supported + since: "2018-11-22" + since-version: "0.22" + } + { + readiness: NotSupported + since: "2018-08-22" + since-version: "0.16" + note: "Alpha level of module" + } + ] + } +} +``` + ## License The license is Apache 2.0, see LICENSE. diff --git a/src/main/scala/com/lightbend/paradox/projectinfo/ParadoxProjectInfoPlugin.scala b/src/main/scala/com/lightbend/paradox/projectinfo/ParadoxProjectInfoPlugin.scala index 70b41ff..a9df5bb 100644 --- a/src/main/scala/com/lightbend/paradox/projectinfo/ParadoxProjectInfoPlugin.scala +++ b/src/main/scala/com/lightbend/paradox/projectinfo/ParadoxProjectInfoPlugin.scala @@ -35,7 +35,8 @@ object ParadoxProjectInfoPlugin extends AutoPlugin { override def projectSettings: Seq[Setting[_]] = projectInfoSettings(Compile) override def globalSettings: Seq[Setting[_]] = Seq( - projectInfoVersion := version.value + projectInfoVersion := version.value, + readinessLevels := Map.empty ) def projectInfoSettings(config: Configuration): Seq[Setting[_]] = @@ -44,9 +45,10 @@ object ParadoxProjectInfoPlugin extends AutoPlugin { Def.task { val s = state.value Seq { _: Writer.Context ⇒ - val f = new File((LocalRootProject / baseDirectory).value, "project/project-info.conf") - val extracted = Project.extract(s) - val rootVersion = extracted.get(projectInfoVersion) + val f = new File((LocalRootProject / baseDirectory).value, "project/project-info.conf") + val extracted = Project.extract(s) + val rootVersion = projectInfoVersion.value + val readinessLevelsMap = readinessLevels.value val config = if (f.exists()) { ConfigFactory .parseFile(f) @@ -63,7 +65,7 @@ object ParadoxProjectInfoPlugin extends AutoPlugin { val projectName = try extracted.get(project / name) catch { - case e: Exception => + case _: Exception => throw new RuntimeException( s"couldn't read sbt setting `$projectId / name`, does the projectId exist?" ) @@ -78,7 +80,7 @@ object ParadoxProjectInfoPlugin extends AutoPlugin { extracted.get(project / crossScalaVersions).toList ) } - new ProjectInfoDirective(config, sbtDetails) + new ProjectInfoDirective(config, sbtDetails, readinessLevelsMap) } } }.value diff --git a/src/main/scala/com/lightbend/paradox/projectinfo/ParadoxProjectInfoPluginKeys.scala b/src/main/scala/com/lightbend/paradox/projectinfo/ParadoxProjectInfoPluginKeys.scala index 2596fd4..a88f44d 100644 --- a/src/main/scala/com/lightbend/paradox/projectinfo/ParadoxProjectInfoPluginKeys.scala +++ b/src/main/scala/com/lightbend/paradox/projectinfo/ParadoxProjectInfoPluginKeys.scala @@ -20,6 +20,8 @@ import sbt._ trait ParadoxProjectInfoPluginKeys { val projectInfoVersion = settingKey[String]("The version/revision propagated into `project-info.version`.") + val readinessLevels = + settingKey[Map[String, ReadinessLevel]]("The referenced readiness-levels when using levels") } object ParadoxProjectInfoPluginKeys extends ParadoxProjectInfoPluginKeys diff --git a/src/main/scala/com/lightbend/paradox/projectinfo/ProjectInfoDirective.scala b/src/main/scala/com/lightbend/paradox/projectinfo/ProjectInfoDirective.scala index d4d23fe..55aa3af 100644 --- a/src/main/scala/com/lightbend/paradox/projectinfo/ProjectInfoDirective.scala +++ b/src/main/scala/com/lightbend/paradox/projectinfo/ProjectInfoDirective.scala @@ -23,13 +23,16 @@ import com.typesafe.config.Config import org.pegdown.Printer import org.pegdown.ast.{DirectiveNode, Visitor} -class ProjectInfoDirective(config: Config, moduleToSbtValues: String => SbtValues) - extends LeafBlockDirective("project-info") { +class ProjectInfoDirective( + config: Config, + moduleToSbtValues: String => SbtValues, + readinessLevelsMap: Map[String, ReadinessLevel] +) extends LeafBlockDirective("project-info") { def render(node: DirectiveNode, visitor: Visitor, printer: Printer): Unit = { val projectId = node.attributes.value("projectId") if (config.hasPath(projectId)) { val module = config.getConfig(projectId) - val data = SbtAndProjectInfo(moduleToSbtValues(projectId), ProjectInfo(projectId, module)) + val data = SbtAndProjectInfo(moduleToSbtValues(projectId), ProjectInfo(projectId, readinessLevelsMap, module)) ProjectInfoDirective.renderInfo(printer, data) } else throw new RuntimeException(s"project-info.conf does not contain `$projectId`") } diff --git a/src/main/scala/com/lightbend/paradox/projectinfo/model.scala b/src/main/scala/com/lightbend/paradox/projectinfo/model.scala index 01cb78b..38586b7 100644 --- a/src/main/scala/com/lightbend/paradox/projectinfo/model.scala +++ b/src/main/scala/com/lightbend/paradox/projectinfo/model.scala @@ -35,41 +35,8 @@ case class SbtValues( crossScalaVersions: immutable.Seq[String] ) -trait ReadinessLevel { def name: String } -object ReadinessLevel { - private def glossary(anchor: String, label: String): String = - s"""$label""".stripMargin - - case object Supported extends ReadinessLevel { - val name = - s"""${glossary( - "supported", - "Supported" - )}, Lightbend Subscription provides support""" - } - case object Certified extends ReadinessLevel { - val name = - s"""${glossary("certified", "Certified")} by Lightbend""" - } - case object Incubating extends ReadinessLevel { - val name = glossary("incubating", "Incubating") - } - case object CommunityDriven extends ReadinessLevel { - val name = glossary("community-driven", "Community-driven") - } - case object EndOfLife extends ReadinessLevel { - val name = - s"${glossary("eol", "End-of-Life")}, it is not recommended to use this project any more." - } - - def fromString(s: String): ReadinessLevel = s match { - case "Supported" => Supported - case "Certified" => Certified - case "Incubating" => Incubating - case "CommunityDriven" => CommunityDriven - case "EndOfLife" => EndOfLife - case other => throw new IllegalArgumentException(s"unknown readiness level: $other") - } +trait ReadinessLevel { + def name: String } case class Link(url: String, text: Option[String], newTab: Boolean = true) @@ -94,9 +61,9 @@ case class Level( object Level { - def apply(c: Config): Level = { + def apply(c: Config, readinessLevelsMap: Map[String, ReadinessLevel]): Level = { import Util.ExtendedConfig - val ml = c.getReadinessLevel("readiness") + val ml = c.getReadinessLevel("readiness", readinessLevelsMap) val since = c.getLocalDate("since") val sinceVersion = c.getString("since-version") val ends = c.getOption("ends", _.getLocalDate(_)) @@ -122,7 +89,7 @@ case class ProjectInfo( object ProjectInfo { import Util.ExtendedConfig - def apply(name: String, c: Config): ProjectInfo = { + def apply(name: String, readinessLevelsMap: Map[String, ReadinessLevel], c: Config): ProjectInfo = { val title = c.getOption("title", _.getString(_)).getOrElse(name) val scalaVersions = if (c.hasPath("scala-versions")) c.getStringList("scala-versions").asScala.toList @@ -137,7 +104,8 @@ object ProjectInfo { val levels = c .getOption( "levels", - (config, string) => for { item <- config.getObjectList(string).asScala.toList } yield Level(item.toConfig) + (config, string) => + for { item <- config.getObjectList(string).asScala.toList } yield Level(item.toConfig, readinessLevelsMap) ) .getOrElse(List.empty) @@ -163,8 +131,12 @@ object Util { private val dateFormat = DateTimeFormatter.ISO_LOCAL_DATE implicit class ExtendedConfig(c: Config) { - def getReadinessLevel(path: String): ReadinessLevel = ReadinessLevel.fromString(c.getString(path)) - def getLocalDate(path: String): LocalDate = LocalDate.parse(c.getString(path), dateFormat) + def getReadinessLevel(path: String, readinessLevelsMap: Map[String, ReadinessLevel]): ReadinessLevel = { + val value = c.getString(path) + readinessLevelsMap.getOrElse(value, throw new Exception(s"No configured readinessLevels with value:$value")) + } + + def getLocalDate(path: String): LocalDate = LocalDate.parse(c.getString(path), dateFormat) def getOption[T](path: String, read: (Config, String) => T): Option[T] = if (c.hasPath(path)) Some(read(c, path)) else None def getOptionalBoolean(path: String, defaultValue: Boolean): Boolean = diff --git a/src/test/scala/com/lightbend/paradox/projectinfo/ProjectInfoSpec.scala b/src/test/scala/com/lightbend/paradox/projectinfo/ProjectInfoSpec.scala index 18d263a..691aedb 100644 --- a/src/test/scala/com/lightbend/paradox/projectinfo/ProjectInfoSpec.scala +++ b/src/test/scala/com/lightbend/paradox/projectinfo/ProjectInfoSpec.scala @@ -36,7 +36,9 @@ class ProjectInfoSpec extends AnyWordSpec with Matchers { |} """.stripMargin val c = ConfigFactory.parseString(in).getConfig("level") - Level(c) should be(Level(ReadinessLevel.Incubating, LocalDate.of(2018, 11, 22), "0.12", None, None)) + Level(c, SampleReadinessLevels.values) should be( + Level(SampleReadinessLevels.Incubating, LocalDate.of(2018, 11, 22), "0.12", None, None) + ) } } @@ -51,7 +53,7 @@ class ProjectInfoSpec extends AnyWordSpec with Matchers { |} """.stripMargin val c = ConfigFactory.parseString(in).getConfig("core") - ProjectInfo("core", c) should be( + ProjectInfo("core", SampleReadinessLevels.values, c) should be( ProjectInfo( "core", "core", @@ -93,7 +95,7 @@ class ProjectInfoSpec extends AnyWordSpec with Matchers { val c = ConfigFactory.parseString(in).getConfig("project-info") val name = "core" val conf = c.getConfig(name) - ProjectInfo(name, conf) should be( + ProjectInfo(name, SampleReadinessLevels.values, conf) should be( ProjectInfo( "core", title = "The core project", @@ -107,7 +109,7 @@ class ProjectInfoSpec extends AnyWordSpec with Matchers { snapshots = None, levels = List( Level( - ReadinessLevel.Incubating, + SampleReadinessLevels.Incubating, LocalDate.of(2018, 11, 22), "0.18", None, @@ -150,7 +152,7 @@ class ProjectInfoSpec extends AnyWordSpec with Matchers { val c = ConfigFactory.parseString(in).resolve().getConfig("project-info") val name = "core" val conf = c.getConfig(name) - ProjectInfo(name, conf) should be( + ProjectInfo(name, SampleReadinessLevels.values, conf) should be( ProjectInfo( "core", title = "The core project", @@ -163,9 +165,9 @@ class ProjectInfoSpec extends AnyWordSpec with Matchers { releaseNotes = None, snapshots = None, levels = List( - Level(ReadinessLevel.Supported, LocalDate.of(2018, 12, 12), "0.21", None, None), + Level(SampleReadinessLevels.Supported, LocalDate.of(2018, 12, 12), "0.21", None, None), Level( - ReadinessLevel.Incubating, + SampleReadinessLevels.Incubating, LocalDate.of(2018, 11, 22), "0.18", None, @@ -212,7 +214,7 @@ class ProjectInfoSpec extends AnyWordSpec with Matchers { val c = ConfigFactory.parseString(in).resolve().getConfig("project-info") val name = "core" val conf = c.getConfig(name) - ProjectInfo(name, conf) should be( + ProjectInfo(name, SampleReadinessLevels.values, conf) should be( ProjectInfo( "core", title = "The core project", @@ -234,7 +236,7 @@ class ProjectInfoSpec extends AnyWordSpec with Matchers { releaseNotes = None, snapshots = None, levels = List( - Level(ReadinessLevel.Supported, LocalDate.of(2018, 12, 12), "0.21", None, None) + Level(SampleReadinessLevels.Supported, LocalDate.of(2018, 12, 12), "0.21", None, None) ) ) ) @@ -276,7 +278,7 @@ class ProjectInfoSpec extends AnyWordSpec with Matchers { val c = ConfigFactory.parseString(in).resolve().getConfig("project-info") val name = "core" val conf = c.getConfig(name) - ProjectInfo(name, conf) should be( + ProjectInfo(name, SampleReadinessLevels.values, conf) should be( ProjectInfo( "core", title = "The core project", @@ -292,7 +294,7 @@ class ProjectInfoSpec extends AnyWordSpec with Matchers { releaseNotes = None, snapshots = None, levels = List( - Level(ReadinessLevel.Supported, LocalDate.of(2018, 12, 12), "0.21", None, None) + Level(SampleReadinessLevels.Supported, LocalDate.of(2018, 12, 12), "0.21", None, None) ) ) ) @@ -324,7 +326,7 @@ class ProjectInfoSpec extends AnyWordSpec with Matchers { val c = ConfigFactory.parseString(in).resolve().getConfig("project-info") val name = "core" val conf = c.getConfig(name) - ProjectInfo(name, conf) should be( + ProjectInfo(name, SampleReadinessLevels.values, conf) should be( ProjectInfo( "core", title = "The core project", @@ -343,7 +345,7 @@ class ProjectInfoSpec extends AnyWordSpec with Matchers { ) ), levels = List( - Level(ReadinessLevel.Supported, LocalDate.of(2018, 12, 12), "0.21", None, None) + Level(SampleReadinessLevels.Supported, LocalDate.of(2018, 12, 12), "0.21", None, None) ) ) ) diff --git a/src/test/scala/com/lightbend/paradox/projectinfo/ReadinessLevelSpec.scala b/src/test/scala/com/lightbend/paradox/projectinfo/SampleReadinessLevels.scala similarity index 55% rename from src/test/scala/com/lightbend/paradox/projectinfo/ReadinessLevelSpec.scala rename to src/test/scala/com/lightbend/paradox/projectinfo/SampleReadinessLevels.scala index a3cac50..b49a719 100644 --- a/src/test/scala/com/lightbend/paradox/projectinfo/ReadinessLevelSpec.scala +++ b/src/test/scala/com/lightbend/paradox/projectinfo/SampleReadinessLevels.scala @@ -16,21 +16,24 @@ package com.lightbend.paradox.projectinfo -import ReadinessLevel.* -import org.scalatest.flatspec.AnyFlatSpec - -class ReadinessLevelSpec extends AnyFlatSpec { - - "String values" should "read correctly" in { - val values = Map( - "EndOfLife" -> EndOfLife, - "Supported" -> Supported, - "CommunityDriven" -> CommunityDriven, - "Incubating" -> Incubating - ) - for { - (s, expected) <- values - } assert(ReadinessLevel.fromString(s) === expected) +object SampleReadinessLevels { + case object EndOfLife extends ReadinessLevel { + val name = "End of life" + } + case object Supported extends ReadinessLevel { + val name = "Supported" + } + case object CommunityDriven extends ReadinessLevel { + val name = "Community Driven" + } + case object Incubating extends ReadinessLevel { + val name = "Incubating" } + val values = Map( + "EndOfLife" -> EndOfLife, + "Supported" -> Supported, + "CommunityDriven" -> CommunityDriven, + "Incubating" -> Incubating + ) }