From 3ca75587df0db94bc4f1a220af93eea35b2828d8 Mon Sep 17 00:00:00 2001 From: Gilles Dubochet Date: Fri, 21 Jan 2011 17:31:29 +0000 Subject: [scaladoc] First attempt at modularisation of S... [scaladoc] First attempt at modularisation of Scaladoc generator. Option "docgenerator" controls which generation backend is called. No review for now; needs a discussion. --- src/compiler/scala/tools/ant/Scaladoc.scala | 13 ++++ src/compiler/scala/tools/nsc/doc/DocFactory.scala | 37 +++++++-- src/compiler/scala/tools/nsc/doc/Index.scala | 12 +++ src/compiler/scala/tools/nsc/doc/Settings.scala | 48 ++++++++++-- .../scala/tools/nsc/doc/doclet/Generator.scala | 24 ++++++ .../scala/tools/nsc/doc/doclet/Indexer.scala | 21 +++++ .../scala/tools/nsc/doc/doclet/Universer.scala | 21 +++++ src/compiler/scala/tools/nsc/doc/html/Doclet.scala | 13 ++++ .../scala/tools/nsc/doc/html/HtmlFactory.scala | 8 +- .../scala/tools/nsc/doc/html/page/Index.scala | 4 +- .../tools/nsc/doc/html/page/ReferenceIndex.scala | 5 +- .../scala/tools/nsc/doc/html/page/Template.scala | 19 ++++- .../tools/nsc/doc/html/resource/lib/template.css | 2 +- .../tools/nsc/doc/model/IndexModelFactory.scala | 90 ++++++++++++---------- 14 files changed, 248 insertions(+), 69 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/doc/Index.scala create mode 100644 src/compiler/scala/tools/nsc/doc/doclet/Generator.scala create mode 100644 src/compiler/scala/tools/nsc/doc/doclet/Indexer.scala create mode 100644 src/compiler/scala/tools/nsc/doc/doclet/Universer.scala create mode 100644 src/compiler/scala/tools/nsc/doc/html/Doclet.scala (limited to 'src') diff --git a/src/compiler/scala/tools/ant/Scaladoc.scala b/src/compiler/scala/tools/ant/Scaladoc.scala index 78e73522a4..727b8db0cc 100644 --- a/src/compiler/scala/tools/ant/Scaladoc.scala +++ b/src/compiler/scala/tools/ant/Scaladoc.scala @@ -45,6 +45,7 @@ import scala.tools.nsc.reporters.{Reporter, ConsoleReporter} *
  • bottom,
  • *
  • addparams,
  • *
  • deprecation,
  • + *
  • docgenerator,
  • *
  • unchecked.
  • * *

    @@ -99,6 +100,9 @@ class Scaladoc extends ScalaMatchingTask { /** The character encoding of the files to compile. */ private var encoding: Option[String] = None + /** The fully qualified name of a doclet class, which will be used to generate the documentation. */ + private var docgenerator: Option[String] = None + /** The document title of the generated HTML documentation. */ private var doctitle: Option[String] = None @@ -265,6 +269,14 @@ class Scaladoc extends ScalaMatchingTask { encoding = Some(input) } + /** Sets the docgenerator attribute. + * + * @param input A fully qualified class name of a doclet. + */ + def setDocgenerator(input: String) { + docgenerator = Some(input) + } + /** Sets the docversion attribute. * * @param input The value of docversion. @@ -508,6 +520,7 @@ class Scaladoc extends ScalaMatchingTask { if (!docsourceurl.isEmpty) docSettings.docsourceurl.value =decodeEscapes(docsourceurl.get) docSettings.deprecation.value = deprecation docSettings.unchecked.value = unchecked + if (!docgenerator.isEmpty) docSettings.docgenerator.value = docgenerator.get log("Scaladoc params = '" + addParams + "'", Project.MSG_DEBUG) docSettings processArgumentString addParams diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala index 62e84861f2..13a2fe0508 100644 --- a/src/compiler/scala/tools/nsc/doc/DocFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/DocFactory.scala @@ -1,10 +1,11 @@ /* NSC -- new Scala compiler -- Copyright 2007-2011 LAMP/EPFL */ - package scala.tools.nsc package doc import reporters.Reporter +import util.NoPosition +import java.lang.ClassNotFoundException /** A documentation processor controls the process of generating Scala documentation, which is as follows. * @@ -46,7 +47,7 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor /** Creates a scaladoc site for all symbols defined in this call's `files`, as well as those defined in `files` of * previous calls to the same processor. * @param files The list of paths (relative to the compiler's source path, or absolute) of files to document. */ - def universe(files: List[String]): Option[Universe] = { + def makeUniverse(files: List[String]): Option[Universe] = { (new compiler.Run()) compile files compiler.addSourceless assert(settings.docformat.value == "html") @@ -61,9 +62,35 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor /** Generate document(s) for all `files` containing scaladoc documenataion. * @param files The list of paths (relative to the compiler's source path, or absolute) of files to document. */ - def document(files: List[String]): Unit = - universe(files) foreach { docModel => - new html.HtmlFactory(docModel, new model.IndexModelFactory makeModel(docModel)) generate + def document(files: List[String]): Unit = { + + class NoCompilerRunException extends Exception + + try { + val docletClass = Class.forName(settings.docgenerator.value) // default is html.Doclet + val docletInstance = docletClass.newInstance().asInstanceOf[doclet.Generator] + if (docletInstance.isInstanceOf[doclet.Universer]) { + makeUniverse(files) match { + case Some(universe) => + docletClass.getMethod("setUniverse", classOf[Universe]).invoke(docletInstance, universe) + if (docletInstance.isInstanceOf[doclet.Indexer]) { + val index = model.IndexModelFactory.makeIndex(universe) + docletClass.getMethod("setIndex", classOf[Index]).invoke(docletInstance, index) + } + case None => + throw new NoCompilerRunException() + } + } + docletInstance.generate + } + catch { + case e: ClassNotFoundException => + case e: NoCompilerRunException => + reporter.info(NoPosition, "No documentation generated with unsucessful compiler run", false) } + + + } + } diff --git a/src/compiler/scala/tools/nsc/doc/Index.scala b/src/compiler/scala/tools/nsc/doc/Index.scala new file mode 100644 index 0000000000..e845f8e909 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/Index.scala @@ -0,0 +1,12 @@ +package scala.tools.nsc.doc + +import scala.collection._ + + +trait Index { + + type SymbolMap = SortedMap[String, SortedSet[model.TemplateEntity]] + + def firstLetterIndex: Map[Char, SymbolMap] + +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index 238803a485..054d16719b 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -15,26 +15,58 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) { /** A setting that defines in which format the documentation is output. ''Note:'' this setting is currently always * `html`. */ - val docformat = ChoiceSetting ("-doc-format", "format", "Selects in which format documentation is rendered", List("html"), "html") + val docformat = ChoiceSetting ( + "-doc-format", + "format", + "Selects in which format documentation is rendered", + List("html"), + "html" + ) /** A setting that defines the overall title of the documentation, typically the name of the library being - * documented. 'Note:'' This setting is currently not used. */ - val doctitle = StringSetting ("-doc-title", "doc-title", "The overall name of the Scaladoc site", "") + * documented. ''Note:'' This setting is currently not used. */ + val doctitle = StringSetting ( + "-doc-title", + "title", + "The overall name of the Scaladoc site", + "" + ) /** A setting that defines the overall version number of the documentation, typically the version of the library being - * documented. 'Note:'' This setting is currently not used. */ - val docversion = StringSetting ("-doc-version", "doc-version", "An optional version number, to be appended to the title", "") + * documented. ''Note:'' This setting is currently not used. */ + val docversion = StringSetting ( + "-doc-version", + "version", + "An optional version number, to be appended to the title", + "" + ) /** A setting that defines a URL to be concatenated with source locations and show a link to source files. * If needed the sourcepath option can be used to exclude undesired initial part of the link to sources */ - val docsourceurl = StringSetting ("-doc-source-url", "url", "A URL pattern used to build links to template sources; use variables, for example: €{TPL_NAME} ('Seq'), €{TPL_OWNER} ('scala.collection'), €{FILE_PATH} ('scala/collection/Seq')", "") + val docsourceurl = StringSetting ( + "-doc-source-url", + "url", + "A URL pattern used to build links to template sources; use variables, for example: €{TPL_NAME} ('Seq'), €{TPL_OWNER} ('scala.collection'), €{FILE_PATH} ('scala/collection/Seq')", + "" + ) - val useStupidTypes = BooleanSetting ("-Yuse-stupid-types", "Print the types of inherited members as seen from their original definition context. Hint: you don't want to do that!") + val useStupidTypes = BooleanSetting ( + "-Yuse-stupid-types", + "Print the types of inherited members as seen from their original definition context. Hint: you don't want to do that!" + ) + + val docgenerator = StringSetting ( + "-doc-generator", + "class-name", + "The fully qualified name of a doclet class, which will be used to generate the documentation", + "scala.tools.nsc.doc.html.Doclet" + ) + + // TODO: add a new setting for whether or not to document sourceless entities (e.g., Any, Unit, etc) // Somewhere slightly before r18708 scaladoc stopped building unless the // self-type check was suppressed. I hijacked the slotted-for-removal-anyway // suppress-vt-warnings option and renamed it for this purpose. noSelfCheck.value = true - // TODO: add a new setting for whether or not to document sourceless entities (e.g., Any, Unit, etc) } diff --git a/src/compiler/scala/tools/nsc/doc/doclet/Generator.scala b/src/compiler/scala/tools/nsc/doc/doclet/Generator.scala new file mode 100644 index 0000000000..6e4986d7ec --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/doclet/Generator.scala @@ -0,0 +1,24 @@ +package scala.tools.nsc.doc +package doclet + +import scala.collection._ + +/** Hook into the documentation generation process. The Doclet receives a model of the code being generated, and + * can then do whatever it wants with it. */ +abstract class Generator { + + val checks: mutable.Set[()=>Boolean] = + mutable.Set.empty[()=>Boolean] + + /** Called after the model of the generated documentation is created */ + def generate: Unit = { + assert(checks forall { check => check() }) + generateImpl + } + + def generateImpl: Unit + +} + + + diff --git a/src/compiler/scala/tools/nsc/doc/doclet/Indexer.scala b/src/compiler/scala/tools/nsc/doc/doclet/Indexer.scala new file mode 100644 index 0000000000..e58893e3b4 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/doclet/Indexer.scala @@ -0,0 +1,21 @@ +package scala.tools.nsc +package doc +package doclet + +/** A `Generator` may implement the `Indexer` trait to gain access to pre-calculated indexing information */ +trait Indexer extends Generator { + + protected var indexField: Index = null + + def index: Index = indexField + + def setIndex(i: Index) { + assert(indexField == null) + indexField = i + } + + checks += { () => + indexField != null + } + +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/doclet/Universer.scala b/src/compiler/scala/tools/nsc/doc/doclet/Universer.scala new file mode 100644 index 0000000000..ee8b7809e5 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/doclet/Universer.scala @@ -0,0 +1,21 @@ +package scala.tools.nsc +package doc +package doclet + +/** A `Generator` may implement the `Universer` trait to gain access to a model of the documented program */ +trait Universer extends Generator { + + protected var universeField: Universe = null + + def universe: Universe = universeField + + def setUniverse(u: Universe) { + assert(universeField == null) + universeField = u + } + + checks += { () => + universeField != null + } + +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/html/Doclet.scala b/src/compiler/scala/tools/nsc/doc/html/Doclet.scala new file mode 100644 index 0000000000..a16fc920c6 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/html/Doclet.scala @@ -0,0 +1,13 @@ +package scala.tools.nsc.doc +package html + +import doclet._ + +/** * The default doclet used by the scaladoc command line tool when no user-provided doclet is provided. */ +class Doclet extends Generator with Universer with Indexer { + + def generateImpl { + new html.HtmlFactory(universe, index).generate + } + +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala index 3fe8364c96..199c184bfc 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala @@ -16,7 +16,7 @@ import scala.collection._ /** A class that can generate Scaladoc sites to some fixed root folder. * @author David Bernard * @author Gilles Dubochet */ -class HtmlFactory(val universe: Universe, indexModel: IndexModelFactory#IndexModel) { +class HtmlFactory(val universe: doc.Universe, index: doc.Index) { /** The character encoding to be used for generated Scaladoc sites. This value is currently always UTF-8. */ def encoding: String = "UTF-8" @@ -62,7 +62,7 @@ class HtmlFactory(val universe: Universe, indexModel: IndexModelFactory#IndexMod copyResource("lib/filter_box_right.png") copyResource("lib/remove.png") - new page.Index(universe,indexModel) writeFor this + new page.Index(universe, index) writeFor this val written = mutable.HashSet.empty[DocTemplateEntity] @@ -75,8 +75,8 @@ class HtmlFactory(val universe: Universe, indexModel: IndexModelFactory#IndexMod writeTemplate(universe.rootPackage) - for(letter <- indexModel) { - new html.page.ReferenceIndex(letter._1,indexModel, universe) writeFor this + for(letter <- index.firstLetterIndex) { + new html.page.ReferenceIndex(letter._1, index, universe) writeFor this } } 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 9e7842912e..cacbd3afc6 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala @@ -14,7 +14,7 @@ import scala.collection._ import scala.xml._ import scala.util.parsing.json.{JSONObject, JSONArray} -class Index(universe: Universe, indexModel: IndexModelFactory#IndexModel) extends HtmlPage { +class Index(universe: doc.Universe, index: doc.Index) extends HtmlPage { def path = List("index.html") @@ -68,7 +68,7 @@ class Index(universe: Universe, indexModel: IndexModelFactory#IndexModel) extend

    {
    - { for(l <- indexModel.keySet.toList.sortBy( _.toString )) yield { // TODO there should be a better way to do that + { for(l <- index.firstLetterIndex.keySet.toList.sortBy( _.toString )) yield { // TODO there should be a better way to do that val ch = if(l=='#') "%23" else l // url encoding if needed {l.toUpper} ++ xml.Text(" ") } } diff --git a/src/compiler/scala/tools/nsc/doc/html/page/ReferenceIndex.scala b/src/compiler/scala/tools/nsc/doc/html/page/ReferenceIndex.scala index 920ad3ada7..25e166a782 100755 --- a/src/compiler/scala/tools/nsc/doc/html/page/ReferenceIndex.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/ReferenceIndex.scala @@ -9,7 +9,7 @@ package html package page -class ReferenceIndex(letter: Char, indexModel: model.IndexModelFactory#IndexModel, universe: Universe) extends HtmlPage { +class ReferenceIndex(letter: Char, index: doc.Index, universe: Universe) extends HtmlPage { def path = List("index-"+letter+".html","index") @@ -24,11 +24,10 @@ class ReferenceIndex(letter: Char, indexModel: model.IndexModelFactory#IndexMode - val groupedMembers = indexModel(letter) def body = - { for(groups <- groupedMembers) yield { + { for(groups <- index.firstLetterIndex(letter)) yield {
    { groups._1 }
    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 2f1da592b8..650a0e6e09 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -343,27 +343,38 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage { } ++ { if (mbr.deprecation.isEmpty || isReduced) NodeSeq.Empty else
      deprecated: - {
    1. { bodyToHtml(mbr.deprecation.get) }
    2. } + {
    3. { bodyToHtml(mbr.deprecation.get) }
    4. }
    } ++ { mbr.comment match { case Some(comment) => + { if(!comment.example.isEmpty && !isReduced) +
    Example{ if (comment.example.length > 1) "s" else ""} : +
      { + val exampleXml: List[scala.xml.NodeSeq] = + for(example <- comment.example ) yield +
    1. { bodyToHtml(example) }
    2. + exampleXml.reduceLeft(_ ++ Text(", ") ++ _) + }
    +
    + else NodeSeq.Empty + } { if(!comment.version.isEmpty && !isReduced)
      version - { for(body <- comment.version.toList) yield
    1. {bodyToHtml(body)}
    2. } + { for(body <- comment.version.toList) yield
    3. {bodyToHtml(body)}
    4. }
    else NodeSeq.Empty } { if(!comment.since.isEmpty && !isReduced)
      since - { for(body <- comment.since.toList) yield
    1. {bodyToHtml(body)}
    2. } + { for(body <- comment.since.toList) yield
    3. {bodyToHtml(body)}
    4. }
    else NodeSeq.Empty } { if(!comment.see.isEmpty && !isReduced)
      see also: - { val seeXml:List[scala.xml.NodeSeq]=(for(see <- comment.see ) yield
    1. {bodyToHtml(see)}
    2. ) + { val seeXml:List[scala.xml.NodeSeq]=(for(see <- comment.see ) yield
    3. {bodyToHtml(see)}
    4. ) seeXml.reduceLeft(_ ++ Text(", ") ++ _) }
    diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css index e7b4d672da..890da9aa23 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css @@ -2,7 +2,7 @@ html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, code, +a, abbr, acronym, address, code, pre, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, input, table, caption, tbody, tfoot, thead, tr, th, td { diff --git a/src/compiler/scala/tools/nsc/doc/model/IndexModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/IndexModelFactory.scala index 0d19850cf2..6c599e1a88 100755 --- a/src/compiler/scala/tools/nsc/doc/model/IndexModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/IndexModelFactory.scala @@ -9,52 +9,58 @@ package model import scala.collection._ -class IndexModelFactory { - - /** SortedMap[symbol name, SortedSet[owner template]] */ - type SymbolMap = immutable.SortedMap[String,SortedSet[model.TemplateEntity]] - /** Map[symbol's first letter, SymbolMap] */ - type IndexModel = Map[Char, SymbolMap] - - def makeModel(universe:Universe)={ - import model._ - - val index = new mutable.HashMap[Char,SymbolMap] { - /* Owner template ordering */ - implicit def orderingSet = math.Ordering.String.on { x:TemplateEntity => x.name.toLowerCase } - /* symbol name ordering */ - implicit def orderingMap = math.Ordering.String.on { x:String => x.toLowerCase } - - def addMember(d:MemberEntity) = { - val firstLetter = { - val ch = d.name.head.toLower - if(ch.isLetterOrDigit) ch else '#' - } - this(firstLetter) = - if(this.contains(firstLetter)) { - val letter = this(firstLetter) - val value = this(firstLetter).get(d.name).getOrElse(SortedSet.empty[TemplateEntity]) + d.inDefinitionTemplates.head - letter + ((d.name, value)) - } else { - immutable.SortedMap( (d.name, SortedSet(d.inDefinitionTemplates.head)) ) +object IndexModelFactory { + + def makeIndex(universe: Universe): Index = new Index { + + lazy val firstLetterIndex: Map[Char, SymbolMap] = { + + val result = new mutable.HashMap[Char,SymbolMap] { + + /* Owner template ordering */ + implicit def orderingSet = math.Ordering.String.on { x: TemplateEntity => x.name.toLowerCase } + /* symbol name ordering */ + implicit def orderingMap = math.Ordering.String.on { x: String => x.toLowerCase } + + def addMember(d: MemberEntity) = { + val firstLetter = { + val ch = d.name.head.toLower + if(ch.isLetterOrDigit) ch else '#' + } + this(firstLetter) = + if(this.contains(firstLetter)) { + val letter = this(firstLetter) + val value = this(firstLetter).get(d.name).getOrElse(SortedSet.empty[TemplateEntity]) + d.inDefinitionTemplates.head + letter + ((d.name, value)) + } else { + immutable.SortedMap( (d.name, SortedSet(d.inDefinitionTemplates.head)) ) + } } - } - } - //@scala.annotation.tailrec // TODO - def gather(owner:DocTemplateEntity):Unit = - for(m <- owner.members if m.inDefinitionTemplates.isEmpty || m.inDefinitionTemplates.head == owner) - m match { - case tpl:DocTemplateEntity => - index.addMember(tpl) - gather(tpl) - case alias:AliasType => index.addMember(alias) - case absType:AbstractType => index.addMember(absType) - case non:NonTemplateMemberEntity if !non.isConstructor => index.addMember(non) - case x @ _ => } + //@scala.annotation.tailrec // TODO + def gather(owner: DocTemplateEntity): Unit = + for(m <- owner.members if m.inDefinitionTemplates.isEmpty || m.inDefinitionTemplates.head == owner) + m match { + case tpl: DocTemplateEntity => + result.addMember(tpl) + gather(tpl) + case alias: AliasType => + result.addMember(alias) + case absType: AbstractType => + result.addMember(absType) + case non: NonTemplateMemberEntity if !non.isConstructor => + result.addMember(non) + case x @ _ => + } + gather(universe.rootPackage) - index.toMap + + result.toMap + + } + } + } \ No newline at end of file -- cgit v1.2.3