aboutsummaryrefslogtreecommitdiff
path: root/doc-tool/src/dotty/tools/dottydoc/staticsite/Page.scala
blob: f250cbb016e7abeab8f40183ab88428f6576beba (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package dotty.tools
package dottydoc
package staticsite

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 model.Package

import java.util.{ Map => JMap, List => JList }

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]

  def yaml: Map[String, AnyRef] = {
    if (_yaml eq null) initFields()
    _yaml
  }

  def html: String = {
    if (_html eq null) initFields()
    _html
  }

  def firstParagraph: String = {
    if (_html eq null) initFields()

    val sb = new StringBuilder
    var pos = 0
    // to handle nested paragraphs in non markdown code
    var open = 0

    while (pos < _html.length - 4) {
      val str = _html.substring(pos, pos + 4)
      val lstr = str.toLowerCase
      sb append str.head

      pos += 1
      if (lstr.contains("<p>"))
        open += 1
      else if (lstr == "</p>") {
        open -= 1
        if (open == 0) {
          pos = Int.MaxValue
          sb append "/p>"
        }
      }
    }

    sb.toString
  }

  protected[this] var _yaml: Map[String, AnyRef /* String | JList[String] */] = _
  protected[this] var _html: String = _
  protected[this] def initFields() = {
    val md = Parser.builder(Site.markdownOptions).build.parse(pageContent)
    val yamlCollector = new AbstractYamlFrontMatterVisitor()
    yamlCollector.visit(md)

    _yaml = updatedYaml {
      yamlCollector
      .getData().asScala
      .mapValues {
        case xs if xs.size == 1 =>
          val str = xs.get(0)
          if (str.length > 0 && str.head == '"' && str.last == '"')
            str.substring(1, str.length - 1)
          else str
        case xs => xs
      }
      .toMap
    }

    // YAML must start with "---" and end in either "---" or "..."
    val withoutYaml =
      if (pageContent.startsWith("---\n")) {
        val str =
          pageContent.lines
          .drop(1)
          .dropWhile(line => line != "---" && line != "...")
          .drop(1).mkString("\n")

        if (str.isEmpty) throw IllegalFrontMatter(pageContent)
        else str
      }
      else pageContent

    // make accessible via "{{ page.title }}" in templates
    val page = Map("page" ->  _yaml.asJava)
    _html = LiquidTemplate(withoutYaml).render(params ++ page, includes)
  }

  /** Takes "page" from `params` map in case this is a second expansion, and
    * removes "layout" from the parameters if it exists. We don't want to
    * preserve the layout from the previously expanded template
    */
  private def updatedYaml(newYaml: Map[String, AnyRef]): Map[String, AnyRef] =
    params
    .get("page")
    .flatMap {
      case page: Map[String, AnyRef] @unchecked =>
        Some(page - "layout" ++ newYaml)
      case _ => None
    }
    .getOrElse(newYaml)
}

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], val includes: Map[String, String], docs: Map[String, Package]) extends Page {
  lazy val pageContent = fileContents

  override protected[this] def initFields() = {
    super.initFields()
    val md = Parser.builder(Site.markdownOptions).build.parse(_html)
    // fix markdown linking
    MarkdownLinkVisitor(md, docs, params)
    MarkdownCodeBlockVisitor(md)
    _html = HtmlRenderer
      .builder(Site.markdownOptions)
      .escapeHtml(false)
      .build()
      .render(md)
  }
}