diff options
author | Kato Kazuyoshi <kato.kazuyoshi@gmail.com> | 2011-10-04 15:18:54 +0000 |
---|---|---|
committer | Kato Kazuyoshi <kato.kazuyoshi@gmail.com> | 2011-10-04 15:18:54 +0000 |
commit | d1a7af8e27227c7625762fe10560e70853f6529a (patch) | |
tree | 2b3aa9f2d4558e3c45f685f6a29d1c95d5d5234e | |
parent | 5d283f3f68a6b855afbfc01df784cd972f5b4ced (diff) | |
download | scala-d1a7af8e27227c7625762fe10560e70853f6529a.tar.gz scala-d1a7af8e27227c7625762fe10560e70853f6529a.tar.bz2 scala-d1a7af8e27227c7625762fe10560e70853f6529a.zip |
Add a small "template engine" and separete HTML...
Add a small "template engine" and separete HTML from Scaladoc's source
code. Review by ureche.
8 files changed, 302 insertions, 183 deletions
diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala index b58c71eaa9..c5b3d6173c 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala @@ -28,18 +28,25 @@ abstract class HtmlPage extends Page { thisPage => /** The body of this page. */ def body: NodeSeq + def resourceStream(path: String) = + getClass.getResourceAsStream("/scala/tools/nsc/doc/html/resource/" + path) + def writeFor(site: HtmlFactory) { val doctype = DocType("html", PublicID("-//W3C//DTD XHTML 1.1//EN", "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"), Nil) - val html = - <html> - <head> - <title>{ title }</title> - <meta http-equiv="content-type" content={ "text/html; charset=" + site.encoding }/> - { headers } - </head> - { body } - </html> + val html = { + val engine = + new TemplateEngine(XML.load(resourceStream("template/htmlpage.html"))) + + engine.render( + "meta-encoding" -> <meta http-equiv="content-type" content={ + "text/html; charset=" + site.encoding + }/>, + "title" -> title, + "headers" -> headers, + "body" -> body + ) + } val fos = createFileOutputStream(site) val w = Channels.newWriter(fos.getChannel, site.encoding) try { diff --git a/src/compiler/scala/tools/nsc/doc/html/TemplateEngine.scala b/src/compiler/scala/tools/nsc/doc/html/TemplateEngine.scala new file mode 100644 index 0000000000..dc8c867ef5 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/html/TemplateEngine.scala @@ -0,0 +1,59 @@ +package scala.tools.nsc.doc.html + +import scala.xml._ +import scala.util.matching.Regex + +class TemplateEngine(element: Elem) { + val Pattern = new Regex("\\{\\{([A-Za-z]+)\\}\\}") + + private def renderMetaDataValue(value: Seq[Node], pairs: Map[String, Any]) = { + def f(m: Regex.Match) = + if (m.group(1) == null) { + "" + } else { + pairs.getOrElse(m.group(1), "").toString + } + + value.map { + n => Text(Pattern.replaceAllIn(n.mkString, f _)) + } + } + + private def renderMetaData(list: MetaData, pairs: Map[String, Any]): MetaData = { + list match { + case Null => list + case unprefixed: UnprefixedAttribute => + new UnprefixedAttribute(unprefixed.key, + renderMetaDataValue(unprefixed.value, pairs), + renderMetaData(unprefixed.next, pairs)) + case prefixed: PrefixedAttribute => + new PrefixedAttribute(prefixed.pre, prefixed.key, + renderMetaDataValue(prefixed.value, pairs), + renderMetaData(prefixed.next, pairs)) + } + } + + private def renderNode(node: Node, pairs: Map[String, Any]): Node = + node match { + case v @ <val/> => { + v.attributes.get("name") match { + case Some(s) => pairs.get(s.mkString) match { + case Some(n: Node) => n + case any => Text(any.mkString) + } + case None => Text("") + } + } + case e: Elem => { + val child = e.child.map(c => renderNode(c, pairs)) + Elem(e.prefix, e.label, + renderMetaData(e.attributes, pairs), + e.scope, child: _*) + } + case any => any + } + + def render(pairs: Map[String, Any]) = renderNode(element, pairs) + def render(pairs: (String, Any)*): Node = + render(Map[String, Any](pairs : _*)) +} diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala index 346780147e..f2fc8caaf6 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala @@ -34,79 +34,62 @@ class Index(universe: doc.Universe, index: doc.Index) extends HtmlPage { <script type="text/javascript" src={ relativeLinkTo{List("scheduler.js", "lib")} }></script> </xml:group> - val body = - <body> - <div id="library"> - <img class='class icon' src={ relativeLinkTo{List("class.png", "lib")} }/> - <img class='trait icon' src={ relativeLinkTo{List("trait.png", "lib")} }/> - <img class='object icon' src={ relativeLinkTo{List("object.png", "lib")} }/> - <img class='package icon' src={ relativeLinkTo{List("package.png", "lib")} }/> - </div> - { browser } - <div id="content" class="ui-layout-center"> - <iframe name="template" src={ relativeLinkTo{List("package.html")} }/> - </div> - </body> - - def browser = - <div id="browser" class="ui-layout-west"> - <div class="ui-west-center"> - <div id="filter"></div> - <div class="pack" id="tpl">{ - def packageElem(pack: model.Package): NodeSeq = { - <xml:group> - { if (!pack.isRootPackage) - <a class="tplshow" href={ relativeLinkTo(pack) } target="template">{ pack.qualifiedName }</a> - else NodeSeq.Empty - } - <ol class="templates">{ - val tpls: Map[String, Seq[DocTemplateEntity]] = - (pack.templates filter (t => !t.isPackage && !isExcluded(t) )) groupBy (_.name) - - val placeholderSeq: NodeSeq = <div class="placeholder"></div> - - def createLink(entity: DocTemplateEntity, includePlaceholder: Boolean, includeText: Boolean) = { - val entityType = docEntityKindToString(entity) - val linkContent = ( - { if (includePlaceholder) placeholderSeq else NodeSeq.Empty } - ++ - { if (includeText) <span class="tplLink">{ Text(packageQualifiedName(entity)) }</span> else NodeSeq.Empty } - ) - <a class="tplshow" href={ relativeLinkTo(entity) } target="template"><span class={ entityType }>({ Text(entityType) })</span>{ linkContent }</a> - } - - for (tn <- tpls.keySet.toSeq sortBy (_.toLowerCase)) yield { - val entities = tpls(tn) - val row = (entities find (e => e.isPackage || e.isObject), entities find (e => e.isTrait || e.isClass)) - - val itemContents = row match { - case (Some(obj), None) => createLink(obj, includePlaceholder = true, includeText = true) + val body = { + val engine = + new TemplateEngine(XML.load(resourceStream("template/index.html"))) + engine.render("root" -> packageElem(universe.rootPackage)) + } - case (maybeObj, Some(template)) => - val firstLink = maybeObj match { - case Some(obj) => createLink(obj, includePlaceholder = false, includeText = false) - case None => placeholderSeq - } + def packageElem(pack: model.Package): NodeSeq = { + <xml:group>{ + if (!pack.isRootPackage) + <a class="tplshow" href={ relativeLinkTo(pack) } target="template">{ pack.qualifiedName }</a> + else NodeSeq.Empty + }<ol class="templates">{ + val tpls: Map[String, Seq[DocTemplateEntity]] = + (pack.templates filter (t => !t.isPackage && !isExcluded(t) )) groupBy (_.name) + + val placeholderSeq: NodeSeq = <div class="placeholder"></div> + + def createLink(entity: DocTemplateEntity, includePlaceholder: Boolean, includeText: Boolean) = { + val entityType = docEntityKindToString(entity) + val linkContent = ( + { if (includePlaceholder) placeholderSeq else NodeSeq.Empty } + ++ + { if (includeText) <span class="tplLink">{ Text(packageQualifiedName(entity)) }</span> else NodeSeq.Empty } + ) + <a class="tplshow" href={ relativeLinkTo(entity) } target="template"><span class={ entityType }>({ Text(entityType) })</span>{ linkContent }</a> + } + + for (tn <- tpls.keySet.toSeq sortBy (_.toLowerCase)) yield { + val entities = tpls(tn) + val row = (entities find (e => e.isPackage || e.isObject), entities find (e => e.isTrait || e.isClass)) + + val itemContents = row match { + case (Some(obj), None) => createLink(obj, includePlaceholder = true, includeText = true) + + case (maybeObj, Some(template)) => + val firstLink = maybeObj match { + case Some(obj) => createLink(obj, includePlaceholder = false, includeText = false) + case None => placeholderSeq + } - firstLink ++ createLink(template, includePlaceholder = false, includeText = true) + firstLink ++ createLink(template, includePlaceholder = false, includeText = true) - case _ => // FIXME: this default case should not be necessary. For some reason AnyRef is not a package, object, trait, or class - val entry = entities.head - placeholderSeq ++ createLink(entry, includePlaceholder = false, includeText = true) - } + case _ => // FIXME: this default case should not be necessary. For some reason AnyRef is not a package, object, trait, or class + val entry = entities.head + placeholderSeq ++ createLink(entry, includePlaceholder = false, includeText = true) + } - <li title={ entities.head.qualifiedName }>{ itemContents }</li> - } - }</ol> - <ol class="packages"> { - for (sp <- pack.packages sortBy (_.name.toLowerCase)) yield - <li class="pack" title={ sp.qualifiedName }>{ packageElem(sp) }</li> - }</ol> - </xml:group> - } - packageElem(universe.rootPackage) - }</div></div><script src="index.js"></script> - </div> + <li title={ entities.head.qualifiedName }>{ itemContents }</li> + } + }</ol> + <ol class="packages"> { + for (sp <- pack.packages sortBy (_.name.toLowerCase)) yield + <li class="pack" title={ sp.qualifiedName }>{ packageElem(sp) }</li> + }</ol> + </xml:group> + } def packageQualifiedName(ety: DocTemplateEntity): String = if (ety.inTemplate.isPackage) ety.name diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index d152c3e2bf..03fe5b4267 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -9,7 +9,7 @@ package html package page import model._ -import scala.xml.{ NodeSeq, Text } +import scala.xml.{ NodeSeq, Text, XML } class Template(tpl: DocTemplateEntity) extends HtmlPage { @@ -63,121 +63,115 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage { <p id="owner">{ templatesToHtml(tpl.inTemplate.toRoot.reverse.tail, xml.Text(".")) }</p> } - <body class={ if (tpl.isTrait || tpl.isClass || tpl.qualifiedName == "scala.AnyRef") "type" else "value" } - onload={ "sh_highlightDocument('../lib/', '.min.js');" }> - <div id="definition"> - { - tpl.companion match { - case Some(companion) if (companion.visibility.isPublic && companion.inSource != None) => - <a href={relativeLinkTo(companion)} title="Go to companion"><img src={ relativeLinkTo(List(docEntityKindToBigImage(tpl), "lib")) }/></a> - case _ => - <img src={ relativeLinkTo(List(docEntityKindToBigImage(tpl), "lib")) }/> - }} - { owner } - <h1>{ displayName }</h1> - </div> + val bodyClass = if (tpl.isTrait || tpl.isClass || tpl.qualifiedName == "scala.AnyRef") "type" else "value" - { signature(tpl, true) } - { memberToCommentHtml(tpl, true) } - - <div id="mbrsel"> - <div id='textfilter'><span class='pre'/><span class='input'><input type='text' accesskey='/'/></span><span class='post'/></div> - { if (tpl.linearizationTemplates.isEmpty) NodeSeq.Empty else - <div id="order"> - <span class="filtertype">Ordering</span> - <ol><li class="alpha in"><span>Alphabetic</span></li><li class="inherit out"><span>By inheritance</span></li></ol> - </div> - } - { if (tpl.linearizationTemplates.isEmpty) NodeSeq.Empty else - <div id="ancestors"> - <span class="filtertype">Inherited</span> - <ol><li class="hideall out"><span>Hide All</span></li> - <li class="showall in"><span>Show all</span></li></ol> - <ol id="linearization">{ - (tpl :: tpl.linearizationTemplates) map { wte => <li class="in" name={ wte.qualifiedName }><span>{ wte.name }</span></li> } - }</ol> - </div> - } - { - <div id="visbl"> - <span class="filtertype">Visibility</span> - <ol><li class="public in"><span>Public</span></li><li class="all out"><span>All</span></li></ol> - </div> - } - </div> + val icon = tpl.companion match { + case Some(companion) if (companion.visibility.isPublic && companion.inSource != None) => + <a href={relativeLinkTo(companion)} title="Go to companion"><img src={ relativeLinkTo(List(docEntityKindToBigImage(tpl), "lib")) }/></a> + case _ => + <img src={ relativeLinkTo(List(docEntityKindToBigImage(tpl), "lib")) }/> + } - <div id="template"> - <div id="allMembers"> - { if (constructors.isEmpty) NodeSeq.Empty else - <div id="constructors" class="members"> - <h3>Instance Constructors</h3> - <ol>{ constructors map (memberToHtml(_)) }</ol> - </div> - } + val b = <body onload={ "sh_highlightDocument('../lib/', '.min.js');" }> + </body> - { if (typeMembers.isEmpty) NodeSeq.Empty else - <div id="types" class="types members"> - <h3>Type Members</h3> - <ol>{ typeMembers map (memberToHtml(_)) }</ol> - </div> - } + val order = if (tpl.linearizationTemplates.isEmpty) NodeSeq.Empty else + <div id="order"> + <span class="filtertype">Ordering</span> + <ol><li class="alpha in"><span>Alphabetic</span></li><li class="inherit out"><span>By inheritance</span></li></ol> + </div> - { if (absValueMembers.isEmpty) NodeSeq.Empty else - <div id="values" class="values members"> - <h3>Abstract Value Members</h3> - <ol>{ absValueMembers map (memberToHtml(_)) }</ol> - </div> - } + val ancestors = if (tpl.linearizationTemplates.isEmpty) NodeSeq.Empty else + <div id="ancestors"> + <span class="filtertype">Inherited</span> + <ol> + <li class="hideall out"><span>Hide All</span></li> + <li class="showall in"><span>Show all</span></li> + </ol> + <ol id="linearization">{ + (tpl :: tpl.linearizationTemplates) map { wte => + <li class="in" name={ wte.qualifiedName }><span>{ + wte.name + }</span></li> } + }</ol> + </div> - { if (concValueMembers.isEmpty) NodeSeq.Empty else - <div id="values" class="values members"> - <h3>{ if (absValueMembers.isEmpty) "Value Members" else "Concrete Value Members" }</h3> - <ol>{ concValueMembers map (memberToHtml(_)) }</ol> - </div> - } + val constructorsElem = if (constructors.isEmpty) NodeSeq.Empty else + <div id="constructors" class="members"> + <h3>Instance Constructors</h3> + <ol>{ constructors map (memberToHtml(_)) }</ol> + </div> - { if (deprValueMembers.isEmpty) NodeSeq.Empty else - <div id="values" class="values members"> - <h3>Deprecated Value Members</h3> - <ol>{ deprValueMembers map (memberToHtml(_)) }</ol> - </div> - } - </div> + val types = if (typeMembers.isEmpty) NodeSeq.Empty else + <div id="types" class="types members"> + <h3>Type Members</h3> + <ol>{ typeMembers map (memberToHtml(_)) }</ol> + </div> - <div id="inheritedMembers"> - { - NodeSeq fromSeq (for ((superTpl, superType) <- (tpl.linearizationTemplates zip tpl.linearizationTypes)) yield - <div class="parent" name={ superTpl.qualifiedName }> - <h3>Inherited from { - if (tpl.universe.settings.useStupidTypes.value) - superTpl match { - case dtpl: DocTemplateEntity => - val sig = signature(dtpl, false, true) \ "_" - sig - case tpl: TemplateEntity => - tpl.name - } - else - typeToHtml(superType, true) - }</h3> - </div> - ) - } - </div> + val abstructValues = if (absValueMembers.isEmpty) NodeSeq.Empty else + <div id="values" class="values members"> + <h3>Abstract Value Members</h3> + <ol>{ absValueMembers map (memberToHtml(_)) }</ol> + </div> + val concreteValues = if (concValueMembers.isEmpty) NodeSeq.Empty else + <div id="values" class="values members"> + <h3>{ if (absValueMembers.isEmpty) "Value Members" else "Concrete Value Members" }</h3> + <ol>{ concValueMembers map (memberToHtml(_)) }</ol> </div> - <div id="tooltip" ></div> + val deprecatedValues = if (deprValueMembers.isEmpty) NodeSeq.Empty else + <div id="values" class="values members"> + <h3>Deprecated Value Members</h3> + <ol>{ deprValueMembers map (memberToHtml(_)) }</ol> + </div> - { - if (Set("epfl", "EPFL").contains(tpl.universe.settings.docfooter.value)) - <div id="footer">Scala programming documentation. Copyright (c) 2003-2011 <a href="http://www.epfl.ch" target="_top">EPFL</a>, with contributions from <a href="http://typesafe.com" target="_top">Typesafe</a>.</div> - else - <div id="footer"> { tpl.universe.settings.docfooter.value } </div> + val inheritedMembers = NodeSeq fromSeq ( + for ((superTpl, superType) <- (tpl.linearizationTemplates zip tpl.linearizationTypes)) yield + <div class="parent" name={ superTpl.qualifiedName }> + <h3>Inherited from { + if (tpl.universe.settings.useStupidTypes.value) + superTpl match { + case dtpl: DocTemplateEntity => + val sig = signature(dtpl, false, true) \ "_" + sig + case tpl: TemplateEntity => + tpl.name + } + else + typeToHtml(superType, true) + }</h3> + </div> + ) + + val footer = { + val setting = tpl.universe.settings.docfooter.value + if (Set("epfl", "EPFL").contains(setting)) { + <div id="footer">Scala programming documentation. Copyright (c) 2003-2011 <a href="http://www.epfl.ch" target="_top">EPFL</a>, with contributions from <a href="http://typesafe.com" target="_top">Typesafe</a>.</div> + } else { + <div id="footer"> { setting } </div> } + } - - </body> + val engine = + new TemplateEngine(XML.load(resourceStream("template/template.html"))) + engine.render( + "bodyClass" -> (if (tpl.isTrait || tpl.isClass || tpl.qualifiedName == "scala.AnyRef") "type" else "value"), + "signature" -> signature(tpl, true), + "comment" -> memberToCommentHtml(tpl, true), + "icon" -> icon, + "owner" -> owner, + "displayName" -> displayName, + "order" -> order, + "ancestors" -> ancestors, + "constructors" -> constructorsElem, + "types" -> types, + "abstructValues" -> abstructValues, + "concreteValues" -> concreteValues, + "deprecatedValues" -> deprecatedValues, + "comment" -> memberToCommentHtml(tpl, true), + "footer" -> footer + ) } def boundsToString(hi: Option[TypeEntity], lo: Option[TypeEntity]): String = { diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/template/htmlpage.html b/src/compiler/scala/tools/nsc/doc/html/resource/template/htmlpage.html new file mode 100644 index 0000000000..5c03d92fb9 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/html/resource/template/htmlpage.html @@ -0,0 +1,8 @@ +<html> + <head> + <title><val name="title" /></title> + <val name="meta-encoding" /> + <val name="headers" /> + </head> + <val name="body" /> +</html> diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/template/index.html b/src/compiler/scala/tools/nsc/doc/html/resource/template/index.html new file mode 100644 index 0000000000..14c6724c45 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/html/resource/template/index.html @@ -0,0 +1,12 @@ +<body> + <div id="browser" class="ui-layout-west"> + <div class="ui-west-center"> + <div id="filter"></div> + <div class="pack" id="tpl"><val name="root" /></div> + </div> + <script src="index.js"></script> + </div> + <div id="content" class="ui-layout-center"> + <iframe name="template" src="package.html"></iframe> + </div> +</body> diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/template/template.html b/src/compiler/scala/tools/nsc/doc/html/resource/template/template.html new file mode 100644 index 0000000000..789ba7edf4 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/html/resource/template/template.html @@ -0,0 +1,39 @@ +<body class="{{bodyClass}}" + onload="sh_highlightDocument('../lib/', '.min.js');"> + <div id="definition"> + <val name="icon" /> + <val name="owner" /> + <h1><val name="displayName" /></h1> + </div> + <val name="signature" /> + <val name="comment" /> + <div id="mbrsel"> + <div id="textfilter"> + <span class="pre"/> + <span class="input"> + <input type="text" accesskey="/"/> + </span> + <span class="post"/> + </div> + + <val name="order" /> + <val name="ancestors" /> + + <div id="visbl"> + <span class="filtertype">Visibility</span> + <ol><li class="public in"><span>Public</span></li><li class="all out"><span>All</span></li></ol> + </div> + </div> + <div id="template"> + <div id="allMembers"> + <val name="constructors" /> + <val name="types" /> + <val name="abstractValues" /> + <val name="concreteValues" /> + <val name="deprecatedValues" /> + </div> + <div id="inheritedMembers"><val name="inheritedMembers" /></div> + </div> + <div id="tooltip" ></div> + <val name="footer" /> +</body> diff --git a/test/scaladoc/scala/IndexTest.scala b/test/scaladoc/scala/IndexTest.scala index 7679bab0c6..ec8011bf41 100644 --- a/test/scaladoc/scala/IndexTest.scala +++ b/test/scaladoc/scala/IndexTest.scala @@ -5,6 +5,23 @@ import scala.tools.nsc.doc import scala.tools.nsc.doc.html.page.Index import java.net.URLClassLoader +object XMLUtil { + import scala.xml._ + + def stripGroup(seq: Node): Node = { + seq match { + case group: Group => { + <div class="group">{ group.nodes.map(stripGroup _) }</div> + } + case e: Elem => { + val child = e.child.map(stripGroup _) + Elem(e.prefix, e.label, e.attributes, e.scope, child : _*) + } + case _ => seq + } + } +} + object Test extends Properties("Index") { def getClasspath = { @@ -71,10 +88,10 @@ object Test extends Properties("Index") { case None => false } } - property("browser contants a script element") = { + property("body contants a script element") = { createIndex("src/compiler/scala/tools/nsc/doc/html/page/Index.scala") match { case Some(index) => - (index.browser \ "script").size == 1 + (XMLUtil.stripGroup(index.body) \\ "script").size == 1 case None => false } |