aboutsummaryrefslogtreecommitdiff
path: root/dottydoc/jvm/src
diff options
context:
space:
mode:
Diffstat (limited to 'dottydoc/jvm/src')
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/DottyDoc.scala40
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/core/Phases.scala104
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/model/CommentParsers.scala16
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/model/Html.scala37
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyEntities.scala88
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyParsers.scala57
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentCleaner.scala25
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentCooker.scala8
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentParser.scala779
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentRegex.scala84
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/util/IndexWriters.scala43
11 files changed, 1281 insertions, 0 deletions
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/DottyDoc.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/DottyDoc.scala
new file mode 100644
index 000000000..1c24b67a7
--- /dev/null
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/DottyDoc.scala
@@ -0,0 +1,40 @@
+package dotty.tools
+package dottydoc
+
+import core.Phases.DocPhase
+import dotc.config.CompilerCommand
+import dotc.config.Printers.dottydoc
+import dotc.core.Contexts.Context
+import dotc.core.Phases.Phase
+import dotc.typer.FrontEnd
+import dotc.{Compiler, Driver}
+
+/** Custom Compiler with phases for the documentation tool
+ *
+ * The idea here is to structure `dottydoc` around the new infrastructure. As
+ * such, dottydoc will itself be a compiler. It will, however, produce a format
+ * that can be used by other tools or web-browsers.
+ *
+ * Example:
+ * 1. Use the existing FrontEnd to typecheck the code being fed to dottydoc
+ * 2. Create JSON from the results of the FrontEnd phase
+ */
+case object DottyDocCompiler extends Compiler {
+ override def phases: List[List[Phase]] =
+ List(new FrontEnd) ::
+ List(new DocPhase) ::
+ Nil
+}
+
+object DottyDoc extends Driver {
+ override def setup(args: Array[String], rootCtx: Context): (List[String], Context) = {
+ val ctx = rootCtx.fresh
+ val summary = CompilerCommand.distill(args)(ctx)
+ ctx.setSettings(summary.sstate)
+ ctx.setSetting(ctx.settings.YkeepComments, true)
+ val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired)(ctx)
+ (fileNames, ctx)
+ }
+
+ override def newCompiler(implicit ctx: Context): Compiler = DottyDocCompiler
+}
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/core/Phases.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/core/Phases.scala
new file mode 100644
index 000000000..a52572363
--- /dev/null
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/core/Phases.scala
@@ -0,0 +1,104 @@
+package dotty.tools
+package dottydoc
+package core
+
+/** Dotty and Dottydoc imports */
+import dotc.ast.Trees._
+import dotc.CompilationUnit
+import dotc.config.Printers.dottydoc
+import dotc.core.Contexts.Context
+import dotc.core.Phases.Phase
+import dotc.core.Symbols.Symbol
+
+object Phases {
+
+ class DocPhase extends Phase {
+ import model.comment.Comment
+ import model.CommentParsers.wikiParser
+ import model.Entities._
+ import model.EntityFactories._
+ import dotty.tools.dotc.core.Flags
+ import dotty.tools.dotc.ast.tpd._
+
+ def phaseName = "docphase"
+
+ /** Build documentation hierarchy from existing tree */
+ def collect(tree: Tree)(implicit ctx: Context): Entity = {
+
+ def collectList(xs: List[Tree])(implicit ctx: Context): List[Entity] =
+ xs.map(collect).filter(_ != NonEntity)
+
+ def collectPackageMembers(xs: List[Tree])(implicit ctx: Context): List[PackageMember] =
+ collectList(xs).asInstanceOf[List[PackageMember]]
+
+ def collectMembers(tree: Tree)(implicit ctx: Context): List[Entity] = tree match {
+ case t: Template => collectList(t.body)
+ case _ => Nil
+ }
+
+ val comment =
+ ctx.base.docstring(tree.symbol).map(c => Comment(wikiParser.parseHtml(c)))
+
+ tree match {
+ /** package */
+ case p @ PackageDef(pid, st) =>
+ val name = pid.name.toString
+ Package(name, collectPackageMembers(st), comment, path(p, name))
+
+ /** trait */
+ case t @ TypeDef(n, rhs) if t.symbol.is(Flags.Trait) =>
+ val name = n.toString
+ Trait(name, collectMembers(rhs), comment, flags(t), path(t, name))
+
+ /** objects, on the format "Object$" so drop the last letter */
+ case o @ TypeDef(n, rhs) if o.symbol.is(Flags.Module) =>
+ val name = n.toString.dropRight(1)
+ Object(name, collectMembers(rhs), comment, flags(o), path(o, name))
+
+ /** class / case class */
+ case c @ TypeDef(name, rhs) if c.symbol.isClass =>
+ (name.toString, collectMembers(rhs), comment, flags(c), path(c, name.toString)) match {
+ case x if c.symbol.is(Flags.CaseClass) => CaseClass.tupled(x)
+ case x => Class.tupled(x)
+ }
+
+ /** def */
+ case d: DefDef =>
+ Def(d.name.toString, comment, flags(d), path(d, d.name.toString))
+
+ /** val */
+ case v: ValDef if !v.symbol.is(Flags.ModuleVal) =>
+ Val(v.name.toString, comment, flags(v), path(v, v.name.toString))
+
+ case x => {
+ dottydoc.println(s"Found unwanted entity: $x (${x.pos}, ${comment})\n${x.show}")
+ NonEntity
+ }
+ }
+ }
+
+ var packages: Map[String, Package] = Map.empty
+
+ def addEntity(p: Package): Unit = {
+ val path = p.path.mkString(".")
+ packages = packages + (path -> packages.get(path).map { ex =>
+ val children = (ex.children ::: p.children).distinct.sortBy(_.name)
+ val comment = ex.comment.orElse(p.comment)
+ Package(p.name, children, comment, p.path)
+ }.getOrElse(p))
+ }
+
+ override def run(implicit ctx: Context): Unit =
+ collect(ctx.compilationUnit.tpdTree) match {
+ case p: Package => addEntity(p)
+ case _ => ()
+ }
+
+ override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = {
+ val compUnits = super.runOn(units)
+ util.IndexWriters.writeJs(packages, "../js/out")
+ compUnits
+ }
+ }
+
+}
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/CommentParsers.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/CommentParsers.scala
new file mode 100644
index 000000000..dce8f4a69
--- /dev/null
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/CommentParsers.scala
@@ -0,0 +1,16 @@
+package dotty.tools
+package dottydoc
+package model
+
+object CommentParsers {
+ import comment._
+ import BodyParsers._
+
+ sealed class WikiParser
+ extends CommentCleaner with CommentParser with CommentCooker {
+ def parseHtml(str: String): String =
+ parse(clean(str), str).toHtml
+ }
+
+ val wikiParser = new WikiParser
+}
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/Html.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/Html.scala
new file mode 100644
index 000000000..4fb9168b7
--- /dev/null
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/Html.scala
@@ -0,0 +1,37 @@
+package dotty.tools.dottydoc
+package model
+
+import scalatags.Text.all._
+
+object Html {
+ import prickle._
+ import Entities._
+
+ private def relPath(to: String, from: Entity) =
+ "../" * from.path.length + to
+
+ def entityHtml(ent: Entity) = "<!DOCTYPE html>" + html(
+ head(
+ meta(charset := "utf-8"),
+ meta(name := "viewport",
+ content := "width=device-width, initial-scale=1, shrink-to-fit=no"),
+ meta("http-equiv".attr := "x-ua-compatible", content := "ie=edge"),
+
+ script(`type` := "text/javascript", src := relPath("static/material.min.js", ent)),
+ script(`type` := "text/javascript", src := relPath("static/highlight.pack.js", ent)),
+ script(`type` := "text/javascript", src := relPath("index.js", ent)),
+ script(`type` := "text/javascript", src := relPath("target/scala-2.11/dottydoc-fastopt.js", ent)),
+ link(rel := "stylesheet", href := relPath("static/material.min.css", ent)),
+ link(rel := "stylesheet", href := relPath("static/github.css", ent)),
+ link(rel := "stylesheet", href := relPath("static/index.css", ent)),
+ link(rel := "stylesheet", href := "https://fonts.googleapis.com/icon?family=Material+Icons")
+
+ ),
+ body(div(id := "main-container")),
+ script(raw(
+ s"""|Index.currentEntity = ${Pickle.intoString(ent)};
+ |dotty.tools.dottydoc.js.DottyDocJS()
+ | .main(document.getElementById("main-container"));
+ """.stripMargin))
+ )
+}
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyEntities.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyEntities.scala
new file mode 100644
index 000000000..e06789e98
--- /dev/null
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyEntities.scala
@@ -0,0 +1,88 @@
+package dotty.tools.dottydoc
+package model
+package comment
+
+import scala.collection._
+
+/** A body of text. A comment has a single body, which is composed of
+ * at least one block. Inside every body is exactly one summary (see
+ * [[scala.tools.nsc.doc.model.comment.Summary]]). */
+final case class Body(blocks: Seq[Block]) {
+
+ /** The summary text of the comment body. */
+ lazy val summary: Option[Inline] = {
+ def summaryInBlock(block: Block): Seq[Inline] = block match {
+ case Title(text, _) => summaryInInline(text)
+ case Paragraph(text) => summaryInInline(text)
+ case UnorderedList(items) => items flatMap summaryInBlock
+ case OrderedList(items, _) => items flatMap summaryInBlock
+ case DefinitionList(items) => items.values.toSeq flatMap summaryInBlock
+ case _ => Nil
+ }
+ def summaryInInline(text: Inline): Seq[Inline] = text match {
+ case Summary(text) => List(text)
+ case Chain(items) => items flatMap summaryInInline
+ case Italic(text) => summaryInInline(text)
+ case Bold(text) => summaryInInline(text)
+ case Underline(text) => summaryInInline(text)
+ case Superscript(text) => summaryInInline(text)
+ case Subscript(text) => summaryInInline(text)
+ case Link(_, title) => summaryInInline(title)
+ case _ => Nil
+ }
+ (blocks flatMap { summaryInBlock(_) }).toList match {
+ case Nil => None
+ case inline :: Nil => Some(inline)
+ case inlines => Some(Chain(inlines))
+ }
+ }
+}
+
+/** A block-level element of text, such as a paragraph or code block. */
+sealed abstract class Block
+
+final case class Title(text: Inline, level: Int) extends Block
+final case class Paragraph(text: Inline) extends Block
+final case class Code(data: String) extends Block
+final case class UnorderedList(items: Seq[Block]) extends Block
+final case class OrderedList(items: Seq[Block], style: String) extends Block
+final case class DefinitionList(items: SortedMap[Inline, Block]) extends Block
+final case class HorizontalRule() extends Block
+
+/** An section of text inside a block, possibly with formatting. */
+sealed abstract class Inline
+
+final case class Chain(items: Seq[Inline]) extends Inline
+final case class Italic(text: Inline) extends Inline
+final case class Bold(text: Inline) extends Inline
+final case class Underline(text: Inline) extends Inline
+final case class Superscript(text: Inline) extends Inline
+final case class Subscript(text: Inline) extends Inline
+final case class Link(target: String, title: Inline) extends Inline
+final case class Monospace(text: Inline) extends Inline
+final case class Text(text: String) extends Inline
+//TODO: this should be used
+//abstract class EntityLink(val title: Inline) extends Inline { def link: LinkTo }
+//object EntityLink {
+// def apply(title: Inline, linkTo: LinkTo) = new EntityLink(title) { def link: LinkTo = linkTo }
+// def unapply(el: EntityLink): Option[(Inline, LinkTo)] = Some((el.title, el.link))
+//}
+final case class HtmlTag(data: String) extends Inline {
+ private val Pattern = """(?ms)\A<(/?)(.*?)[\s>].*\z""".r
+ private val (isEnd, tagName) = data match {
+ case Pattern(s1, s2) =>
+ (! s1.isEmpty, Some(s2.toLowerCase))
+ case _ =>
+ (false, None)
+ }
+
+ def canClose(open: HtmlTag) = {
+ isEnd && tagName == open.tagName
+ }
+
+ private val TagsNotToClose = Set("br", "img")
+ def close = tagName collect { case name if !TagsNotToClose(name) => HtmlTag(s"</$name>") }
+}
+
+/** The summary of a comment, usually its first sentence. There must be exactly one summary per body. */
+final case class Summary(text: Inline) extends Inline
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyParsers.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyParsers.scala
new file mode 100644
index 000000000..9b6ec600d
--- /dev/null
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyParsers.scala
@@ -0,0 +1,57 @@
+package dotty.tools.dottydoc
+package model
+package comment
+
+object BodyParsers {
+ implicit class BodyToHtml(val body: Body) extends AnyVal {
+ def toHtml: String = bodyToHtml(body)
+
+ private def inlineToHtml(inl: Inline): String = inl match {
+ case Chain(items) => (items map inlineToHtml).mkString
+ case Italic(in) => s"<i>${inlineToHtml(in)}</i>"
+ case Bold(in) => s"<b>${inlineToHtml(in)}</b>"
+ case Underline(in) => s"<u>${inlineToHtml(in)}</u>"
+ case Superscript(in) => s"<sup>${inlineToHtml(in)}</sup>"
+ case Subscript(in) => s"<sub>${inlineToHtml(in) }</sub>"
+ case Link(raw, title) => s"""<a href=$raw target="_blank">${inlineToHtml(title)}</a>"""
+ case Monospace(in) => s"<code>${inlineToHtml(in)}</code>"
+ case Text(text) => text
+ case Summary(in) => inlineToHtml(in)
+ case HtmlTag(tag) => tag
+ //TODO: when we have EntityLinks, they should be enabled here too
+ //case EntityLink(target, link) => linkToHtml(target, link, hasLinks = true)
+ }
+
+ private def bodyToHtml(body: Body): String =
+ (body.blocks map blockToHtml).mkString
+
+ private def blockToHtml(block: Block): String = block match {
+ case Title(in, 1) => s"<h1>${inlineToHtml(in)}</h1>"
+ case Title(in, 2) => s"<h2>${inlineToHtml(in)}</h2>"
+ case Title(in, 3) => s"<h3>${inlineToHtml(in)}</h3>"
+ case Title(in, _) => s"<h4>${inlineToHtml(in)}</h4>"
+ case Paragraph(in) => s"<p>${inlineToHtml(in)}</p>"
+ case Code(data) => s"""<pre><code class="scala">$data</code></pre>"""
+ case UnorderedList(items) =>
+ s"<ul>${listItemsToHtml(items)}</ul>"
+ case OrderedList(items, listStyle) =>
+ s"<ol class=${listStyle}>${listItemsToHtml(items)}</ol>"
+ case DefinitionList(items) =>
+ s"<dl>${items map { case (t, d) => s"<dt>${inlineToHtml(t)}</dt><dd>${blockToHtml(d)}</dd>" } }</dl>"
+ case HorizontalRule() =>
+ "<hr/>"
+ }
+
+ private def listItemsToHtml(items: Seq[Block]) =
+ items.foldLeft(""){ (list, item) =>
+ item match {
+ case OrderedList(_, _) | UnorderedList(_) => // html requires sub ULs to be put into the last LI
+ list + s"<li>${blockToHtml(item)}</li>"
+ case Paragraph(inline) =>
+ list + s"<li>${inlineToHtml(inline)}</li>" // LIs are blocks, no need to use Ps
+ case block =>
+ list + s"<li>${blockToHtml(block)}</li>"
+ }
+ }
+ }
+}
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentCleaner.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentCleaner.scala
new file mode 100644
index 000000000..5eb64c612
--- /dev/null
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentCleaner.scala
@@ -0,0 +1,25 @@
+package dotty.tools.dottydoc
+package model
+package comment
+
+trait CommentCleaner {
+ import Regexes._
+
+ def clean(comment: String): List[String] = {
+ def cleanLine(line: String): String = {
+ // Remove trailing whitespaces
+ TrailingWhitespace.replaceAllIn(line, "") match {
+ case CleanCommentLine(ctl) => ctl
+ case tl => tl
+ }
+ }
+ val strippedComment = comment.trim.stripPrefix("/*").stripSuffix("*/")
+ val safeComment = DangerousTags.replaceAllIn(strippedComment, { htmlReplacement(_) })
+ val javadoclessComment = JavadocTags.replaceAllIn(safeComment, { javadocReplacement(_) })
+ val markedTagComment =
+ SafeTags.replaceAllIn(javadoclessComment, { mtch =>
+ java.util.regex.Matcher.quoteReplacement(safeTagMarker + mtch.matched + safeTagMarker)
+ })
+ markedTagComment.lines.toList map (cleanLine(_))
+ }
+}
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentCooker.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentCooker.scala
new file mode 100644
index 000000000..4d4474d52
--- /dev/null
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentCooker.scala
@@ -0,0 +1,8 @@
+package dotty.tools.dottydoc
+package model
+package comment
+
+trait CommentCooker {
+ trait Context
+ def cook(comment: String)(implicit ctx: Context): String = ""
+}
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentParser.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentParser.scala
new file mode 100644
index 000000000..5ba05fbfe
--- /dev/null
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentParser.scala
@@ -0,0 +1,779 @@
+package dotty.tools.dottydoc
+package model
+package comment
+
+import dotty.tools.dotc.util.Positions._
+import dotty.tools.dotc.core.Symbols._
+import scala.collection.mutable
+import dotty.tools.dotc.config.Printers.dottydoc
+import scala.util.matching.Regex
+
+//TODO: re-enable pos?
+trait CommentParser {
+ import Regexes._
+
+ /** Parses a raw comment string into a `Comment` object.
+ * @param cleanComment a cleaned comment to be parsed
+ * @param src the raw comment source string.
+ * @param pos the position of the comment in source.
+ */
+ def parse(comment: List[String], src: String, /*pos: Position,*/ site: Symbol = NoSymbol): Body = {
+
+ /** Parses a comment (in the form of a list of lines) to a `Comment`
+ * instance, recursively on lines. To do so, it splits the whole comment
+ * into main body and tag bodies, then runs the `WikiParser` on each body
+ * before creating the comment instance.
+ *
+ * @param docBody The body of the comment parsed until now.
+ * @param tags All tags parsed until now.
+ * @param lastTagKey The last parsed tag, or `None` if the tag section
+ * hasn't started. Lines that are not tagged are part
+ * of the previous tag or, if none exists, of the body.
+ * @param remaining The lines that must still recursively be parsed.
+ * @param inCodeBlock Whether the next line is part of a code block (in
+ * which no tags must be read).
+ */
+ def parseComment (
+ docBody: StringBuilder,
+ tags: Map[TagKey, List[String]],
+ lastTagKey: Option[TagKey],
+ remaining: List[String],
+ inCodeBlock: Boolean
+ ): Body = remaining match {
+
+ case CodeBlockStartRegex(before, marker, after) :: ls if (!inCodeBlock) =>
+ if (!before.trim.isEmpty && !after.trim.isEmpty)
+ parseComment(docBody, tags, lastTagKey, before :: marker :: after :: ls, inCodeBlock = false)
+ else if (!before.trim.isEmpty)
+ parseComment(docBody, tags, lastTagKey, before :: marker :: ls, inCodeBlock = false)
+ else if (!after.trim.isEmpty)
+ parseComment(docBody, tags, lastTagKey, marker :: after :: ls, inCodeBlock = true)
+ else lastTagKey match {
+ case Some(key) =>
+ val value =
+ ((tags get key): @unchecked) match {
+ case Some(b :: bs) => (b + endOfLine + marker) :: bs
+ case None => oops("lastTagKey set when no tag exists for key")
+ }
+ parseComment(docBody, tags + (key -> value), lastTagKey, ls, inCodeBlock = true)
+ case None =>
+ parseComment(docBody append endOfLine append marker, tags, lastTagKey, ls, inCodeBlock = true)
+ }
+
+ case CodeBlockEndRegex(before, marker, after) :: ls => {
+ if (!before.trim.isEmpty && !after.trim.isEmpty)
+ parseComment(docBody, tags, lastTagKey, before :: marker :: after :: ls, inCodeBlock = true)
+ if (!before.trim.isEmpty)
+ parseComment(docBody, tags, lastTagKey, before :: marker :: ls, inCodeBlock = true)
+ else if (!after.trim.isEmpty)
+ parseComment(docBody, tags, lastTagKey, marker :: after :: ls, inCodeBlock = false)
+ else lastTagKey match {
+ case Some(key) =>
+ val value =
+ ((tags get key): @unchecked) match {
+ case Some(b :: bs) => (b + endOfLine + marker) :: bs
+ case None => oops("lastTagKey set when no tag exists for key")
+ }
+ parseComment(docBody, tags + (key -> value), lastTagKey, ls, inCodeBlock = false)
+ case None =>
+ parseComment(docBody append endOfLine append marker, tags, lastTagKey, ls, inCodeBlock = false)
+ }
+ }
+
+ case SymbolTagRegex(name, sym, body) :: ls if (!inCodeBlock) => {
+ val key = SymbolTagKey(name, sym)
+ val value = body :: tags.getOrElse(key, Nil)
+ parseComment(docBody, tags + (key -> value), Some(key), ls, inCodeBlock)
+ }
+
+ case SimpleTagRegex(name, body) :: ls if (!inCodeBlock) => {
+ val key = SimpleTagKey(name)
+ val value = body :: tags.getOrElse(key, Nil)
+ parseComment(docBody, tags + (key -> value), Some(key), ls, inCodeBlock)
+ }
+
+ case SingleTagRegex(name) :: ls if (!inCodeBlock) => {
+ val key = SimpleTagKey(name)
+ val value = "" :: tags.getOrElse(key, Nil)
+ parseComment(docBody, tags + (key -> value), Some(key), ls, inCodeBlock)
+ }
+
+ case line :: ls if (lastTagKey.isDefined) => {
+ val newtags = if (!line.isEmpty) {
+ val key = lastTagKey.get
+ val value =
+ ((tags get key): @unchecked) match {
+ case Some(b :: bs) => (b + endOfLine + line) :: bs
+ case None => oops("lastTagKey set when no tag exists for key")
+ }
+ tags + (key -> value)
+ } else tags
+ parseComment(docBody, newtags, lastTagKey, ls, inCodeBlock)
+ }
+
+ case line :: ls => {
+ if (docBody.length > 0) docBody append endOfLine
+ docBody append line
+ parseComment(docBody, tags, lastTagKey, ls, inCodeBlock)
+ }
+
+ case Nil => {
+ // Take the {inheritance, content} diagram keys aside, as it doesn't need any parsing
+ val inheritDiagramTag = SimpleTagKey("inheritanceDiagram")
+ val contentDiagramTag = SimpleTagKey("contentDiagram")
+
+ val inheritDiagramText: List[String] = tags.get(inheritDiagramTag) match {
+ case Some(list) => list
+ case None => List.empty
+ }
+
+ val contentDiagramText: List[String] = tags.get(contentDiagramTag) match {
+ case Some(list) => list
+ case None => List.empty
+ }
+
+ val stripTags=List(inheritDiagramTag, contentDiagramTag, SimpleTagKey("template"), SimpleTagKey("documentable"))
+ val tagsWithoutDiagram = tags.filterNot(pair => stripTags.contains(pair._1))
+
+ val bodyTags: mutable.Map[TagKey, List[Body]] =
+ mutable.Map((tagsWithoutDiagram mapValues {tag => tag map (parseWikiAtSymbol(_, /*pos,*/ site))}).toSeq: _*)
+
+ def oneTag(key: SimpleTagKey, filterEmpty: Boolean = true): Option[Body] =
+ ((bodyTags remove key): @unchecked) match {
+ case Some(r :: rs) if !(filterEmpty && r.blocks.isEmpty) =>
+ //if (!rs.isEmpty) dottydoc.println(s"$pos: only one '@${key.name}' tag is allowed")
+ Some(r)
+ case _ => None
+ }
+
+ def allTags(key: SimpleTagKey): List[Body] =
+ (bodyTags remove key).getOrElse(Nil).filterNot(_.blocks.isEmpty)
+
+ def allSymsOneTag(key: TagKey, filterEmpty: Boolean = true): Map[String, Body] = {
+ val keys: Seq[SymbolTagKey] =
+ bodyTags.keys.toSeq flatMap {
+ case stk: SymbolTagKey if (stk.name == key.name) => Some(stk)
+ case stk: SimpleTagKey if (stk.name == key.name) =>
+ //dottydoc.println(s"$pos: tag '@${stk.name}' must be followed by a symbol name")
+ None
+ case _ => None
+ }
+ val pairs: Seq[(String, Body)] =
+ for (key <- keys) yield {
+ val bs = (bodyTags remove key).get
+ //if (bs.length > 1)
+ //dottydoc.println(s"$pos: only one '@${key.name}' tag for symbol ${key.symbol} is allowed")
+ (key.symbol, bs.head)
+ }
+ Map.empty[String, Body] ++ (if (filterEmpty) pairs.filterNot(_._2.blocks.isEmpty) else pairs)
+ }
+
+ def linkedExceptions: Map[String, Body] = {
+ val m = allSymsOneTag(SimpleTagKey("throws"), filterEmpty = false)
+
+ m.map { case (name,body) =>
+ //val link = memberLookup(pos, name, site)
+ val newBody = body match {
+ case Body(List(Paragraph(Chain(content)))) =>
+ val descr = Text(" ") +: content
+ //val entityLink = EntityLink(Monospace(Text(name)), link)
+ Body(List(Paragraph(Chain(/*entityLink +: */descr))))
+ case _ => body
+ }
+ (name, newBody)
+ }
+ }
+
+ // TODO: this method should return a parsed comment with the members below
+ //val com = createComment (
+ // body0 = Some(parseWikiAtSymbol(docBody.toString, pos, site)),
+ // authors0 = allTags(SimpleTagKey("author")),
+ // see0 = allTags(SimpleTagKey("see")),
+ // result0 = oneTag(SimpleTagKey("return")),
+ // throws0 = linkedExceptions,
+ // valueParams0 = allSymsOneTag(SimpleTagKey("param")),
+ // typeParams0 = allSymsOneTag(SimpleTagKey("tparam")),
+ // version0 = oneTag(SimpleTagKey("version")),
+ // since0 = oneTag(SimpleTagKey("since")),
+ // todo0 = allTags(SimpleTagKey("todo")),
+ // deprecated0 = oneTag(SimpleTagKey("deprecated"), filterEmpty = false),
+ // note0 = allTags(SimpleTagKey("note")),
+ // example0 = allTags(SimpleTagKey("example")),
+ // constructor0 = oneTag(SimpleTagKey("constructor")),
+ // source0 = Some(clean(src).mkString("\n")),
+ // inheritDiagram0 = inheritDiagramText,
+ // contentDiagram0 = contentDiagramText,
+ // group0 = oneTag(SimpleTagKey("group")),
+ // groupDesc0 = allSymsOneTag(SimpleTagKey("groupdesc")),
+ // groupNames0 = allSymsOneTag(SimpleTagKey("groupname")),
+ // groupPrio0 = allSymsOneTag(SimpleTagKey("groupprio")),
+ // hideImplicitConversions0 = allTags(SimpleTagKey("hideImplicitConversion")),
+ // shortDescription0 = allTags(SimpleTagKey("shortDescription"))
+ //)
+ //
+ //for ((key, _) <- bodyTags)
+ // dottydoc.println(s"$pos: Tag '@${key.name}' is not recognised")
+
+ parseWikiAtSymbol(docBody.toString, /*pos,*/ site)
+ }
+ }
+
+ parseComment(new StringBuilder(comment.size), Map.empty, None, comment, inCodeBlock = false)
+ }
+
+ /** A key used for a tag map. The key is built from the name of the tag and
+ * from the linked symbol if the tag has one.
+ * Equality on tag keys is structural. */
+ private sealed abstract class TagKey {
+ def name: String
+ }
+
+ private final case class SimpleTagKey(name: String) extends TagKey
+ private final case class SymbolTagKey(name: String, symbol: String) extends TagKey
+
+ /** Something that should not have happened, happened, and Scaladoc should exit. */
+ private def oops(msg: String): Nothing =
+ throw new IllegalArgumentException("program logic: " + msg)
+
+ /** Parses a string containing wiki syntax into a `Comment` object.
+ * Note that the string is assumed to be clean:
+ * - Removed Scaladoc start and end markers.
+ * - Removed start-of-line star and one whitespace afterwards (if present).
+ * - Removed all end-of-line whitespace.
+ * - Only `endOfLine` is used to mark line endings. */
+ def parseWikiAtSymbol(string: String, /*pos: Position,*/ site: Symbol): Body = new WikiParser(string, /*pos,*/ site).document()
+
+ /** TODO
+ *
+ * @author Ingo Maier
+ * @author Manohar Jonnalagedda
+ * @author Gilles Dubochet */
+ protected final class WikiParser(val buffer: String, /*pos: Position,*/ site: Symbol) extends CharReader(buffer) { wiki =>
+ var summaryParsed = false
+
+ def document(): Body = {
+ val blocks = new mutable.ListBuffer[Block]
+ while (char != endOfText)
+ blocks += block()
+ Body(blocks.toList)
+ }
+
+ /* BLOCKS */
+
+ /** {{{ block ::= code | title | hrule | listBlock | para }}} */
+ def block(): Block = {
+ if (checkSkipInitWhitespace("{{{"))
+ code()
+ else if (checkSkipInitWhitespace('='))
+ title()
+ else if (checkSkipInitWhitespace("----"))
+ hrule()
+ else if (checkList)
+ listBlock
+ else {
+ para()
+ }
+ }
+
+ /** listStyle ::= '-' spc | '1.' spc | 'I.' spc | 'i.' spc | 'A.' spc | 'a.' spc
+ * Characters used to build lists and their constructors */
+ protected val listStyles = Map[String, (Seq[Block] => Block)]( // TODO Should this be defined at some list companion?
+ "- " -> ( UnorderedList(_) ),
+ "1. " -> ( OrderedList(_,"decimal") ),
+ "I. " -> ( OrderedList(_,"upperRoman") ),
+ "i. " -> ( OrderedList(_,"lowerRoman") ),
+ "A. " -> ( OrderedList(_,"upperAlpha") ),
+ "a. " -> ( OrderedList(_,"lowerAlpha") )
+ )
+
+ /** Checks if the current line is formed with more than one space and one the listStyles */
+ def checkList =
+ (countWhitespace > 0) && (listStyles.keys exists { checkSkipInitWhitespace(_) })
+
+ /** {{{
+ * nListBlock ::= nLine { mListBlock }
+ * nLine ::= nSpc listStyle para '\n'
+ * }}}
+ * Where n and m stand for the number of spaces. When `m > n`, a new list is nested. */
+ def listBlock(): Block = {
+
+ /** Consumes one list item block and returns it, or None if the block is
+ * not a list or a different list. */
+ def listLine(indent: Int, style: String): Option[Block] =
+ if (countWhitespace > indent && checkList)
+ Some(listBlock)
+ else if (countWhitespace != indent || !checkSkipInitWhitespace(style))
+ None
+ else {
+ jumpWhitespace()
+ jump(style)
+ val p = Paragraph(inline(isInlineEnd = false))
+ blockEnded("end of list line ")
+ Some(p)
+ }
+
+ /** Consumes all list item blocks (possibly with nested lists) of the
+ * same list and returns the list block. */
+ def listLevel(indent: Int, style: String): Block = {
+ val lines = mutable.ListBuffer.empty[Block]
+ var line: Option[Block] = listLine(indent, style)
+ while (line.isDefined) {
+ lines += line.get
+ line = listLine(indent, style)
+ }
+ val constructor = listStyles(style)
+ constructor(lines)
+ }
+
+ val indent = countWhitespace
+ val style = (listStyles.keys find { checkSkipInitWhitespace(_) }).getOrElse(listStyles.keys.head)
+ listLevel(indent, style)
+ }
+
+ def code(): Block = {
+ jumpWhitespace()
+ jump("{{{")
+ val str = readUntil("}}}")
+ if (char == endOfText)
+ reportError(/*pos,*/ "unclosed code block")
+ else
+ jump("}}}")
+ blockEnded("code block")
+ Code(normalizeIndentation(str))
+ }
+
+ /** {{{ title ::= ('=' inline '=' | "==" inline "==" | ...) '\n' }}} */
+ def title(): Block = {
+ jumpWhitespace()
+ val inLevel = repeatJump('=')
+ val text = inline(check("=" * inLevel))
+ val outLevel = repeatJump('=', inLevel)
+ if (inLevel != outLevel)
+ reportError(/*pos,*/ "unbalanced or unclosed heading")
+ blockEnded("heading")
+ Title(text, inLevel)
+ }
+
+ /** {{{ hrule ::= "----" { '-' } '\n' }}} */
+ def hrule(): Block = {
+ jumpWhitespace()
+ repeatJump('-')
+ blockEnded("horizontal rule")
+ HorizontalRule()
+ }
+
+ /** {{{ para ::= inline '\n' }}} */
+ def para(): Block = {
+ val p =
+ if (summaryParsed)
+ Paragraph(inline(isInlineEnd = false))
+ else {
+ val s = summary()
+ val r =
+ if (checkParaEnded()) List(s) else List(s, inline(isInlineEnd = false))
+ summaryParsed = true
+ Paragraph(Chain(r))
+ }
+ while (char == endOfLine && char != endOfText)
+ nextChar()
+ p
+ }
+
+ /* INLINES */
+
+ val OPEN_TAG = "^<([A-Za-z]+)( [^>]*)?(/?)>$".r
+ val CLOSE_TAG = "^</([A-Za-z]+)>$".r
+ private def readHTMLFrom(begin: HtmlTag): String = {
+ val list = mutable.ListBuffer.empty[String]
+ val stack = mutable.ListBuffer.empty[String]
+
+ begin.close match {
+ case Some(HtmlTag(CLOSE_TAG(s))) =>
+ stack += s
+ case _ =>
+ return ""
+ }
+
+ do {
+ val str = readUntil { char == safeTagMarker || char == endOfText }
+ nextChar()
+
+ list += str
+
+ str match {
+ case OPEN_TAG(s, _, standalone) => {
+ if (standalone != "/") {
+ stack += s
+ }
+ }
+ case CLOSE_TAG(s) => {
+ if (s == stack.last) {
+ stack.remove(stack.length-1)
+ }
+ }
+ case _ => ;
+ }
+ } while (stack.length > 0 && char != endOfText)
+
+ list mkString ""
+ }
+
+ def inline(isInlineEnd: => Boolean): Inline = {
+
+ def inline0(): Inline = {
+ if (char == safeTagMarker) {
+ val tag = htmlTag()
+ HtmlTag(tag.data + readHTMLFrom(tag))
+ }
+ else if (check("'''")) bold()
+ else if (check("''")) italic()
+ else if (check("`")) monospace()
+ else if (check("__")) underline()
+ else if (check("^")) superscript()
+ else if (check(",,")) subscript()
+ else if (check("[[")) link()
+ else {
+ val str = readUntil {
+ char == safeTagMarker ||
+ check("''") ||
+ char == '`' ||
+ check("__") ||
+ char == '^' ||
+ check(",,") ||
+ check("[[") ||
+ isInlineEnd ||
+ checkParaEnded ||
+ char == endOfLine
+ }
+ Text(str)
+ }
+ }
+
+ val inlines: List[Inline] = {
+ val iss = mutable.ListBuffer.empty[Inline]
+ iss += inline0()
+ while (!isInlineEnd && !checkParaEnded) {
+ val skipEndOfLine = if (char == endOfLine) {
+ nextChar()
+ true
+ } else {
+ false
+ }
+
+ val current = inline0()
+ (iss.last, current) match {
+ case (Text(t1), Text(t2)) if skipEndOfLine =>
+ iss.update(iss.length - 1, Text(t1 + endOfLine + t2))
+ case (i1, i2) if skipEndOfLine =>
+ iss ++= List(Text(endOfLine.toString), i2)
+ case _ => iss += current
+ }
+ }
+ iss.toList
+ }
+
+ inlines match {
+ case Nil => Text("")
+ case i :: Nil => i
+ case is => Chain(is)
+ }
+
+ }
+
+ def htmlTag(): HtmlTag = {
+ jump(safeTagMarker)
+ val read = readUntil(safeTagMarker)
+ if (char != endOfText) jump(safeTagMarker)
+ HtmlTag(read)
+ }
+
+ def bold(): Inline = {
+ jump("'''")
+ val i = inline(check("'''"))
+ jump("'''")
+ Bold(i)
+ }
+
+ def italic(): Inline = {
+ jump("''")
+ val i = inline(check("''"))
+ jump("''")
+ Italic(i)
+ }
+
+ def monospace(): Inline = {
+ jump("`")
+ val i = inline(check("`"))
+ jump("`")
+ Monospace(i)
+ }
+
+ def underline(): Inline = {
+ jump("__")
+ val i = inline(check("__"))
+ jump("__")
+ Underline(i)
+ }
+
+ def superscript(): Inline = {
+ jump("^")
+ val i = inline(check("^"))
+ if (jump("^")) {
+ Superscript(i)
+ } else {
+ Chain(Seq(Text("^"), i))
+ }
+ }
+
+ def subscript(): Inline = {
+ jump(",,")
+ val i = inline(check(",,"))
+ jump(",,")
+ Subscript(i)
+ }
+
+ def summary(): Inline = {
+ val i = inline(checkSentenceEnded())
+ Summary(
+ if (jump("."))
+ Chain(List(i, Text(".")))
+ else
+ i
+ )
+ }
+
+ def link(): Inline = {
+ val SchemeUri = """([a-z]+:.*)""".r
+ jump("[[")
+ val parens = 2 + repeatJump('[')
+ val stop = "]" * parens
+ val target = readUntil { check(stop) || isWhitespaceOrNewLine(char) }
+ val title =
+ if (!check(stop)) Some({
+ jumpWhitespaceOrNewLine()
+ inline(check(stop))
+ })
+ else None
+ jump(stop)
+
+ (target, title) match {
+ case (SchemeUri(uri), optTitle) =>
+ Link(uri, optTitle getOrElse Text(uri))
+ case (qualName, optTitle) =>
+ //TODO: this should be enabled
+ //makeEntityLink(optTitle getOrElse Text(target), pos, target, site)
+ title.getOrElse(Text("broken link"))
+ }
+ }
+
+ /* UTILITY */
+
+ /** {{{ eol ::= { whitespace } '\n' }}} */
+ def blockEnded(blockType: String): Unit = {
+ if (char != endOfLine && char != endOfText) {
+ reportError(/*pos,*/ "no additional content on same line after " + blockType)
+ jumpUntil(endOfLine)
+ }
+ while (char == endOfLine)
+ nextChar()
+ }
+
+ /**
+ * Eliminates the (common) leading spaces in all lines, based on the first line
+ * For indented pieces of code, it reduces the indent to the least whitespace prefix:
+ * {{{
+ * indented example
+ * another indented line
+ * if (condition)
+ * then do something;
+ * ^ this is the least whitespace prefix
+ * }}}
+ */
+ def normalizeIndentation(_code: String): String = {
+
+ val code = _code.replaceAll("\\s+$", "").dropWhile(_ == '\n') // right-trim + remove all leading '\n'
+ val lines = code.split("\n")
+
+ // maxSkip - size of the longest common whitespace prefix of non-empty lines
+ val nonEmptyLines = lines.filter(_.trim.nonEmpty)
+ val maxSkip = if (nonEmptyLines.isEmpty) 0 else nonEmptyLines.map(line => line.prefixLength(_ == ' ')).min
+
+ // remove common whitespace prefix
+ lines.map(line => if (line.trim.nonEmpty) line.substring(maxSkip) else line).mkString("\n")
+ }
+
+ def checkParaEnded(): Boolean = {
+ (char == endOfText) ||
+ ((char == endOfLine) && {
+ val poff = offset
+ nextChar() // read EOL
+ val ok = {
+ checkSkipInitWhitespace(endOfLine) ||
+ checkSkipInitWhitespace('=') ||
+ checkSkipInitWhitespace("{{{") ||
+ checkList ||
+ checkSkipInitWhitespace('\u003D')
+ }
+ offset = poff
+ ok
+ })
+ }
+
+ def checkSentenceEnded(): Boolean = {
+ (char == '.') && {
+ val poff = offset
+ nextChar() // read '.'
+ val ok = char == endOfText || char == endOfLine || isWhitespace(char)
+ offset = poff
+ ok
+ }
+ }
+
+ def reportError(/*pos: Position,*/ message: String) =
+ //dottydoc.println(s"$pos: $message")
+ dottydoc.println(s"$message")
+ }
+
+ protected sealed class CharReader(buffer: String) { reader =>
+
+ var offset: Int = 0
+ def char: Char =
+ if (offset >= buffer.length) endOfText else buffer charAt offset
+
+ final def nextChar() =
+ offset += 1
+
+ final def check(chars: String): Boolean = {
+ val poff = offset
+ val ok = jump(chars)
+ offset = poff
+ ok
+ }
+
+ def checkSkipInitWhitespace(c: Char): Boolean = {
+ val poff = offset
+ jumpWhitespace()
+ val ok = jump(c)
+ offset = poff
+ ok
+ }
+
+ def checkSkipInitWhitespace(chars: String): Boolean = {
+ val poff = offset
+ jumpWhitespace()
+ val (ok0, chars0) =
+ if (chars.charAt(0) == ' ')
+ (offset > poff, chars substring 1)
+ else
+ (true, chars)
+ val ok = ok0 && jump(chars0)
+ offset = poff
+ ok
+ }
+
+ def countWhitespace: Int = {
+ var count = 0
+ val poff = offset
+ while (isWhitespace(char) && char != endOfText) {
+ nextChar()
+ count += 1
+ }
+ offset = poff
+ count
+ }
+
+ /* Jumpers */
+
+ /** Jumps a character and consumes it
+ * @return true only if the correct character has been jumped */
+ final def jump(ch: Char): Boolean = {
+ if (char == ch) {
+ nextChar()
+ true
+ }
+ else false
+ }
+
+ /** Jumps all the characters in chars, consuming them in the process.
+ * @return true only if the correct characters have been jumped
+ */
+ final def jump(chars: String): Boolean = {
+ var index = 0
+ while (index < chars.length && char == chars.charAt(index) && char != endOfText) {
+ nextChar()
+ index += 1
+ }
+ index == chars.length
+ }
+
+ final def repeatJump(c: Char, max: Int = Int.MaxValue): Int = {
+ var count = 0
+ while (jump(c) && count < max)
+ count += 1
+ count
+ }
+
+ final def jumpUntil(ch: Char): Int = {
+ var count = 0
+ while (char != ch && char != endOfText) {
+ nextChar()
+ count += 1
+ }
+ count
+ }
+
+ final def jumpUntil(pred: => Boolean): Int = {
+ var count = 0
+ while (!pred && char != endOfText) {
+ nextChar()
+ count += 1
+ }
+ count
+ }
+
+ def jumpWhitespace() = jumpUntil(!isWhitespace(char))
+
+ def jumpWhitespaceOrNewLine() = jumpUntil(!isWhitespaceOrNewLine(char))
+
+
+ /* Readers */
+ final def readUntil(c: Char): String = {
+ withRead {
+ while (char != c && char != endOfText) {
+ nextChar()
+ }
+ }
+ }
+
+ final def readUntil(chars: String): String = {
+ assert(chars.length > 0)
+ withRead {
+ val c = chars.charAt(0)
+ while (!check(chars) && char != endOfText) {
+ nextChar()
+ while (char != c && char != endOfText)
+ nextChar()
+ }
+ }
+ }
+
+ final def readUntil(pred: => Boolean): String = {
+ withRead {
+ while (char != endOfText && !pred) {
+ nextChar()
+ }
+ }
+ }
+
+ private def withRead(read: => Unit): String = {
+ val start = offset
+ read
+ buffer.substring(start, offset)
+ }
+
+ /* Chars classes */
+ def isWhitespace(c: Char) = c == ' ' || c == '\t'
+
+ def isWhitespaceOrNewLine(c: Char) = isWhitespace(c) || c == '\n'
+ }
+}
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentRegex.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentRegex.scala
new file mode 100644
index 000000000..2d75b0c66
--- /dev/null
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentRegex.scala
@@ -0,0 +1,84 @@
+package dotty.tools.dottydoc
+package model
+package comment
+
+import scala.util.matching.Regex
+
+object Regexes {
+ val TrailingWhitespace = """\s+$""".r
+
+ /** The body of a line, dropping the (optional) start star-marker,
+ * one leading whitespace and all trailing whitespace
+ */
+ val CleanCommentLine =
+ new Regex("""(?:\s*\*\s?)?(.*)""")
+
+ /** Dangerous HTML tags that should be replaced by something safer,
+ * such as wiki syntax, or that should be dropped
+ */
+ val DangerousTags =
+ new Regex("""<(/?(div|ol|ul|li|h[1-6]|p))( [^>]*)?/?>|<!--.*-->""")
+
+ /** Javadoc tags that should be replaced by something useful, such as wiki
+ * syntax, or that should be dropped. */
+ val JavadocTags =
+ new Regex("""\{\@(code|docRoot|linkplain|link|literal|value)\p{Zs}*([^}]*)\}""")
+
+ /** Maps a javadoc tag to a useful wiki replacement, or an empty string if it cannot be salvaged. */
+ def javadocReplacement(mtch: Regex.Match): String = {
+ mtch.group(1) match {
+ case "code" => "<code>" + mtch.group(2) + "</code>"
+ case "docRoot" => ""
+ case "link" => "`[[" + mtch.group(2) + "]]`"
+ case "linkplain" => "[[" + mtch.group(2) + "]]"
+ case "literal" => "`" + mtch.group(2) + "`"
+ case "value" => "`" + mtch.group(2) + "`"
+ case _ => ""
+ }
+ }
+
+ /** Maps a dangerous HTML tag to a safe wiki replacement, or an empty string
+ * if it cannot be salvaged. */
+ def htmlReplacement(mtch: Regex.Match): String = mtch.group(1) match {
+ case "p" | "div" => "\n\n"
+ case "h1" => "\n= "
+ case "/h1" => " =\n"
+ case "h2" => "\n== "
+ case "/h2" => " ==\n"
+ case "h3" => "\n=== "
+ case "/h3" => " ===\n"
+ case "h4" | "h5" | "h6" => "\n==== "
+ case "/h4" | "/h5" | "/h6" => " ====\n"
+ case "li" => "\n * - "
+ case _ => ""
+ }
+
+ /** Safe HTML tags that can be kept. */
+ val SafeTags =
+ new Regex("""((&\w+;)|(&#\d+;)|(</?(abbr|acronym|address|area|a|bdo|big|blockquote|br|button|b|caption|cite|code|col|colgroup|dd|del|dfn|em|fieldset|form|hr|img|input|ins|i|kbd|label|legend|link|map|object|optgroup|option|param|pre|q|samp|select|small|span|strong|sub|sup|table|tbody|td|textarea|tfoot|th|thead|tr|tt|var)( [^>]*)?/?>))""")
+
+ val safeTagMarker = '\u000E'
+ val endOfLine = '\u000A'
+ val endOfText = '\u0003'
+
+ /** A Scaladoc tag not linked to a symbol and not followed by text */
+ val SingleTagRegex =
+ new Regex("""\s*@(\S+)\s*""")
+
+ /** A Scaladoc tag not linked to a symbol. Returns the name of the tag, and the rest of the line. */
+ val SimpleTagRegex =
+ new Regex("""\s*@(\S+)\s+(.*)""")
+
+ /** A Scaladoc tag linked to a symbol. Returns the name of the tag, the name
+ * of the symbol, and the rest of the line. */
+ val SymbolTagRegex =
+ new Regex("""\s*@(param|tparam|throws|groupdesc|groupname|groupprio)\s+(\S*)\s*(.*)""")
+
+ /** The start of a Scaladoc code block */
+ val CodeBlockStartRegex =
+ new Regex("""(.*?)((?:\{\{\{)|(?:\u000E<pre(?: [^>]*)?>\u000E))(.*)""")
+
+ /** The end of a Scaladoc code block */
+ val CodeBlockEndRegex =
+ new Regex("""(.*?)((?:\}\}\})|(?:\u000E</pre>\u000E))(.*)""")
+}
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/util/IndexWriters.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/util/IndexWriters.scala
new file mode 100644
index 000000000..4f9a87949
--- /dev/null
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/util/IndexWriters.scala
@@ -0,0 +1,43 @@
+package dotty.tools.dottydoc
+package util
+
+object IndexWriters {
+ import prickle._
+ import model.Entities._
+ import model.Html._
+
+ def writeJs(packs: Map[String, Package], outPath: String): Unit = {
+ for ((_, pack) <- packs) {
+ println(s"""Writing '${pack.path.mkString(".")}'""")
+ writeFile(
+ entityHtml(pack),
+ outPath + pack.path.tail.mkString("/", "/", "/"),
+ "index.html")
+
+ for (child <- pack.children) {
+ println(s"""Writing '${child.path.mkString(".")}'""")
+ writeFile(
+ entityHtml(child),
+ outPath + child.path.dropRight(1).mkString("/", "/", "/"),
+ child.path.last + ".html")
+ }
+ }
+
+ val pickled = Pickle.intoString(packs)
+ val js = "Index = {}; Index.packages = " + pickled + ";"
+ println("Writing index.js...")
+ writeFile(js, outPath + "/../", "index.js")
+ println("Done writing static material, building js-app")
+
+ }
+
+ def writeFile(str: String, path: String, file: String): Unit = {
+ def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) {
+ val p = new java.io.PrintWriter(f)
+ try { op(p) } finally { p.close() }
+ }
+
+ new java.io.File(path).mkdirs()
+ printToFile(new java.io.File(path + file))(_.println(str))
+ }
+}