From 51a22ba302184937f4b7930cc7db60b2ab353e71 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 9 Jan 2017 15:27:08 +0100 Subject: Implement `{% include 'template' %}` feature --- doc-tool/resources/_includes/header.html | 1 + .../tools/dottydoc/staticsite/LiquidTemplate.scala | 46 ++++++++++++++++++++++ .../src/dotty/tools/dottydoc/staticsite/Page.scala | 11 ++---- .../tools/dottydoc/staticsite/ResourceFinder.scala | 16 ++++++++ .../src/dotty/tools/dottydoc/staticsite/Site.scala | 46 ++++++++++++---------- .../tools/dottydoc/staticsite/PageTests.scala | 14 +++++-- .../tools/dottydoc/staticsite/SiteTests.scala | 32 ++++++++++----- 7 files changed, 125 insertions(+), 41 deletions(-) create mode 100644 doc-tool/resources/_includes/header.html create mode 100644 doc-tool/src/dotty/tools/dottydoc/staticsite/LiquidTemplate.scala create mode 100644 doc-tool/src/dotty/tools/dottydoc/staticsite/ResourceFinder.scala diff --git a/doc-tool/resources/_includes/header.html b/doc-tool/resources/_includes/header.html new file mode 100644 index 000000000..219cdf7f9 --- /dev/null +++ b/doc-tool/resources/_includes/header.html @@ -0,0 +1 @@ +

Some header

diff --git a/doc-tool/src/dotty/tools/dottydoc/staticsite/LiquidTemplate.scala b/doc-tool/src/dotty/tools/dottydoc/staticsite/LiquidTemplate.scala new file mode 100644 index 000000000..18ef9ef2a --- /dev/null +++ b/doc-tool/src/dotty/tools/dottydoc/staticsite/LiquidTemplate.scala @@ -0,0 +1,46 @@ +package dotty.tools +package dottydoc +package staticsite + +import dotc.config.Printers.dottydoc + +case class LiquidTemplate(contents: String) extends ResourceFinder { + import scala.collection.JavaConverters._ + import liqp.{ Template, TemplateContext } + import liqp.nodes.LNode + import liqp.tags.Tag + + def render(params: Map[String, AnyRef], includes: Map[String, String]): String = { + Template.parse(contents).`with`(ResourceInclude(params, includes)).render(params.asJava) + } + + private case class ResourceInclude(params: Map[String, AnyRef], includes: Map[String, String]) + extends Tag("include") { + val DefaultExtension = ".html" + + private def renderTemplate(template: String) = "dude" + + override def render(ctx: TemplateContext, nodes: LNode*): AnyRef = { + val origInclude = asString(nodes(0).render(ctx)) + val incResource = origInclude match { + case fileWithExt if fileWithExt.indexOf('.') > 0 => fileWithExt + case file => file + DefaultExtension + } + + includes + .get(incResource) + .map { template => + val additionalParams = + // include has `with` clause: + if (nodes.length > 1) params + (origInclude -> nodes(1).render(ctx)) + else params + + Template.parse(template, ctx.flavor).render(additionalParams.asJava) + } + .getOrElse { + /*dottydoc.*/println(s"couldn't find include file '$origInclude'") + "" + } + } + } +} diff --git a/doc-tool/src/dotty/tools/dottydoc/staticsite/Page.scala b/doc-tool/src/dotty/tools/dottydoc/staticsite/Page.scala index 0293c3367..c8148c627 100644 --- a/doc-tool/src/dotty/tools/dottydoc/staticsite/Page.scala +++ b/doc-tool/src/dotty/tools/dottydoc/staticsite/Page.scala @@ -9,7 +9,6 @@ import dotc.config.Printers.dottydoc import com.vladsch.flexmark.html.HtmlRenderer import com.vladsch.flexmark.parser.Parser import com.vladsch.flexmark.ext.front.matter.AbstractYamlFrontMatterVisitor -import liqp.{ Template => LiquidTemplate } import _root_.java.util.{ Map => JMap } case class IllegalFrontMatter(message: String) extends Exception(message) @@ -17,7 +16,7 @@ case class IllegalFrontMatter(message: String) extends Exception(message) trait Page { import scala.collection.JavaConverters._ - + def includes: Map[String, String] def pageContent: String def params: Map[String, AnyRef] @@ -61,9 +60,7 @@ trait Page { // make accessible via "{{ page.title }}" in templates val page = Map("page" -> _yaml.asJava) - _html = LiquidTemplate - .parse(withoutYaml) - .render((params ++ page).asJava) + _html = LiquidTemplate(withoutYaml).render(params ++ page, includes) } /** Takes "page" from `params` map in case this is a second expansion, and @@ -81,11 +78,11 @@ trait Page { .getOrElse(newYaml) } -class HtmlPage(fileContents: => String, val params: Map[String, AnyRef]) extends Page { +class HtmlPage(fileContents: => String, val params: Map[String, AnyRef], val includes: Map[String, String]) extends Page { lazy val pageContent = fileContents } -class MarkdownPage(fileContents: => String, val params: Map[String, AnyRef]) extends Page { +class MarkdownPage(fileContents: => String, val params: Map[String, AnyRef], val includes: Map[String, String]) extends Page { lazy val pageContent = fileContents override protected[this] def initFields()(implicit ctx: Context) = { diff --git a/doc-tool/src/dotty/tools/dottydoc/staticsite/ResourceFinder.scala b/doc-tool/src/dotty/tools/dottydoc/staticsite/ResourceFinder.scala new file mode 100644 index 000000000..66d31fa18 --- /dev/null +++ b/doc-tool/src/dotty/tools/dottydoc/staticsite/ResourceFinder.scala @@ -0,0 +1,16 @@ +package dotty.tools +package dottydoc +package staticsite + +trait ResourceFinder { + /** If, for some reason, the supplied default files cannot be found - this + * exception will be thrown in `layouts`. + */ + final case class ResourceNotFoundException(message: String) extends Exception(message) + + protected def getResource(r: String): String = + Option(getClass.getResourceAsStream(r)).map(scala.io.Source.fromInputStream) + .map(_.mkString) + .getOrElse(throw ResourceNotFoundException(r)) + +} diff --git a/doc-tool/src/dotty/tools/dottydoc/staticsite/Site.scala b/doc-tool/src/dotty/tools/dottydoc/staticsite/Site.scala index ac609e6b6..b1962735f 100644 --- a/doc-tool/src/dotty/tools/dottydoc/staticsite/Site.scala +++ b/doc-tool/src/dotty/tools/dottydoc/staticsite/Site.scala @@ -7,12 +7,7 @@ import dotc.config.Printers.dottydoc import dotc.core.Contexts.Context import scala.io.Source -class Site(val root: JFile) { - - /** If, for some reason, the supplied default files cannot be found - this - * exception will be thrown in `layouts`. - */ - final case class ResourceNotFoundException(message: String) extends Exception(message) +class Site(val root: JFile) extends ResourceFinder { /** Files that define a layout then referred to by `layout: filename-no-ext` * in yaml front-matter. @@ -24,19 +19,10 @@ class Site(val root: JFile) { * defaults, the user-defined one will take precedence. */ val layouts: Map[String, String] = { - def collectLayouts(dir: JFile): Map[String, String] = - dir - .listFiles - .filter(f => f.getName.endsWith(".md") || f.getName.endsWith(".html")) - .map { f => - (f.getName.substring(0, f.getName.lastIndexOf('.')), Source.fromFile(f).mkString) - } - .toMap - val userDefinedLayouts = root .listFiles.find(d => d.getName == "_layouts" && d.isDirectory) - .map(collectLayouts) + .map(collectFiles(_, f => f.endsWith(".md") || f.endsWith(".html"))) .getOrElse(Map.empty) val defaultLayouts: Map[String, String] = Map( @@ -47,10 +33,28 @@ class Site(val root: JFile) { defaultLayouts ++ userDefinedLayouts } - private def getResource(r: String): String = - Option(getClass.getResourceAsStream(r)).map(scala.io.Source.fromInputStream) - .map(_.mkString) - .getOrElse(throw ResourceNotFoundException(r)) + val includes: Map[String, String] = { + val userDefinedIncludes = + root + .listFiles.find(d => d.getName == "_includes" && d.isDirectory) + .map(collectFiles(_, f => f.endsWith(".md") || f.endsWith(".html"))) + .getOrElse(Map.empty) + + val defaultIncludes: Map[String, String] = Map( + "header.html" -> "/_includes/header.html" + ).mapValues(getResource) + + defaultIncludes ++ userDefinedIncludes + } + + private def collectFiles(dir: JFile, includes: String => Boolean): Map[String, String] = + dir + .listFiles + .filter(f => includes(f.getName)) + .map { f => + (f.getName.substring(0, f.getName.lastIndexOf('.')), Source.fromFile(f).mkString) + } + .toMap def render(page: Page, params: Map[String, AnyRef])(implicit ctx: Context): String = { page.yaml.get("layout").flatMap(layouts.get(_)) match { @@ -58,7 +62,7 @@ class Site(val root: JFile) { page.html case Some(layout) => val newParams = Map("content" -> page.html) ++ params ++ Map("page" -> page.yaml) - val expandedTemplate = new HtmlPage(layout, newParams) + val expandedTemplate = new HtmlPage(layout, newParams, includes) render(expandedTemplate, params) } } diff --git a/doc-tool/test/dotty/tools/dottydoc/staticsite/PageTests.scala b/doc-tool/test/dotty/tools/dottydoc/staticsite/PageTests.scala index 87358d738..6f20e28c2 100644 --- a/doc-tool/test/dotty/tools/dottydoc/staticsite/PageTests.scala +++ b/doc-tool/test/dotty/tools/dottydoc/staticsite/PageTests.scala @@ -15,6 +15,7 @@ class PageTests extends DottyDocTest { |--- | |great""".stripMargin, + Map.empty, Map.empty ) @@ -33,7 +34,8 @@ class PageTests extends DottyDocTest { |--- | |{{ content }}""".stripMargin, - Map("content" -> "Hello, world!") + Map("content" -> "Hello, world!"), + Map.empty ) assert( @@ -45,7 +47,8 @@ class PageTests extends DottyDocTest { val page2 = new MarkdownPage( """|{{ content }}""".stripMargin, - Map("content" -> "hello") + Map("content" -> "hello"), + Map.empty ) assert( page2.yaml == Map(), @@ -57,7 +60,8 @@ class PageTests extends DottyDocTest { """|{% if product.title == "Awesome Shoes" %} |These shoes are awesome! |{% endif %}""".stripMargin, - Map("product" -> Map("title" -> "Awesome Shoes").asJava) + Map("product" -> Map("title" -> "Awesome Shoes").asJava), + Map.empty ) assertEquals( @@ -67,7 +71,7 @@ class PageTests extends DottyDocTest { } @Test def simpleHtmlPage = { - val p1 = new HtmlPage("""

{{ "hello, world!" }}

""", Map.empty) + val p1 = new HtmlPage("""

{{ "hello, world!" }}

""", Map.empty, Map.empty) assert(p1.yaml == Map(), "non-empty yaml found") assertEquals("

hello, world!

", p1.html) } @@ -79,6 +83,7 @@ class PageTests extends DottyDocTest { |--- | |Hello, world!""".stripMargin, + Map.empty, Map.empty ) @@ -93,6 +98,7 @@ class PageTests extends DottyDocTest { | | |Hello, world!""".stripMargin, + Map.empty, Map.empty ) diff --git a/doc-tool/test/dotty/tools/dottydoc/staticsite/SiteTests.scala b/doc-tool/test/dotty/tools/dottydoc/staticsite/SiteTests.scala index 2a8b6ac9b..0c6d232f5 100644 --- a/doc-tool/test/dotty/tools/dottydoc/staticsite/SiteTests.scala +++ b/doc-tool/test/dotty/tools/dottydoc/staticsite/SiteTests.scala @@ -6,6 +6,12 @@ import org.junit.Test import org.junit.Assert._ class SiteTests extends DottyDocTest { + private def html( + str: String, + params: Map[String, AnyRef] = Map.empty, + includes: Map[String, String] = Map.empty + ) = new HtmlPage(str, params, includes) + @Test def hasCorrectLayoutFiles = { val site = new Site(new java.io.File("../doc-tool/resources/")) @@ -20,13 +26,12 @@ class SiteTests extends DottyDocTest { @Test def renderHelloInMainLayout = { val site = new Site(new java.io.File("../doc-tool/resources/")) - val renderedPage = site.render(new HtmlPage( + val renderedPage = site.render(html( """|--- |layout: main |--- | - |Hello, world!""".stripMargin, - Map.empty + |Hello, world!""".stripMargin ), Map.empty) assert( @@ -40,12 +45,11 @@ class SiteTests extends DottyDocTest { @Test def renderMultipleTemplates = { val site = new Site(new java.io.File("../doc-tool/resources/")) - val renderedPage = site.render(new HtmlPage( + val renderedPage = site.render(html( """|--- |layout: index |--- - |Hello, world!""".stripMargin, - Map.empty + |Hello, world!""".stripMargin ), Map.empty) assert( @@ -60,13 +64,12 @@ class SiteTests extends DottyDocTest { @Test def preservesPageYaml = { val site = new Site(new java.io.File("../doc-tool/resources/")) - val renderedPage = site.render(new HtmlPage( + val renderedPage = site.render(html( """|--- |title: Hello, world |layout: index |--- - |Hello, world!""".stripMargin, - Map.empty + |Hello, world!""".stripMargin ), Map.empty) assert( @@ -78,4 +81,15 @@ class SiteTests extends DottyDocTest { "html page did not render properly" ) } + + @Test def include = { + val site = new Site(new java.io.File("../doc-tool/resources/")) + + val renderedInclude = site.render( + html("""{% include "header.html" %}""", includes = site.includes), + Map.empty + ) + + assertEquals("

Some header

\n", renderedInclude) + } } -- cgit v1.2.3