2018-04-18 11:44:37 +02:00
|
|
|
/**
|
|
|
|
|
* Copyright (C) 2016-2018 Lightbend Inc. <https://www.lightbend.com>
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import sbt._
|
|
|
|
|
import sbt.Keys._
|
|
|
|
|
|
2018-05-07 19:23:30 +09:00
|
|
|
import scala.util.control.NonFatal
|
|
|
|
|
|
2018-04-18 11:44:37 +02:00
|
|
|
/**
|
|
|
|
|
* Generate the "index" pages of stream operators.
|
|
|
|
|
*/
|
|
|
|
|
object StreamOperatorsIndexGenerator extends AutoPlugin {
|
|
|
|
|
|
|
|
|
|
override val projectSettings: Seq[Setting[_]] = inConfig(Compile)(Seq(
|
|
|
|
|
resourceGenerators +=
|
|
|
|
|
generateAlphabeticalIndex(sourceDirectory,
|
|
|
|
|
_ / "paradox" / "stream" / "operators" / "index.md"
|
|
|
|
|
)
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
val categories = Seq(
|
2018-05-29 17:00:48 +09:00
|
|
|
"Source operators",
|
|
|
|
|
"Sink operators",
|
2018-04-18 11:44:37 +02:00
|
|
|
"Additional Sink and Source converters",
|
|
|
|
|
"File IO Sinks and Sources",
|
2018-05-19 10:15:20 +09:00
|
|
|
"Simple operators",
|
2018-05-29 17:00:48 +09:00
|
|
|
"Flow operators composed of Sinks and Sources",
|
2018-05-19 10:15:20 +09:00
|
|
|
"Asynchronous operators",
|
2018-05-29 17:00:48 +09:00
|
|
|
"Timer driven operators",
|
|
|
|
|
"Backpressure aware operators",
|
|
|
|
|
"Nesting and flattening operators",
|
|
|
|
|
"Time aware operators",
|
|
|
|
|
"Fan-in operators",
|
2018-04-18 11:44:37 +02:00
|
|
|
// TODO these don't show up as def's yet so don't show up in the index..
|
2018-05-29 17:00:48 +09:00
|
|
|
// "Fan-out operators",
|
|
|
|
|
"Watching status operators",
|
|
|
|
|
"Actor interop operators"
|
2018-04-18 11:44:37 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def categoryId(name: String): String = name.toLowerCase.replace(' ', '-')
|
|
|
|
|
|
|
|
|
|
val pendingSourceOrFlow = Seq(
|
|
|
|
|
"to",
|
|
|
|
|
"toMat",
|
|
|
|
|
"via",
|
|
|
|
|
"viaMat",
|
|
|
|
|
"async",
|
|
|
|
|
"upcast",
|
|
|
|
|
"shape",
|
|
|
|
|
"run",
|
|
|
|
|
"runWith",
|
|
|
|
|
"traversalBuilder",
|
|
|
|
|
"runFold",
|
|
|
|
|
"runFoldAsync",
|
|
|
|
|
"runForeach",
|
|
|
|
|
"runReduce",
|
|
|
|
|
"named",
|
|
|
|
|
"throttleEven",
|
|
|
|
|
"actorPublisher",
|
|
|
|
|
"addAttributes",
|
|
|
|
|
"mapMaterializedValue",
|
|
|
|
|
// *Graph:
|
|
|
|
|
"concatGraph",
|
|
|
|
|
"prependGraph",
|
|
|
|
|
"mergeSortedGraph",
|
|
|
|
|
"fromGraph",
|
|
|
|
|
"interleaveGraph",
|
|
|
|
|
"zipGraph",
|
|
|
|
|
"mergeGraph",
|
|
|
|
|
"wireTapGraph",
|
|
|
|
|
"alsoToGraph",
|
|
|
|
|
"orElseGraph",
|
|
|
|
|
"divertToGraph",
|
|
|
|
|
"zipWithGraph"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// FIXME document these methods as well
|
|
|
|
|
val pendingTestCases = Map(
|
|
|
|
|
"Source" -> (pendingSourceOrFlow ++ Seq(
|
|
|
|
|
"preMaterialize"
|
|
|
|
|
)),
|
|
|
|
|
"Flow" -> (pendingSourceOrFlow ++ Seq(
|
|
|
|
|
"lazyInit",
|
|
|
|
|
"fromProcessorMat",
|
|
|
|
|
"toProcessor",
|
|
|
|
|
"fromProcessor",
|
|
|
|
|
"of",
|
|
|
|
|
"join",
|
|
|
|
|
"joinMat",
|
|
|
|
|
"fromFunction"
|
|
|
|
|
)),
|
|
|
|
|
"Sink" -> Seq(
|
|
|
|
|
"lazyInit",
|
|
|
|
|
"collection",
|
|
|
|
|
"contramap",
|
|
|
|
|
"named",
|
|
|
|
|
"addAttributes",
|
|
|
|
|
"async",
|
|
|
|
|
"mapMaterializedValue",
|
|
|
|
|
"runWith",
|
|
|
|
|
"shape",
|
|
|
|
|
"traversalBuilder",
|
|
|
|
|
"fromGraph",
|
|
|
|
|
"actorSubscriber",
|
|
|
|
|
"foldAsync",
|
|
|
|
|
"newOnCompleteStage"
|
|
|
|
|
),
|
2018-05-07 19:23:30 +09:00
|
|
|
"ActorSink" → Seq(
|
|
|
|
|
"actorRefWithAck"
|
|
|
|
|
),
|
|
|
|
|
"ActorSource" → Seq(
|
|
|
|
|
"actorRef"
|
2018-04-18 11:44:37 +02:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val ignore =
|
|
|
|
|
Set("equals", "hashCode", "notify", "notifyAll", "wait", "toString", "getClass") ++
|
|
|
|
|
Set("productArity", "canEqual", "productPrefix", "copy", "productIterator", "productElement") ++
|
|
|
|
|
Set("create", "apply", "ops", "appendJava", "andThen", "andThenMat", "isIdentity", "withAttributes", "transformMaterializing") ++
|
|
|
|
|
Set("asScala", "asJava", "deprecatedAndThen", "deprecatedAndThenMat") ++
|
|
|
|
|
Set("++")
|
|
|
|
|
|
|
|
|
|
def isPending(element: String, opName: String) =
|
|
|
|
|
pendingTestCases.get(element).exists(_.contains(opName))
|
|
|
|
|
|
|
|
|
|
def generateAlphabeticalIndex(dir: SettingKey[File], locate: File ⇒ File) = Def.task[Seq[File]] {
|
|
|
|
|
val file = locate(dir.value)
|
|
|
|
|
|
|
|
|
|
val defs =
|
|
|
|
|
List(
|
|
|
|
|
"akka-stream/src/main/scala/akka/stream/scaladsl/Source.scala",
|
|
|
|
|
"akka-stream/src/main/scala/akka/stream/javadsl/Source.scala",
|
|
|
|
|
// "akka-stream/src/main/scala/akka/stream/scaladsl/SubSource.scala",
|
|
|
|
|
// "akka-stream/src/main/scala/akka/stream/javadsl/SubSource.scala",
|
|
|
|
|
"akka-stream/src/main/scala/akka/stream/scaladsl/Flow.scala",
|
|
|
|
|
"akka-stream/src/main/scala/akka/stream/javadsl/Flow.scala",
|
|
|
|
|
// "akka-stream/src/main/scala/akka/stream/scaladsl/SubFlow.scala",
|
|
|
|
|
// "akka-stream/src/main/scala/akka/stream/javadsl/SubFlow.scala",
|
|
|
|
|
// "akka-stream/src/main/scala/akka/stream/scaladsl/RunnableFlow.scala",
|
|
|
|
|
// "akka-stream/src/main/scala/akka/stream/javadsl/RunnableFlow.scala",
|
|
|
|
|
"akka-stream/src/main/scala/akka/stream/scaladsl/Sink.scala",
|
|
|
|
|
"akka-stream/src/main/scala/akka/stream/javadsl/Sink.scala",
|
|
|
|
|
"akka-stream/src/main/scala/akka/stream/scaladsl/StreamConverters.scala",
|
|
|
|
|
"akka-stream/src/main/scala/akka/stream/javadsl/StreamConverters.scala",
|
|
|
|
|
"akka-stream/src/main/scala/akka/stream/scaladsl/FileIO.scala",
|
|
|
|
|
"akka-stream/src/main/scala/akka/stream/javadsl/FileIO.scala",
|
2018-05-07 19:23:30 +09:00
|
|
|
|
|
|
|
|
// akka-stream-typed
|
|
|
|
|
"akka-stream-typed/src/main/scala/akka/stream/typed/javadsl/ActorSource.scala",
|
|
|
|
|
"akka-stream-typed/src/main/scala/akka/stream/typed/scaladsl/ActorSource.scala",
|
|
|
|
|
"akka-stream-typed/src/main/scala/akka/stream/typed/javadsl/ActorFlow.scala",
|
|
|
|
|
"akka-stream-typed/src/main/scala/akka/stream/typed/scaladsl/ActorFlow.scala",
|
|
|
|
|
"akka-stream-typed/src/main/scala/akka/stream/typed/scaladsl/ActorSink.scala",
|
|
|
|
|
"akka-stream-typed/src/main/scala/akka/stream/typed/javadsl/ActorSink.scala",
|
2018-04-18 11:44:37 +02:00
|
|
|
).flatMap{ f ⇒
|
2018-05-07 19:23:30 +09:00
|
|
|
val slashesNr = f.count(_ == '/')
|
|
|
|
|
val element = f.split("/")(slashesNr).split("\\.")(0)
|
2018-04-18 11:44:37 +02:00
|
|
|
IO.read(new File(f)).split("\n")
|
|
|
|
|
.map(_.trim).filter(_.startsWith("def "))
|
|
|
|
|
.map(_.drop(4).takeWhile(c ⇒ c != '[' && c != '(' && c != ':'))
|
|
|
|
|
.filter(op => !isPending(element, op))
|
|
|
|
|
.filter(op => !ignore.contains(op))
|
|
|
|
|
.map(_.replaceAll("Mat$", ""))
|
|
|
|
|
.map(method ⇒ (element, method))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val sourceAndFlow = defs.collect { case ("Source", method) => method } intersect defs.collect { case ("Flow", method) => method }
|
|
|
|
|
|
|
|
|
|
val groupedDefs =
|
|
|
|
|
defs.map {
|
|
|
|
|
case (element @ ("Source" | "Flow"), method) if sourceAndFlow.contains(method) =>
|
|
|
|
|
("Source/Flow", method, s"Source-or-Flow/$method.md")
|
|
|
|
|
case (element, method) =>
|
|
|
|
|
(element, method, s"$element/$method.md")
|
|
|
|
|
}.distinct
|
|
|
|
|
|
|
|
|
|
val tablePerCategory = groupedDefs.map { case (element, method, md) =>
|
|
|
|
|
val (description, category) = getDetails(file.getParentFile / md)
|
|
|
|
|
category -> (element, method, md, description)
|
|
|
|
|
}
|
|
|
|
|
.groupBy(_._1)
|
|
|
|
|
.mapValues(lines =>
|
2018-05-07 19:23:30 +09:00
|
|
|
"| |Operator|Description|\n" ++ // TODO mini images here too
|
2018-04-18 11:44:37 +02:00
|
|
|
"|--|--|--|\n" ++
|
|
|
|
|
lines
|
|
|
|
|
.map(_._2)
|
|
|
|
|
.sortBy(_._2)
|
2018-04-23 14:37:20 +02:00
|
|
|
.map { case (element, method, md, description) => s"""|$element|<a name="${method.toLowerCase}"></a>@ref[$method]($md)|$description|""" }
|
2018-04-18 11:44:37 +02:00
|
|
|
.mkString("\n")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
val tables = categories.map { category =>
|
|
|
|
|
s"## $category\n\n" ++
|
|
|
|
|
IO.read(dir.value / "categories" / (categoryId(category) + ".md")) ++ "\n\n" ++
|
|
|
|
|
tablePerCategory(category)
|
|
|
|
|
}.mkString("\n\n")
|
|
|
|
|
|
|
|
|
|
val content =
|
2018-05-07 19:23:30 +09:00
|
|
|
"<!-- DO NOT EDIT DIRECTLY: This file is generated by `project/StreamOperatorsIndexGenerator`. See CONTRIBUTING.md for details. -->\n" +
|
|
|
|
|
"# Operators\n\n" +
|
|
|
|
|
tables +
|
|
|
|
|
"\n\n@@@ index\n\n" +
|
2018-04-18 11:44:37 +02:00
|
|
|
groupedDefs.map { case (_, method, md) => s"* [$method]($md)" }.mkString("\n") + "\n\n@@@\n"
|
|
|
|
|
|
|
|
|
|
if (!file.exists || IO.read(file) != content) IO.write(file, content)
|
|
|
|
|
Seq(file)
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-07 19:23:30 +09:00
|
|
|
def getDetails(file: File): (String, String) = try {
|
2018-04-29 17:20:36 +09:00
|
|
|
val contents = IO.read(file)
|
|
|
|
|
val lines = contents.split("\\r?\\n")
|
|
|
|
|
require(
|
|
|
|
|
lines.size >= 5,
|
|
|
|
|
s"There must be at least 5 lines in $file, including the title, description, category link and an empty line between each two of them"
|
|
|
|
|
)
|
2018-04-18 11:44:37 +02:00
|
|
|
// This forces the short description to be on a single line. We could make this smarter,
|
|
|
|
|
// but 'forcing' the short description to be really short seems nice as well.
|
2018-04-29 17:20:36 +09:00
|
|
|
val description = lines(2)
|
|
|
|
|
require(!description.isEmpty, s"description in $file must be non-empty, single-line description at the 3rd line")
|
|
|
|
|
val categoryLink = lines(4)
|
|
|
|
|
require(categoryLink.startsWith("@ref"), s"""category link in $file should start with @ref, but saw \"$categoryLink\"""")
|
2018-04-18 11:44:37 +02:00
|
|
|
val categoryName = categoryLink.drop(5).takeWhile(_ != ']')
|
|
|
|
|
val categoryLinkId = categoryLink.dropWhile(_ != '#').drop(1).takeWhile(_ != ')')
|
|
|
|
|
require(categories.contains(categoryName), s"category $categoryName in $file should be known")
|
|
|
|
|
require(categoryLinkId == categoryId(categoryName), s"category id $categoryLinkId in $file")
|
|
|
|
|
(description, categoryName)
|
2018-05-07 19:23:30 +09:00
|
|
|
} catch {
|
|
|
|
|
case NonFatal(ex) ⇒
|
|
|
|
|
throw new RuntimeException(s"Unable to extract details from $file", ex)
|
2018-04-18 11:44:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|