From eae6493224581db4ee801e15bbf200e3c03724a8 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 19 Nov 2019 13:47:25 -0500 Subject: [PATCH 1/5] Update interplay to version 2.1.2 --- .travis.yml | 4 ++-- build.sbt | 2 +- project/plugins.sbt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f279ffc..70832d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,8 +22,8 @@ matrix: scala: - 2.10.7 - - 2.12.8 - - 2.13.0 + - 2.12.10 + - 2.13.1 script: sbt ++$TRAVIS_SCALA_VERSION test publishLocal diff --git a/build.sbt b/build.sbt index 1d94c18..95233c6 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,7 @@ libraryDependencies ++= Seq( def specs2Deps(scalaVer: String): Seq[ModuleID] = scalaVer match { case ScalaVersions.scala210 => Seq("org.specs2" %% "specs2-core" % "3.9.5" % Test) - case _ => Seq("org.specs2" %% "specs2-core" % "4.5.1" % Test) + case _ => Seq("org.specs2" %% "specs2-core" % "4.8.1" % Test) } javacOptions ++= Seq( diff --git a/project/plugins.sbt b/project/plugins.sbt index 135451a..748da7f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,2 +1,2 @@ -addSbtPlugin("com.typesafe.play" % "interplay" % "2.1.1") +addSbtPlugin("com.typesafe.play" % "interplay" % "2.1.2") addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.0-M5") From 41311d0623d9f3f8d8fed759e1161f8af006b7de Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 19 Nov 2019 13:48:13 -0500 Subject: [PATCH 2/5] Add scalafmt --- .scalafmt.conf | 11 +++++++++++ .travis.yml | 2 +- project/plugins.sbt | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .scalafmt.conf diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..2bd05ea --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,11 @@ +align = true +assumeStandardLibraryStripMargin = true +danglingParentheses = true +docstrings = JavaDoc +maxColumn = 120 +project.git = true +rewrite.rules = [ AvoidInfix, ExpandImportSelectors, RedundantParens, SortModifiers, PreferCurlyFors ] +rewrite.sortModifiers.order = [ "private", "protected", "final", "sealed", "abstract", "implicit", "override", "lazy" ] +spaces.inImportCurlyBraces = true # more idiomatic to include whitepsace in import x.{ yyy } +trailingCommas = preserve +version = 2.2.2 diff --git a/.travis.yml b/.travis.yml index 70832d0..6e48d9e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,7 @@ scala: - 2.12.10 - 2.13.1 -script: sbt ++$TRAVIS_SCALA_VERSION test publishLocal +script: sbt ++$TRAVIS_SCALA_VERSION test publishLocal scalafmtCheckAll scalafmtSbtCheck cache: directories: diff --git a/project/plugins.sbt b/project/plugins.sbt index 748da7f..2d153e6 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,2 +1,3 @@ addSbtPlugin("com.typesafe.play" % "interplay" % "2.1.2") addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.0-M5") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.2.1") \ No newline at end of file From 6dbfccccc855018c34701fc26de7cdb5b28cc279 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 19 Nov 2019 13:48:34 -0500 Subject: [PATCH 3/5] Do not accept Java 11 failures --- .travis.yml | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6e48d9e..7ea664a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,11 @@ language: scala -before_install: - - curl -sL https://github.com/shyiko/jabba/raw/master/install.sh | bash && . ~/.jabba/jabba.sh - +before_install: curl -sL https://github.com/shyiko/jabba/raw/master/install.sh | bash && . ~/.jabba/jabba.sh install: jabba install "adopt@~1.$TRAVIS_JDK.0-0" && jabba use "$_" && java -Xmx32m -version env: - global: - - JABBA_HOME=$HOME/.jabba - matrix: - - TRAVIS_JDK=8 - - TRAVIS_JDK=11 - -matrix: - fast_finish: true - allow_failures: - # Java 11 is still not fully supported. It is good that we are already - # testing play-ws using it to better discover possible problems but we - # can allow failures here too. - - env: TRAVIS_JDK=11 + - TRAVIS_JDK=8 + - TRAVIS_JDK=11 scala: - 2.10.7 From d65709a4ab4a1c996fe9e4f1a7dc709b1c2f33dd Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 19 Nov 2019 13:51:06 -0500 Subject: [PATCH 4/5] Format code with scalafmt --- build.sbt | 15 +- project/plugins.sbt | 6 +- src/main/scala/play/doc/FileRepository.scala | 43 ++- src/main/scala/play/doc/PageIndex.scala | 114 +++--- src/main/scala/play/doc/PlayDoc.scala | 358 ++++++++++-------- .../scala/play/doc/PlayDocTemplates.scala | 41 +- .../play/doc/PrettifyVerbatimSerializer.scala | 8 +- .../scala/play/doc/FileRepositorySpec.scala | 11 +- src/test/scala/play/doc/PageIndexSpec.scala | 19 +- src/test/scala/play/doc/PlayDocSpec.scala | 30 +- 10 files changed, 353 insertions(+), 292 deletions(-) diff --git a/build.sbt b/build.sbt index 95233c6..d3fc67a 100644 --- a/build.sbt +++ b/build.sbt @@ -8,18 +8,20 @@ lazy val `play-doc` = (project in file(".")) crossScalaVersions := Seq(scala210, scala212, scala213) libraryDependencies ++= Seq( - "org.pegdown" % "pegdown" % "1.6.0", - "commons-io" % "commons-io" % "2.6" + "org.pegdown" % "pegdown" % "1.6.0", + "commons-io" % "commons-io" % "2.6" ) ++ specs2Deps(scalaVersion.value) def specs2Deps(scalaVer: String): Seq[ModuleID] = scalaVer match { case ScalaVersions.scala210 => Seq("org.specs2" %% "specs2-core" % "3.9.5" % Test) - case _ => Seq("org.specs2" %% "specs2-core" % "4.8.1" % Test) + case _ => Seq("org.specs2" %% "specs2-core" % "4.8.1" % Test) } javacOptions ++= Seq( - "-source", "1.8", - "-target", "1.8", + "-source", + "1.8", + "-target", + "1.8", "-Xlint:deprecation", "-Xlint:unchecked", ) @@ -30,7 +32,8 @@ scalacOptions ++= { } else { Seq( "-target:jvm-1.8", - "-encoding", "utf8", + "-encoding", + "utf8", "-deprecation", "-feature", "-unchecked", diff --git a/project/plugins.sbt b/project/plugins.sbt index 2d153e6..e15542d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,3 @@ -addSbtPlugin("com.typesafe.play" % "interplay" % "2.1.2") -addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.0-M5") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.2.1") \ No newline at end of file +addSbtPlugin("com.typesafe.play" % "interplay" % "2.1.2") +addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.0-M5") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.2.1") diff --git a/src/main/scala/play/doc/FileRepository.scala b/src/main/scala/play/doc/FileRepository.scala index b0498dd..98c7d71 100644 --- a/src/main/scala/play/doc/FileRepository.scala +++ b/src/main/scala/play/doc/FileRepository.scala @@ -1,6 +1,8 @@ package play.doc -import java.io.{FileInputStream, File, InputStream} +import java.io.FileInputStream +import java.io.File +import java.io.InputStream import java.util.jar.JarFile import java.util.zip.ZipEntry import scala.collection.JavaConverters._ @@ -59,7 +61,6 @@ trait FileRepository { * @param base The base dir of the file */ class FilesystemRepository(base: File) extends FileRepository { - private def cleanUp[A](loader: InputStream => A) = { is: InputStream => try { loader(is) @@ -82,7 +83,7 @@ class FilesystemRepository(base: File) extends FileRepository { def handleFile[A](path: String)(handler: FileHandle => A) = { getFile(path).map { file => - val is = new FileInputStream(file) + val is = new FileInputStream(file) val handle = FileHandle(file.getName, file.length, is, () => is.close()) handler(handle) } @@ -104,13 +105,12 @@ class FilesystemRepository(base: File) extends FileRepository { * Jar file implementation of the repository */ class JarRepository(jarFile: JarFile, base: Option[String] = None) extends FileRepository { - private val PathSeparator = "/" - private val basePrefix = base.map(_ + PathSeparator).getOrElse("") + private val basePrefix = base.map(_ + PathSeparator).getOrElse("") def getEntry(path: String): Option[(ZipEntry, InputStream)] = { - Option(jarFile.getEntry(basePrefix + path)).flatMap { - entry => Option(jarFile.getInputStream(entry)).map(is => (entry, is)) + Option(jarFile.getEntry(basePrefix + path)).flatMap { entry => + Option(jarFile.getInputStream(entry)).map(is => (entry, is)) } } @@ -119,24 +119,29 @@ class JarRepository(jarFile: JarFile, base: Option[String] = None) extends FileR } def handleFile[A](path: String)(handler: FileHandle => A) = { - getEntry(path).map { case (entry, is) => - val handle = FileHandle(entry.getName.split(PathSeparator).last, entry.getSize, is, () => is.close()) - handler(handle) + getEntry(path).map { + case (entry, is) => + val handle = FileHandle(entry.getName.split(PathSeparator).last, entry.getSize, is, () => is.close()) + handler(handle) } } def findFileWithName(name: String) = { - def startsWith(full: String, part: String) = if (part.isEmpty) true else { - val comparePart = if (full.length == part.length) full else full.take(part.length) - comparePart.equalsIgnoreCase(part) - } - def endsWith(full: String, part: String) = if (part.isEmpty) true else { - val comparePart = if (full.length == part.length) full else full.takeRight(part.length) - comparePart.equalsIgnoreCase(part) - } + def startsWith(full: String, part: String) = + if (part.isEmpty) true + else { + val comparePart = if (full.length == part.length) full else full.take(part.length) + comparePart.equalsIgnoreCase(part) + } + def endsWith(full: String, part: String) = + if (part.isEmpty) true + else { + val comparePart = if (full.length == part.length) full else full.takeRight(part.length) + comparePart.equalsIgnoreCase(part) + } val slashName = PathSeparator + name - val found = jarFile.entries().asScala.map(_.getName).find(n => startsWith(n, basePrefix) && endsWith(n, slashName)) + val found = jarFile.entries().asScala.map(_.getName).find(n => startsWith(n, basePrefix) && endsWith(n, slashName)) found.map(_.substring(basePrefix.length)) } diff --git a/src/main/scala/play/doc/PageIndex.scala b/src/main/scala/play/doc/PageIndex.scala index 209b481..ef1b67c 100644 --- a/src/main/scala/play/doc/PageIndex.scala +++ b/src/main/scala/play/doc/PageIndex.scala @@ -19,20 +19,20 @@ sealed trait TocTree { /** * A table of contents - * + * * @param title The title of this table of contents * @param nodes The nodes in the table of contents * @param descend Whether a table of contents should descend into this table of contents */ case class Toc(name: String, title: String, nodes: List[(String, TocTree)], descend: Boolean = true) extends TocTree { require(nodes.nonEmpty) - - def page = nodes.head._2.page + + def page = nodes.head._2.page } /** * A page (leaf node) pointed to by the table of contents - * + * * @param page The page * @param title The title of the page * @param next Explicitly provided next links. If None, then the index structure are used to generate the next links, @@ -44,12 +44,10 @@ case class TocPage(page: String, title: String, next: Option[List[String]]) exte /** * The page index - * + * * @param toc The table of contents */ class PageIndex(val toc: Toc, path: Option[String] = None) { - - private val byPage: Map[String, Page] = { // First, create a by name index def indexByName(node: TocTree): List[(String, TocTree)] = node match { @@ -60,27 +58,29 @@ class PageIndex(val toc: Toc, path: Option[String] = None) { } val byNameMap = indexByName(toc).toMap - def indexPages(path: Option[String], nav: List[Toc], toc: Toc): List[Page] = { toc.nodes.flatMap { case (_, TocPage(page, title, explicitNext)) => - val nextLinks = explicitNext.map { links => - links.collect(Function.unlift(byNameMap.get)) - }.getOrElse { - findNext(page, nav).toList - } + val nextLinks = explicitNext + .map { links => + links.collect(Function.unlift(byNameMap.get)) + } + .getOrElse { + findNext(page, nav).toList + } List(Page(page, path, title, nav, nextLinks)) - case (pathPart, tocPart: Toc) => indexPages( - path.map(_ + "/" + pathPart).orElse(Some(pathPart)), tocPart :: nav, tocPart - ) + case (pathPart, tocPart: Toc) => + indexPages( + path.map(_ + "/" + pathPart).orElse(Some(pathPart)), + tocPart :: nav, + tocPart + ) } } indexPages(path, List(toc), toc).map(p => p.page -> p).toMap } - - private def findNext(name: String, nav: List[Toc]): Option[TocTree] = { nav match { case Nil => None @@ -100,7 +100,6 @@ class PageIndex(val toc: Toc, path: Option[String] = None) { * Get the page for the given page name */ def get(page: String): Option[Page] = byPage.get(page) - } /** @@ -120,52 +119,57 @@ case class Page(page: String, path: Option[String], title: String, nav: List[Toc } object PageIndex { - def parseFrom(repo: FileRepository, home: String, path: Option[String] = None): Option[PageIndex] = { parseToc(repo, path, "", home) match { case toc: Toc => Some(new PageIndex(toc, path)) - case _ => None + case _ => None } } - private def parseToc(repo: FileRepository, path: Option[String], page: String, title: String, - descend: Boolean = true, next: Option[List[String]] = None): TocTree = { - repo.loadFile(path.fold("index.toc")(_ + "/index.toc"))(IOUtils.toString(_, "utf-8")).fold[TocTree]( - TocPage(page, title, next) - ) { content => - // https://github.com/scala/bug/issues/11125#issuecomment-423375868 - val lines = augmentString(content).lines.toList.map(_.trim).filter(_.nonEmpty) - // Remaining lines are the entries of the contents - val tocNodes = lines.map { entry => - val linkAndTitle :: params = entry.split(";").toList - val (link, title) = { - linkAndTitle.split(":", 2) match { - case Array(p) => p -> p - case Array(p, t) => p -> t + private def parseToc( + repo: FileRepository, + path: Option[String], + page: String, + title: String, + descend: Boolean = true, + next: Option[List[String]] = None + ): TocTree = { + repo + .loadFile(path.fold("index.toc")(_ + "/index.toc"))(IOUtils.toString(_, "utf-8")) + .fold[TocTree]( + TocPage(page, title, next) + ) { content => + // https://github.com/scala/bug/issues/11125#issuecomment-423375868 + val lines = augmentString(content).lines.toList.map(_.trim).filter(_.nonEmpty) + // Remaining lines are the entries of the contents + val tocNodes = lines.map { entry => + val linkAndTitle :: params = entry.split(";").toList + val (link, title) = { + linkAndTitle.split(":", 2) match { + case Array(p) => p -> p + case Array(p, t) => p -> t + } } - } - val parsedParams = params.map { param => - param.split("=", 2) match { - case Array(k) => k -> k - case Array(k, v) => k -> v + val parsedParams = params.map { param => + param.split("=", 2) match { + case Array(k) => k -> k + case Array(k, v) => k -> v + } + }.toMap + + val next = parsedParams.get("next").map { n => + n.split(",").toList } - }.toMap - val next = parsedParams.get("next").map { n => - n.split(",").toList - } + val (relPath, descend) = if (link.startsWith("!")) { + link.drop(1) -> false + } else { + link -> true + } - val (relPath, descend) = if (link.startsWith("!")) { - link.drop(1) -> false - } else { - link -> true + relPath -> parseToc(repo, path.map(_ + "/" + relPath).orElse(Some(relPath)), relPath, title, descend, next) } - - relPath -> parseToc(repo, path.map(_ + "/" + relPath).orElse(Some(relPath)), relPath, title, descend, next) - + Toc(page, title, tocNodes, descend) } - Toc(page, title, tocNodes, descend) - } } - -} \ No newline at end of file +} diff --git a/src/main/scala/play/doc/PlayDoc.scala b/src/main/scala/play/doc/PlayDoc.scala index 05fe140..a688e67 100644 --- a/src/main/scala/play/doc/PlayDoc.scala +++ b/src/main/scala/play/doc/PlayDoc.scala @@ -1,9 +1,12 @@ package play.doc -import java.io.{InputStream, File} -import java.util.{Collections, Arrays} +import java.io.InputStream +import java.io.File +import java.util.Collections +import java.util.Arrays import org.pegdown._ -import org.pegdown.plugins.{ToHtmlSerializerPlugin, PegDownPlugins} +import org.pegdown.plugins.ToHtmlSerializerPlugin +import org.pegdown.plugins.PegDownPlugins import org.pegdown.ast._ import org.apache.commons.io.IOUtils import scala.collection.JavaConverters._ @@ -30,22 +33,47 @@ case class RenderedPage(html: String, sidebarHtml: Option[String], path: String, * @param templates The templates to render snippets. * @param pageExtension The extension to add to rendered pages - used for rendering links. */ -class PlayDoc(markdownRepository: FileRepository, codeRepository: FileRepository, resources: String, - playVersion: String, val pageIndex: Option[PageIndex], templates: PlayDocTemplates, - pageExtension: Option[String]) { - +class PlayDoc( + markdownRepository: FileRepository, + codeRepository: FileRepository, + resources: String, + playVersion: String, + val pageIndex: Option[PageIndex], + templates: PlayDocTemplates, + pageExtension: Option[String] +) { @deprecated("Use the primary constructor", "1.3.0") - def this(markdownRepository: FileRepository, codeRepository: FileRepository, resources: String, - playVersion: String, pageIndex: Option[PageIndex], nextText: String) = this(markdownRepository, codeRepository, - resources, playVersion, pageIndex, new TranslatedPlayDocTemplates(nextText), None) + def this( + markdownRepository: FileRepository, + codeRepository: FileRepository, + resources: String, + playVersion: String, + pageIndex: Option[PageIndex], + nextText: String + ) = + this( + markdownRepository, + codeRepository, + resources, + playVersion, + pageIndex, + new TranslatedPlayDocTemplates(nextText), + None + ) @deprecated("Use the primary constructor", "1.3.0") - def this(markdownRepository: FileRepository, codeRepository: FileRepository, resources: String, - playVersion: String) = this(markdownRepository, codeRepository, resources, playVersion, None, PlayDocTemplates, None) + def this(markdownRepository: FileRepository, codeRepository: FileRepository, resources: String, playVersion: String) = + this(markdownRepository, codeRepository, resources, playVersion, None, PlayDocTemplates, None) @deprecated("Use the primary constructor", "1.4.0") - def this(markdownRepository: FileRepository, codeRepository: FileRepository, resources: String, - playVersion: String, pageIndex: Option[PageIndex], templates: PlayDocTemplates) = + def this( + markdownRepository: FileRepository, + codeRepository: FileRepository, + resources: String, + playVersion: String, + pageIndex: Option[PageIndex], + templates: PlayDocTemplates + ) = this(markdownRepository, codeRepository, resources, playVersion, pageIndex, templates, None) val PlayVersionVariableName = "%PLAY_VERSION%" @@ -69,8 +97,7 @@ class PlayDoc(markdownRepository: FileRepository, codeRepository: FileRepository } { index => index.get(pageName).flatMap { page => withRenderer(page.path, page.nav.headOption) { renderer => - - val pagePath = page.fullPath + ".md" + val pagePath = page.fullPath + ".md" val renderedPage = markdownRepository.loadFile(pagePath)(inputStreamToString).map(renderer) renderedPage.map { html => @@ -93,24 +120,26 @@ class PlayDoc(markdownRepository: FileRepository, codeRepository: FileRepository def collectPagesInOrder(node: TocTree): List[String] = { node match { case TocPage(page, _, _) => List(page) - case toc: Toc => toc.nodes.flatMap(n => collectPagesInOrder(n._2)) + case toc: Toc => toc.nodes.flatMap(n => collectPagesInOrder(n._2)) } } val pages = collectPagesInOrder(idx.toc) pages.flatMap { pageName => - idx.get(pageName).flatMap { page => - withRenderer(page.path, page.nav.headOption, singlePage = singlePage) { renderer => - val pagePath = page.fullPath + ".md" - markdownRepository.loadFile(pagePath)(inputStreamToString).map(renderer) + idx + .get(pageName) + .flatMap { page => + withRenderer(page.path, page.nav.headOption, singlePage = singlePage) { renderer => + val pagePath = page.fullPath + ".md" + markdownRepository.loadFile(pagePath)(inputStreamToString).map(renderer) + } } - }.map { pageName -> _ } + .map { pageName -> _ } } case None => throw new IllegalStateException("Can only render all pages if there's a page index") } } - /** * Render a Play documentation page. * @@ -118,10 +147,8 @@ class PlayDoc(markdownRepository: FileRepository, codeRepository: FileRepository * @return If found a tuple of the rendered page and the rendered sidebar, if the sidebar was found. */ private def renderOldPage(page: String): Option[RenderedPage] = { - // Find the markdown file markdownRepository.findFileWithName(page + ".md").flatMap { pagePath => - val file = new File(pagePath) // Work out the relative path for the file val relativePath = Option(file.getParentFile).map(_.getPath) @@ -149,7 +176,12 @@ class PlayDoc(markdownRepository: FileRepository, codeRepository: FileRepository // Render both the markdown and the sidebar render(pagePath).map { markdown => - RenderedPage(markdown, findSideBar(Option(file.getParentFile)), pagePath, findBreadcrumbs(Option(file.getParentFile))) + RenderedPage( + markdown, + findSideBar(Option(file.getParentFile)), + pagePath, + findBreadcrumbs(Option(file.getParentFile)) + ) } } } @@ -168,21 +200,23 @@ class PlayDoc(markdownRepository: FileRepository, codeRepository: FileRepository } } - private def withRenderer[T](relativePath: Option[String], toc: Option[Toc], - headerIds: Boolean = true, - singlePage: Boolean = false)(block: (String => String) => T): T = { - + private def withRenderer[T]( + relativePath: Option[String], + toc: Option[Toc], + headerIds: Boolean = true, + singlePage: Boolean = false + )(block: (String => String) => T): T = { // Link renderer val link: (String => (String, String)) = { case link if link.contains("|") => val parts = link.split('|') - val href = if (singlePage) "#" + parts.tail.head else addExtension(parts.tail.head) + val href = if (singlePage) "#" + parts.tail.head else addExtension(parts.tail.head) (href, parts.head) case image if image.endsWith(".png") => val link = image match { - case full if full.startsWith("http://") => full + case full if full.startsWith("http://") => full case absolute if absolute.startsWith("/") => resources + absolute - case relative => resources + "/" + relativePath.map(_ + "/").getOrElse("") + relative + case relative => resources + "/" + relativePath.map(_ + "/").getOrElse("") + relative } (link, """""") case link => @@ -197,80 +231,87 @@ class PlayDoc(markdownRepository: FileRepository, codeRepository: FileRepository } // Markdown parser - val processor = new PegDownProcessor(Extensions.ALL & ~Extensions.ANCHORLINKS, PegDownPlugins.builder() - .withPlugin(classOf[CodeReferenceParser]) - .withPlugin(classOf[VariableParser], PlayVersionVariableName) - .withPlugin(classOf[TocParser]) - .build) + val processor = new PegDownProcessor( + Extensions.ALL & ~Extensions.ANCHORLINKS, + PegDownPlugins + .builder() + .withPlugin(classOf[CodeReferenceParser]) + .withPlugin(classOf[VariableParser], PlayVersionVariableName) + .withPlugin(classOf[TocParser]) + .build + ) // ToHtmlSerializer's are stateful and so not reusable - def htmlSerializer = new ToHtmlSerializer(links, - Collections.singletonMap(VerbatimSerializer.DEFAULT, - new VerbatimSerializerWrapper(PrettifyVerbatimSerializer)), - Arrays.asList[ToHtmlSerializerPlugin]( - new CodeReferenceSerializer(relativePath.map(_ + "/").getOrElse("")), - new VariableSerializer(Map(PlayVersionVariableName -> FastEncoder.encode(playVersion))), - new TocSerializer(toc) - ) - ){ - var headingsSeen = Map.empty[String, Int] - def headingToAnchor(heading: String) = { - val anchor = FastEncoder.encode(heading.replace(' ', '-')) - headingsSeen.get(anchor).fold { - headingsSeen += anchor -> 1 - anchor - } { seen => - headingsSeen += anchor -> (seen + 1) - anchor + seen + def htmlSerializer = + new ToHtmlSerializer( + links, + Collections.singletonMap(VerbatimSerializer.DEFAULT, new VerbatimSerializerWrapper(PrettifyVerbatimSerializer)), + Arrays.asList[ToHtmlSerializerPlugin]( + new CodeReferenceSerializer(relativePath.map(_ + "/").getOrElse("")), + new VariableSerializer(Map(PlayVersionVariableName -> FastEncoder.encode(playVersion))), + new TocSerializer(toc) + ) + ) { + var headingsSeen = Map.empty[String, Int] + def headingToAnchor(heading: String) = { + val anchor = FastEncoder.encode(heading.replace(' ', '-')) + headingsSeen + .get(anchor) + .fold { + headingsSeen += anchor -> 1 + anchor + } { seen => + headingsSeen += anchor -> (seen + 1) + anchor + seen + } } - } - override def visit(node: CodeNode) = { - super.visit(new CodeNode(node.getText.replace(PlayVersionVariableName, playVersion))) - } + override def visit(node: CodeNode) = { + super.visit(new CodeNode(node.getText.replace(PlayVersionVariableName, playVersion))) + } - override def visit(node: HeaderNode) = { - // Put an id on header nodes - printer.print(" Seq(t.getText) - case other => collectTextNodes(other) + import scala.collection.JavaConverters._ + def collectTextNodes(node: Node): Seq[String] = { + node.getChildren.asScala.toSeq.flatMap { + case t: TextNode => Seq(t.getText) + case other => collectTextNodes(other) + } } + val title = collectTextNodes(node).mkString + val anchorId = headingToAnchor(title) + + printer + .print(anchorId) + .print("\"") + + printer.print(">") + + // Add section marker + printer + .print("") + .print("§") + .print("") + } else { + printer.print(">") } - val title = collectTextNodes(node).mkString - val anchorId = headingToAnchor(title) - - printer.print(anchorId) - .print("\"") - - printer.print(">") - // Add section marker - printer.print("") - .print("§") - .print("") + visitChildren(node) + printer.print("") } - else { - printer.print(">") - } - - - visitChildren(node) - printer.print("") } - } - def render(markdown: String): String = { val astRoot = processor.parseMarkdown(markdown.toCharArray) htmlSerializer.toHtml(astRoot) @@ -280,13 +321,12 @@ class PlayDoc(markdownRepository: FileRepository, codeRepository: FileRepository } // Directives to insert code, skip code and replace code - private val Insert = """.*###insert: (.*?)(?:###.*)?""".r - private val SkipN = """.*###skip:\s*(\d+).*""".r - private val Skip = """.*###skip.*""".r + private val Insert = """.*###insert: (.*?)(?:###.*)?""".r + private val SkipN = """.*###skip:\s*(\d+).*""".r + private val Skip = """.*###skip.*""".r private val ReplaceNext = """.*###replace: (.*?)(?:###.*)?""".r private class CodeReferenceSerializer(pagePath: String) extends ToHtmlSerializerPlugin { - // Most files will be accessed multiple times from the same markdown file, no point in opening them many times // so memoize them. This cache is only per file rendered, so does not need to be thread safe. val repo = Memoize[String, Option[Seq[String]]] { path => @@ -297,11 +337,10 @@ class PlayDoc(markdownRepository: FileRepository, codeRepository: FileRepository def visit(node: Node, visitor: Visitor, printer: Printer) = node match { case code: CodeReferenceNode => { - // Label is after the #, or if no #, then is the link label val (source, label) = code.getSource.split("#", 2) match { case Array(source, label) => (source, label) - case Array(source) => (source, code.getLabel) + case Array(source) => (source, code.getLabel) } // The file is either relative to current page or absolute, under the root @@ -315,7 +354,7 @@ class PlayDoc(markdownRepository: FileRepository, codeRepository: FileRepository val labelPattern = ("""\s*#\Q""" + label + """\E(\s|\z)""").r sourceFile.flatMap { sourceCode => val notLabel = (s: String) => labelPattern.findFirstIn(s).isEmpty - val segment = sourceCode dropWhile (notLabel) drop (1) takeWhile (notLabel) + val segment = sourceCode.dropWhile(notLabel).drop(1).takeWhile(notLabel) if (segment.isEmpty) { None } else { @@ -326,58 +365,73 @@ class PlayDoc(markdownRepository: FileRepository, codeRepository: FileRepository sourceFile } - segment.map { segment => - - // Calculate the indent, which is equal to the smallest indent of any line, excluding lines that only consist - // of space characters - val indent = segment map { line => - if (!line.exists(_ != ' ')) None else Some(line.indexWhere(_ != ' ')) - } reduce ((i1, i2) => (i1, i2) match { - case (None, None) => None - case (i, None) => i - case (None, i) => i - case (Some(i1), Some(i2)) => Some(math.min(i1, i2)) - }) getOrElse (0) - - // Process directives in segment - case class State(buffer: StringBuilder = new StringBuilder, skip: Option[Int] = None) { - def dropIndentAndAppendLine(s: String): State = { - buffer.append(s.drop(indent)).append("\n") - this - } - def appendLine(s: String): State = { - buffer.append(s).append("\n") - this - } - } - val compiledSegment = (segment.foldLeft(State()) { (state, line) => - state.skip match { - case Some(n) if (n > 1) => state.copy(skip = Some(n - 1)) - case Some(n) => state.copy(skip = None) - case None => line match { - case Insert(code) => state.appendLine(code) - case SkipN(n) => state.copy(skip = Some(n.toInt)) - case Skip() => state - case ReplaceNext(code) => state.appendLine(code).copy(skip = Some(1)) - case _ => state.dropIndentAndAppendLine(line) + segment + .map { segment => + // Calculate the indent, which is equal to the smallest indent of any line, excluding lines that only consist + // of space characters + val indent = segment + .map { line => + if (!line.exists(_ != ' ')) None else Some(line.indexWhere(_ != ' ')) + } + .reduce( + (i1, i2) => + (i1, i2) match { + case (None, None) => None + case (i, None) => i + case (None, i) => i + case (Some(i1), Some(i2)) => Some(math.min(i1, i2)) + } + ) + .getOrElse(0) + + // Process directives in segment + case class State(buffer: StringBuilder = new StringBuilder, skip: Option[Int] = None) { + def dropIndentAndAppendLine(s: String): State = { + buffer.append(s.drop(indent)).append("\n") + this + } + def appendLine(s: String): State = { + buffer.append(s).append("\n") + this } } - }).buffer /* Drop last newline */ .dropRight(1).toString() - - // Guess the type of the file - val fileType = source.split("\\.") match { - case withExtension if (withExtension.length > 1) => Some(withExtension.last) - case _ => None - } + val compiledSegment = (segment + .foldLeft(State()) { (state, line) => + state.skip match { + case Some(n) if (n > 1) => state.copy(skip = Some(n - 1)) + case Some(n) => state.copy(skip = None) + case None => + line match { + case Insert(code) => state.appendLine(code) + case SkipN(n) => state.copy(skip = Some(n.toInt)) + case Skip() => state + case ReplaceNext(code) => state.appendLine(code).copy(skip = Some(1)) + case _ => state.dropIndentAndAppendLine(line) + } + } + }) + .buffer /* Drop last newline */ + .dropRight(1) + .toString() + + // Guess the type of the file + val fileType = source.split("\\.") match { + case withExtension if (withExtension.length > 1) => Some(withExtension.last) + case _ => None + } - // And visit it - fileType.map(t => new VerbatimNode(compiledSegment, t)).getOrElse(new VerbatimNode(compiledSegment)).accept(visitor) + // And visit it + fileType + .map(t => new VerbatimNode(compiledSegment, t)) + .getOrElse(new VerbatimNode(compiledSegment)) + .accept(visitor) - true - } getOrElse { - printer.print("Unable to find label " + label + " in source file " + source) - true - } + true + } + .getOrElse { + printer.print("Unable to find label " + label + " in source file " + source) + true + } } case _ => false } @@ -394,7 +448,7 @@ class PlayDoc(markdownRepository: FileRepository, codeRepository: FileRepository } private class VerbatimSerializerWrapper(wrapped: VerbatimSerializer) extends VerbatimSerializer { - def serialize(node: VerbatimNode, printer: Printer): Unit = { + def serialize(node: VerbatimNode, printer: Printer): Unit = { val text = node.getText.replace(PlayVersionVariableName, playVersion) wrapped.serialize(new VerbatimNode(text, node.getType), printer) } @@ -421,8 +475,7 @@ private class Memoize[-T, +R](f: T => R) extends (T => R) { def apply(x: T): R = { if (vals.contains(x)) { vals(x) - } - else { + } else { val y = f(x) vals + ((x, y)) y @@ -433,4 +486,3 @@ private class Memoize[-T, +R](f: T => R) extends (T => R) { private object Memoize { def apply[T, R](f: T => R) = new Memoize(f) } - diff --git a/src/main/scala/play/doc/PlayDocTemplates.scala b/src/main/scala/play/doc/PlayDocTemplates.scala index 1f601c5..8968ba5 100644 --- a/src/main/scala/play/doc/PlayDocTemplates.scala +++ b/src/main/scala/play/doc/PlayDocTemplates.scala @@ -1,16 +1,15 @@ package play.doc /** - * Templates for rendering Play documentation snippets. - */ + * Templates for rendering Play documentation snippets. + */ trait PlayDocTemplates { - /** - * Render the next link. - * - * @param toc The table of contents. - * @return The next link. - */ + * Render the next link. + * + * @param toc The table of contents. + * @return The next link. + */ def nextLink(toc: TocTree): String /** @@ -22,11 +21,11 @@ trait PlayDocTemplates { def nextLinks(toc: List[TocTree]) = toc.map(nextLink).mkString("") /** - * Render the sidebar. - * - * @param hierarchy The hierarchy to render in the sidebar. - * @return The sidebar. - */ + * Render the sidebar. + * + * @param hierarchy The hierarchy to render in the sidebar. + * @return The sidebar. + */ def sidebar(hierarchy: List[Toc]): String /** @@ -37,19 +36,19 @@ trait PlayDocTemplates { def breadcrumbs(hierarchy: List[Toc]): String /** - * Render a table of contents. - * - * @param toc The table of contents to render. - * @return The table of contents. - */ + * Render a table of contents. + * + * @param toc The table of contents to render. + * @return The table of contents. + */ def toc(toc: Toc): String } class TranslatedPlayDocTemplates(nextText: String) extends PlayDocTemplates { - override def nextLink(toc: TocTree): String = play.doc.html.nextLink(toc, nextText).body - override def sidebar(heirarchy: List[Toc]): String = play.doc.html.sidebar(heirarchy).body + override def nextLink(toc: TocTree): String = play.doc.html.nextLink(toc, nextText).body + override def sidebar(heirarchy: List[Toc]): String = play.doc.html.sidebar(heirarchy).body override def breadcrumbs(hierarchy: List[Toc]): String = play.doc.html.breadcrumbs(hierarchy).body - override def toc(toc: Toc): String = play.doc.html.toc(toc).body + override def toc(toc: Toc): String = play.doc.html.toc(toc).body } object PlayDocTemplates extends TranslatedPlayDocTemplates("Next") diff --git a/src/main/scala/play/doc/PrettifyVerbatimSerializer.scala b/src/main/scala/play/doc/PrettifyVerbatimSerializer.scala index 6e0bac0..2e2ccbb 100644 --- a/src/main/scala/play/doc/PrettifyVerbatimSerializer.scala +++ b/src/main/scala/play/doc/PrettifyVerbatimSerializer.scala @@ -1,6 +1,7 @@ package play.doc -import org.pegdown.{Printer, VerbatimSerializer} +import org.pegdown.Printer +import org.pegdown.VerbatimSerializer import org.pegdown.ast.VerbatimNode import org.parboiled.common.StringUtils @@ -8,14 +9,13 @@ import org.parboiled.common.StringUtils * Prints verbatim nodes in such a format that Google Code Prettify will work with them */ object PrettifyVerbatimSerializer extends VerbatimSerializer { - def serialize(node: VerbatimNode, printer: Printer) = { - def printAttribute(name: String, value: String): Unit = { printer.print(' ').print(name).print('=').print('"').print(value).print('"') } - printer.println() + printer + .println() .print(" val result = (handle.name, handle.size, IOUtils.toString(handle.is, "utf-8")) @@ -16,8 +15,8 @@ class FileRepositorySpec extends Specification { } "FilesystemRepository" should { - val repo = new FilesystemRepository(fileFromClasspath("file-placeholder").getParentFile) - def loadFile(path: String) = loadFileFromRepo(repo, path) + val repo = new FilesystemRepository(fileFromClasspath("file-placeholder").getParentFile) + def loadFile(path: String) = loadFileFromRepo(repo, path) def handleFile(path: String) = handleFileFromRepo(repo, path) import repo.findFileWithName @@ -64,8 +63,8 @@ class FileRepositorySpec extends Specification { } } - def loadFile(path: String) = withJarRepo(None)(loadFileFromRepo(_, path)) - def handleFile(path: String) = withJarRepo(None)(handleFileFromRepo(_, path)) + def loadFile(path: String) = withJarRepo(None)(loadFileFromRepo(_, path)) + def handleFile(path: String) = withJarRepo(None)(handleFileFromRepo(_, path)) def findFileWithName(name: String) = withJarRepo(None)(_.findFileWithName(name)) "load a file" in { diff --git a/src/test/scala/play/doc/PageIndexSpec.scala b/src/test/scala/play/doc/PageIndexSpec.scala index bfea266..35a9edd 100644 --- a/src/test/scala/play/doc/PageIndexSpec.scala +++ b/src/test/scala/play/doc/PageIndexSpec.scala @@ -5,14 +5,12 @@ import java.io.File import org.specs2.mutable.Specification class PageIndexSpec extends Specification { - def fileFromClasspath(name: String) = new File(Thread.currentThread.getContextClassLoader.getResource(name).toURI) - val repo = new FilesystemRepository(fileFromClasspath("file-placeholder").getParentFile) - def maybeIndex = PageIndex.parseFrom(repo, "Home", Some("example")) - def index = maybeIndex.getOrElse(new PageIndex(Toc("", "", Nil))) + val repo = new FilesystemRepository(fileFromClasspath("file-placeholder").getParentFile) + def maybeIndex = PageIndex.parseFrom(repo, "Home", Some("example")) + def index = maybeIndex.getOrElse(new PageIndex(Toc("", "", Nil))) "Page Index " should { - "parse the index" in { maybeIndex must beSome(anInstanceOf[PageIndex]) } @@ -51,7 +49,6 @@ class PageIndexSpec extends Specification { "non existent" in { index.get("NotExists") must beNone } - } "provide a table of contents" in { @@ -63,10 +60,13 @@ class PageIndexSpec extends Specification { toc.nodes.collectFirst { case ("sub", n) => n } must beSome.like { case toc: Toc => toc.title must_== "Sub Section" - toc.nodes.collectFirst { case ("SubFoo1", n) => n } must beSome(TocPage("SubFoo1", "Sub Foo Page 1", None)) - toc.nodes.collectFirst { case ("SubFoo2", n) => n } must beSome(TocPage("SubFoo2", "Sub Foo Page 2", None)) + toc.nodes.collectFirst { case ("SubFoo1", n) => n } must beSome( + TocPage("SubFoo1", "Sub Foo Page 1", None) + ) + toc.nodes.collectFirst { case ("SubFoo2", n) => n } must beSome( + TocPage("SubFoo2", "Sub Foo Page 2", None) + ) } - } } @@ -93,5 +93,4 @@ class PageIndexSpec extends Specification { index.get("SubFoo2").flatMap(_.next) must beNone } } - } diff --git a/src/test/scala/play/doc/PlayDocSpec.scala b/src/test/scala/play/doc/PlayDocSpec.scala index d21ea8a..597cb45 100644 --- a/src/test/scala/play/doc/PlayDocSpec.scala +++ b/src/test/scala/play/doc/PlayDocSpec.scala @@ -4,12 +4,12 @@ import org.specs2.mutable._ import java.io.File class PlayDocSpec extends Specification { - def fileFromClasspath(name: String) = new File(Thread.currentThread.getContextClassLoader.getResource(name).toURI) - val repo = new FilesystemRepository(fileFromClasspath("file-placeholder").getParentFile) - val oldRenderer = new PlayDoc(repo, repo, "resources", "2.1.3", None, "Next") + val repo = new FilesystemRepository(fileFromClasspath("file-placeholder").getParentFile) + val oldRenderer = new PlayDoc(repo, repo, "resources", "2.1.3", None, "Next") - val renderer = new PlayDoc(repo, repo, "resources", "2.4.0", PageIndex.parseFrom(repo, "Home", Some("example")), "Next") + val renderer = + new PlayDoc(repo, repo, "resources", "2.4.0", PageIndex.parseFrom(repo, "Home", Some("example")), "Next") "code snippet handling" should { def test(label: String, rendered: String, file: String = "code/sample.txt") = { @@ -17,16 +17,16 @@ class PlayDocSpec extends Specification { s"""
$rendered
""" } - def failTest(label:String) = { + def failTest(label: String) = { oldRenderer.render("@[" + label + "](code/sample.txt)") must_== - """Unable to find label """ + label + """ in source file """ + "code/sample.txt" + """Unable to find label """ + label + """ in source file """ + "code/sample.txt" } "allow extracting code snippets" in test("simple", "Snippet") "allow extracting code snippets using string that exists as substring elsewhere" in test("one", "One") "allow extracting code snippets using string as full string" in test("onetwothree", "One Two Three") // paired with previous test - "fail on substring code snippets using string as trailing" in failTest("three") // paired with previous test + "fail on substring code snippets using string as trailing" in failTest("three") // paired with previous test "fail on substring with no full string match" in failTest("leading") "should match on full string" in test("leading-following", "Leading Following") // paired with test for exception @@ -79,7 +79,9 @@ class PlayDocSpec extends Specification { } maybeBreadcrumbs must beSome.like { case breadcrumbs => - breadcrumbs must contain("Home") + breadcrumbs must contain( + "Home" + ) } path must_== "example/docs/Foo.md" } @@ -92,12 +94,11 @@ class PlayDocSpec extends Specification { oldRenderer.render("The current Play version is %PLAY_VERSION%") must_== "

The current Play version is 2.1.3

" } "work in verbatim blocks" in { - oldRenderer.render( - """ - | Here is some code: - | - | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "%PLAY_VERSION%") - | + oldRenderer.render(""" + | Here is some code: + | + | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "%PLAY_VERSION%") + | """.stripMargin) must contain("% "2.1.3")") } "work in code blocks" in { @@ -123,5 +124,4 @@ class PlayDocSpec extends Specification { """

§Hello World

""" } } - } From fb9429c5f6ba53b2f9d9c4a9e930a0bb3146294c Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 19 Nov 2019 14:00:11 -0500 Subject: [PATCH 5/5] Update twirl to version 1.5.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index e15542d..bee6848 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,3 @@ addSbtPlugin("com.typesafe.play" % "interplay" % "2.1.2") -addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.0-M5") +addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.0") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.2.1")