aboutsummaryrefslogtreecommitdiff
path: root/doc-tool/src/dotty/tools/dottydoc/staticsite/Template.scala
blob: 498b21f57c0ccacb11743e9bd7765b62e8429292 (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
package dotty.tools
package dottydoc
package staticsite

import scala.util.control.NonFatal

import dotc.util.SourceFile
import dotc.core.Contexts.Context
import dotc.util.Positions.{ Position, NoPosition }
import util.syntax._

trait Template {
  def path: String
  def content: SourceFile
  def show: String = new String(content.content)
}

case class TemplateRenderingError(path: String, ex: Throwable)
extends Exception(s"error rendering $path, $ex")

case class Layout(path: String, content: SourceFile) extends Template

case class Include(path: String, content: SourceFile) extends Template

case class LiquidTemplate(path: String, content: SourceFile) extends Template with ResourceFinder {
  import scala.collection.JavaConverters._
  import dotc.printing.Highlighting._
  import liqp.Template
  import liqp.filters.Filter
  import liqp.parser.Flavor.JEKYLL
  import java.util.{ HashMap, Map => JMap }
  import filters._
  import tags._

  /** Register filters to static container */
  Filter.registerFilter(new Reverse)
  Filter.registerFilter(new First)

  // For some reason, liqp rejects a straight conversion using `.asJava`
  private def toJavaMap(map: Map[String, AnyRef]): HashMap[String, Object] =
    map.foldLeft(new HashMap[String, Object]()) { case (map, (k, v)) =>
      map.put(k, v)
      map
    }

  private def protectedRender(op: => String)(implicit ctx: Context) = try {
    Some(op)
  } catch {
    case NonFatal(ex) => {
      // TODO: when we reimplement the liquid parser, this can go away. For now
      // this is an OK approximation of what went wrong.
      if ((ex.getCause eq null) || ex.getMessage.contains("exceeded the max amount of time")) {
        ctx.docbase.error(
          "unknown error occurred in " +
          Blue(path).toString +
          ", most likely incorrect usage of tag"
        )
        None
      }
      else ex.getCause match {
        case mm: org.antlr.runtime.MismatchedTokenException => {
          val unexpected = LiquidTemplate.token(mm.getUnexpectedType)
          val expected = LiquidTemplate.token(mm.expecting)

          ctx.error(
            if (unexpected == "EOF")
              s"unexpected end of file, expected: '$expected'"
            else
              s"unexpected token '$unexpected', expected: '$expected'",
            content atPos Position(mm.index)
          )

          None
        }
        case ex => {
          if (true || ctx.settings.debug.value)
            throw ex

          None
        }
      }
    }
  }

  def render(params: Map[String, AnyRef], includes: Map[String, Include])(implicit ctx: Context): Option[String] =
    protectedRender {
      Template.parse(show, JEKYLL)
        .`with`(ResourceInclude(params, includes))
        .`with`(RenderReference(params))
        .`with`(RenderLink(params))
        .`with`(RenderTitle(params))
        .`with`(Docstring(params))
        .render(toJavaMap(params))
    }
}

object LiquidTemplate {
  import liqp.parser.LiquidParser

  private val _tokens: Map[String, String] = Map(
    "TagStart" -> "{%",
    "TagEnd"   -> "%}"
  )

  def token(i: Int): String =
    if (i == -1) "EOF"
    else if (i >= LiquidParser.tokenNames.length)
      "non-existing token"
    else _tokens
      .get(LiquidParser.tokenNames(i))
      .getOrElse(s"token  $i")
}