From 3940153fd08e0c0596ac289766d9ef2877b56591 Mon Sep 17 00:00:00 2001 From: Andrzej Ratajczak Date: Fri, 13 Mar 2020 10:46:33 +0100 Subject: First version of javadoc output generation --- .../javadoc/JavadocDocumentableToPageTranslator.kt | 18 + .../main/kotlin/javadoc/JavadocLocationProvider.kt | 103 ++++++ .../src/main/kotlin/javadoc/JavadocPageCreator.kt | 92 +++++ .../src/main/kotlin/javadoc/JavadocPlugin.kt | 29 ++ .../src/main/kotlin/javadoc/JavadocRenderer.kt | 28 ++ .../main/kotlin/javadoc/KorteJavadocRenderer.kt | 193 ++++++++++ .../kotlin/javadoc/pages/JavadocContentNodes.kt | 163 +++++++++ .../main/kotlin/javadoc/pages/JavadocPageNodes.kt | 328 +++++++++++++++++ .../main/kotlin/javadoc/pages/OverviewSummary.kt | 161 +++++++++ .../main/kotlin/javadoc/pages/htmlGeneration.kt | 391 +++++++++++++++++++++ .../main/kotlin/javadoc/pages/htmlPreprocessors.kt | 63 ++++ .../javadoc/src/main/kotlin/javadoc/pages/pages.kt | 17 + 12 files changed, 1586 insertions(+) create mode 100644 plugins/javadoc/src/main/kotlin/javadoc/JavadocDocumentableToPageTranslator.kt create mode 100644 plugins/javadoc/src/main/kotlin/javadoc/JavadocLocationProvider.kt create mode 100644 plugins/javadoc/src/main/kotlin/javadoc/JavadocPageCreator.kt create mode 100644 plugins/javadoc/src/main/kotlin/javadoc/JavadocPlugin.kt create mode 100644 plugins/javadoc/src/main/kotlin/javadoc/JavadocRenderer.kt create mode 100644 plugins/javadoc/src/main/kotlin/javadoc/KorteJavadocRenderer.kt create mode 100644 plugins/javadoc/src/main/kotlin/javadoc/pages/JavadocContentNodes.kt create mode 100644 plugins/javadoc/src/main/kotlin/javadoc/pages/JavadocPageNodes.kt create mode 100644 plugins/javadoc/src/main/kotlin/javadoc/pages/OverviewSummary.kt create mode 100644 plugins/javadoc/src/main/kotlin/javadoc/pages/htmlGeneration.kt create mode 100644 plugins/javadoc/src/main/kotlin/javadoc/pages/htmlPreprocessors.kt create mode 100644 plugins/javadoc/src/main/kotlin/javadoc/pages/pages.kt (limited to 'plugins/javadoc/src/main/kotlin') diff --git a/plugins/javadoc/src/main/kotlin/javadoc/JavadocDocumentableToPageTranslator.kt b/plugins/javadoc/src/main/kotlin/javadoc/JavadocDocumentableToPageTranslator.kt new file mode 100644 index 00000000..840bdc55 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/javadoc/JavadocDocumentableToPageTranslator.kt @@ -0,0 +1,18 @@ +package javadoc + +import org.jetbrains.dokka.base.signatures.SignatureProvider +import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.pages.ModulePageNode +import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.transformers.documentation.DocumentableToPageTranslator +import org.jetbrains.dokka.utilities.DokkaLogger + +class JavadocDocumentableToPageTranslator( + private val commentsToContentConverter: CommentsToContentConverter, + private val signatureProvider: SignatureProvider, + private val logger: DokkaLogger +) : DocumentableToPageTranslator { + override fun invoke(module: DModule): RootPageNode = + JavadocPageCreator(commentsToContentConverter, signatureProvider, logger).pageForModule(module) +} \ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/javadoc/JavadocLocationProvider.kt b/plugins/javadoc/src/main/kotlin/javadoc/JavadocLocationProvider.kt new file mode 100644 index 00000000..520486f6 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/javadoc/JavadocLocationProvider.kt @@ -0,0 +1,103 @@ +package javadoc + +import javadoc.pages.* +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.resolvers.local.LocationProvider +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.SourceSetData +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.PageNode +import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.query +import java.nio.file.Paths +import java.util.* + +class JavadocLocationProvider(pageRoot: RootPageNode, private val context: DokkaContext) : LocationProvider { + private val externalLocationProviderFactories = + context.plugin().query { externalLocationProviderFactory } + private val externalLocationProvider = + externalLocationProviderFactories.asSequence().map { it.getExternalLocationProvider("javadoc10") } + .filterNotNull().take(1).firstOrNull() + private val externalDocumentationLinks by lazy { + context.configuration.passesConfigurations + .filter { passConfig -> passConfig.analysisPlatform == Platform.jvm } + .flatMap { it.externalDocumentationLinks } + .distinct() + } + + private val pathIndex = IdentityHashMap>().apply { + fun registerPath(page: PageNode, prefix: List = emptyList()) { + val newPrefix = prefix + page.takeIf { it is JavadocPackagePageNode }?.name.orEmpty() + val path = (prefix + when (page) { + is AllClassesPage -> listOf("allclasses") + is TreeViewPage -> if (page.classes == null) + listOf("overview-tree") + else + listOf("package-tree") + is ContentPage -> if (page.dri.isNotEmpty() && page.dri.first().classNames != null) + listOfNotNull(page.dri.first().classNames) + else if (page is JavadocPackagePageNode) + listOf(page.name, "package-summary") + else + listOf("index") + else -> emptyList() + }).filterNot { it.isEmpty() } + + put(page, path) + page.children.forEach { registerPath(it, newPrefix) } + + } + put(pageRoot, listOf("index")) + pageRoot.children.forEach { registerPath(it) } + } + + private val nodeIndex = IdentityHashMap().apply { + fun registerNode(node: PageNode) { + if (node is ContentPage) put(node.dri.first(), node) + + node.children.forEach(::registerNode) + } + registerNode(pageRoot) + } + + private operator fun IdentityHashMap>.get(dri: DRI) = this[nodeIndex[dri]] + + override fun resolve(dri: DRI, sourceSets: List, context: PageNode?): String = + context?.let { resolve(it, skipExtension = false) } ?: nodeIndex[dri]?.let { + resolve(it, skipExtension = true) + } ?: with(externalLocationProvider!!) { + dri.toLocation() + } + + override fun resolve(node: PageNode, context: PageNode?, skipExtension: Boolean): String = + pathIndex[node]?.joinToString("/")?.let { + if (skipExtension) it.removeSuffix(".html") else it + } ?: run { + throw IllegalStateException("Path for ${node::class.java.canonicalName}:${node.name} not found") + } + + fun resolve(link: LinkJavadocListEntry, dir: String = "", skipExtension: Boolean = true) = pathIndex[link.dri.first()]?.let { + when (link.kind) { + JavadocContentKind.Class -> it + JavadocContentKind.OverviewSummary -> it.dropLast(1) + "index" + JavadocContentKind.PackageSummary -> it.dropLast(1) + "package-summary" + JavadocContentKind.AllClasses -> it.dropLast(1) + "allclasses" + JavadocContentKind.OverviewTree -> it.dropLast(1) + "overview-tree" + JavadocContentKind.PackageTree -> it.dropLast(1) + "package-tree" + else -> it + } + }?.joinToString("/")?.let {if (skipExtension) "$it.html" else it}?.let { + Paths.get(dir).relativize(Paths.get(it)).toString() + } ?: run {throw IllegalStateException("Page for ${link.name} not found")} + + override fun resolveRoot(node: PageNode): String { + TODO("Not yet implemented") + } + + override fun ancestors(node: PageNode): List { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/javadoc/JavadocPageCreator.kt b/plugins/javadoc/src/main/kotlin/javadoc/JavadocPageCreator.kt new file mode 100644 index 00000000..f5ca4bb3 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/javadoc/JavadocPageCreator.kt @@ -0,0 +1,92 @@ +package javadoc + +import javadoc.pages.* +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.signatures.SignatureProvider +import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.pages.ContentKind +import org.jetbrains.dokka.utilities.DokkaLogger + +open class JavadocPageCreator( + commentsToContentConverter: CommentsToContentConverter, + signatureProvider: SignatureProvider, + val logger: DokkaLogger +) { + fun pageForModule(m: DModule): JavadocModulePageNode = + JavadocModulePageNode( + m.name.ifEmpty { "root" }, + contentForModule(m), + m.packages.map { pageForPackage(it) }, + setOf(m.dri) + ) + + fun pageForPackage(p: DPackage) = + JavadocPackagePageNode(p.name, contentForPackage(p), setOf(p.dri), p, + p.classlikes.map { pageForClasslike(it) } // TODO: nested classlikes + ).also { + it + } + + fun pageForClasslike(c: DClasslike): JavadocClasslikePageNode { + val constructors = when (c) { + is DClass -> c.constructors + is DEnum -> c.constructors + else -> emptyList() + } + + return JavadocClasslikePageNode(c.name.orEmpty(), contentForClasslike(c), setOf(c.dri), c, emptyList()) + } + + fun contentForModule(m: DModule): JavadocContentNode = + JavadocContentGroup( + setOf(m.dri), + JavadocContentKind.OverviewSummary, + m.sourceSets.filter { it.platform == Platform.jvm }.toSet() + ) { + title(m.name, "0.0.1", dri = setOf(m.dri), kind = ContentKind.Main) + list("Packages", "Package", setOf(m.dri), ContentKind.Packages, m.packages.map { p -> + val doc = p.documentation.entries.find { (k, _) -> k.platform == Platform.jvm }?.value?.let { + it.children.joinToString("\n") { it.root.toString() } + }.orEmpty() + RowJavadocListEntry( + LinkJavadocListEntry(p.name, setOf(p.dri), JavadocContentKind.PackageSummary, sourceSets), + doc + ) + }) + } + + fun contentForPackage(p: DPackage): JavadocContentNode = + JavadocContentGroup( + setOf(p.dri), + JavadocContentKind.PackageSummary, + p.sourceSets.filter { it.platform == Platform.jvm }.toSet() + ) { + title(p.name, "0.0.1", dri = setOf(p.dri), kind = ContentKind.Packages) + list("Packages", "Package", setOf(p.dri), ContentKind.Packages, p.classlikes.map { c -> + val doc = c.documentation.entries.find { (k, _) -> k.platform == Platform.jvm }?.value?.let { + it.children.joinToString("\n") { it.root.toString() } + }.orEmpty() + RowJavadocListEntry( + LinkJavadocListEntry(c.name.orEmpty(), setOf(c.dri), JavadocContentKind.Class, sourceSets), + doc + ) + }) + } + + fun contentForClasslike(c: DClasslike): JavadocContentNode = + JavadocContentGroup( + setOf(c.dri), + JavadocContentKind.Class, + c.sourceSets.filter { it.platform == Platform.jvm }.toSet() + ) { + title( + c.name.orEmpty(), + "0.0.1", + parent = c.dri.packageName, + dri = setOf(c.dri), + kind = JavadocContentKind.Class + ) + } +} + diff --git a/plugins/javadoc/src/main/kotlin/javadoc/JavadocPlugin.kt b/plugins/javadoc/src/main/kotlin/javadoc/JavadocPlugin.kt new file mode 100644 index 00000000..f58faa49 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/javadoc/JavadocPlugin.kt @@ -0,0 +1,29 @@ +package org.jetbrains.dokka.javadoc + +import javadoc.JavadocDocumentableToPageTranslator +import javadoc.KorteJavadocRenderer +import org.jetbrains.dokka.CoreExtensions +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.plugability.DokkaPlugin +import org.jetbrains.dokka.plugability.querySingle + +class JavadocPlugin : DokkaPlugin() { + val dokkaJavadocPlugin by extending { + val dokkaBasePlugin = plugin() + CoreExtensions.renderer providing { ctx -> + KorteJavadocRenderer(dokkaBasePlugin.querySingle { outputWriter }, ctx, "views") + } applyIf { format == "javadoc" } + } + + val pageTranslator by extending { + val dokkaBasePlugin = plugin() + CoreExtensions.documentableToPageTranslator providing { ctx -> + JavadocDocumentableToPageTranslator( + dokkaBasePlugin.querySingle { commentsToContentConverter }, + dokkaBasePlugin.querySingle { signatureProvider }, + ctx.logger + ) + } + } +} + diff --git a/plugins/javadoc/src/main/kotlin/javadoc/JavadocRenderer.kt b/plugins/javadoc/src/main/kotlin/javadoc/JavadocRenderer.kt new file mode 100644 index 00000000..588d89ba --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/javadoc/JavadocRenderer.kt @@ -0,0 +1,28 @@ +package org.jetbrains.dokka.javadoc + +//class JavadocRenderer(val outputWriter: OutputWriter, val context: DokkaContext) : Renderer { +// override fun render(root: RootPageNode) { +// val rootIndex = (root as RootIndexPage) +// val instructions = (rootIndex.strategy as RenderingStrategy.Callback).instructions +// outputWriter.writeHtml("index", instructions(this, rootIndex)) // TODO get version +// rootIndex.children.forEach { renderPages(it) } +// } +// +// private fun renderPages(node: PageNode, dir: String = "") { +// val path = if (node is JavadocPageNode) node.fullPath else "$dir/${node.name}" +// if (node is RendererSpecificPage) { +// when (val strategy = node.strategy) { +// is RenderingStrategy.Copy -> outputWriter.writeResources(strategy.from, "") +// is RenderingStrategy.Write -> outputWriter.writeHtml(path, strategy.text) +// is RenderingStrategy.Callback -> outputWriter.writeHtml(path, strategy.instructions(this, node)) +// RenderingStrategy.DoNothing -> Unit +// } +// } +//// else +//// throw IllegalStateException("${node.name} was not expected here") +// +// node.children.forEach { renderPages(it, path) } +// } +// +// private fun OutputWriter.writeHtml(path: String, text: String) = write(path, text, ".html") +//} \ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/javadoc/KorteJavadocRenderer.kt b/plugins/javadoc/src/main/kotlin/javadoc/KorteJavadocRenderer.kt new file mode 100644 index 00000000..ad83968e --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/javadoc/KorteJavadocRenderer.kt @@ -0,0 +1,193 @@ +package javadoc + +import com.soywiz.korte.* +import javadoc.pages.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.jetbrains.dokka.base.renderers.OutputWriter +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.pages.PageNode +import org.jetbrains.dokka.pages.RendererSpecificPage +import org.jetbrains.dokka.pages.RenderingStrategy +import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.renderers.Renderer +import java.nio.file.Path +import java.nio.file.Paths +import java.time.LocalDate + +class KorteJavadocRenderer(val outputWriter: OutputWriter, val context: DokkaContext, val resourceDir: String) : + Renderer { + private lateinit var locationProvider: JavadocLocationProvider + + override fun render(root: RootPageNode) = root.let { preprocessors.fold(root) { r, t -> t.invoke(r) } }.let { r -> + locationProvider = JavadocLocationProvider(r, context) + runBlocking(Dispatchers.IO) { + renderModulePageNode(r as JavadocModulePageNode) + } + } + + private fun templateForNode(node: JavadocPageNode) = when (node) { + is JavadocClasslikePageNode -> "class.korte" + is JavadocPackagePageNode -> "tabPage.korte" + is JavadocModulePageNode -> "tabPage.korte" + is AllClassesPage -> "listPage.korte" + is TreeViewPage -> "treePage.korte" + else -> "" + } + + private fun CoroutineScope.renderNode(node: PageNode, path: String = "") { + if (node is JavadocPageNode) { + renderJavadocNode(node) + } else if (node is RendererSpecificPage) { + renderSpecificPage(node, path) + } + } + + private fun CoroutineScope.renderModulePageNode(node: JavadocModulePageNode) { + val link = "." + val name = "index" + val pathToRoot = "" + + val contentMap = mapOf( + "docName" to "docName", // todo docname + "pathToRoot" to pathToRoot + ) + node.contentMap + writeFromTemplate(outputWriter, "$link/$name".toNormalized(), "tabPage.korte", contentMap.toList()) + node.children.forEach { renderNode(it, link) } + } + + private fun CoroutineScope.renderJavadocNode(node: JavadocPageNode) { + val link = locationProvider.resolve(node, skipExtension = true) + val dir = Paths.get(link).parent?.let { it.toNormalized() }.orEmpty() + val pathToRoot = dir.split("/").joinToString("/") { ".." }.let { + if (it.isNotEmpty()) "$it/" else it + } + + val contentMap = mapOf( + "docName" to "docName", // todo docname + "pathToRoot" to pathToRoot, + "dir" to dir + ) + node.contentMap +// DokkaConsoleLogger.info("${node::class.java.canonicalName}::${node.name} - $link") + writeFromTemplate(outputWriter, link, templateForNode(node), contentMap.toList()) + node.children.forEach { renderNode(it, link.toNormalized()) } + } + + fun CoroutineScope.renderSpecificPage(node: RendererSpecificPage, path: String) = launch { + when (val strategy = node.strategy) { + is RenderingStrategy.Copy -> outputWriter.writeResources(strategy.from, "") + is RenderingStrategy.Write -> outputWriter.writeHtml(path, strategy.text) + is RenderingStrategy.Callback -> outputWriter.writeResources( + path, + strategy.instructions(this@KorteJavadocRenderer, node) + ) + RenderingStrategy.DoNothing -> Unit + } + } + + fun Pair.pairToTag() = "\n${first}\n${second}" + + fun LinkJavadocListEntry.toLinkTag(parent: String? = null) = + createLinkTag(locationProvider.resolve(dri.first(), sourceSets.toList()).let { + if (parent != null) it.relativizePath(parent) + else it + }, name) + + fun DRI.toLink(context: PageNode? = null) = locationProvider.resolve(this, emptyList(), context) + + fun createLinkTag(address: String, name: String) = + address.let { if (it.endsWith(".html")) it else "$it.html" }.let { + """$name""" + } + + private fun String.parent() = Paths.get(this).parent.toNormalized() + private fun Path.toNormalized() = this.normalize().toFile().toString() + private fun String.toNormalized() = Paths.get(this).toNormalized() + private fun String.relativizePath(parent: String) = Paths.get(parent).relativize(Paths.get(this)).toNormalized() + + private suspend fun OutputWriter.writeHtml(path: String, text: String) = write(path, text, ".html") + private fun CoroutineScope.writeFromTemplate( + writer: OutputWriter, + path: String, + template: String, + args: List> + ) = + launch { + val tmp = templateRenderer.render(template, *(args.toTypedArray())) + writer.writeHtml( + path, + tmp + ) + } + + fun getTemplateConfig() = TemplateConfig().also { config -> + listOf( + TeFunction("curDate") { LocalDate.now() }, + TeFunction("jQueryVersion") { "3.1" }, + TeFunction("jQueryMigrateVersion") { "1.2.1" }, + TeFunction("rowColor") { args -> if ((args.first() as Int) % 2 == 0) "altColor" else "rowColor" }, + TeFunction("h1Title") { args -> if ((args.first() as? String) == "package") "title=\"Package\" " else "" }, + TeFunction("createTabRow") { args -> + val (link, doc) = args.first() as RowJavadocListEntry + val dir = args[1] as String? + (createLinkTag(locationProvider.resolve(link, dir.orEmpty()), link.name) to doc).pairToTag().trim() + }, + TeFunction("createListRow") { args -> + val link = args.first() as LinkJavadocListEntry + val dir = args[1] as String? +// link.toLinkTag(dir) + createLinkTag(locationProvider.resolve(link, dir.orEmpty()), link.name) + }, + TeFunction("createPackageHierarchy") { args -> + val list = args.first() as List + list.mapIndexed { i, p -> + val content = if (i + 1 == list.size) "" else ", " + val name = p.name + "
  • $name$content
  • " + }.joinToString("\n") + }, + TeFunction("renderInheritanceGraph") { args -> + val node = args.first() as TreeViewPage.InheritanceNode + + fun drawRec(node: TreeViewPage.InheritanceNode): String = + "
  • " + node.dri.let { dri -> + listOfNotNull( + dri.packageName, + dri.classNames + ).joinToString(".") + node.interfaces.takeUnless { node.isInterface || it.isEmpty() } + ?.let { + " implements " + it.joinToString(", ") { n -> + listOfNotNull( + n.packageName, + createLinkTag(n.toLink(), n.classNames.orEmpty()) + ).joinToString(".") + } + }.orEmpty() + } + node.children.filterNot { it.isInterface }.takeUnless { it.isEmpty() }?.let { + "
      " + it.joinToString("\n", transform = ::drawRec) + "
    " + }.orEmpty() + "
  • " + + drawRec(node) + }, + Filter("length") { subject.dynamicLength() } + ).forEach { + when (it) { + is TeFunction -> config.register(it) + is Filter -> config.register(it) + is Tag -> config.register(it) + } + } + } + + val config = getTemplateConfig() + val templateRenderer = Templates(ResourceTemplateProvider(resourceDir), config = config, cache = true) + + class ResourceTemplateProvider(val basePath: String) : TemplateProvider { + override suspend fun get(template: String): String = + javaClass.classLoader.getResourceAsStream("$basePath/$template")?.bufferedReader()?.lines()?.toArray() + ?.joinToString("\n") ?: throw IllegalStateException("Template not found: $basePath/$template") + } +} \ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/javadoc/pages/JavadocContentNodes.kt b/plugins/javadoc/src/main/kotlin/javadoc/pages/JavadocContentNodes.kt new file mode 100644 index 00000000..df026be3 --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/javadoc/pages/JavadocContentNodes.kt @@ -0,0 +1,163 @@ +package javadoc.pages + +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.SourceSetData +import org.jetbrains.dokka.model.properties.PropertyContainer +import org.jetbrains.dokka.pages.* + +enum class JavadocContentKind : Kind { + AllClasses, OverviewSummary, PackageSummary, Class, OverviewTree, PackageTree +} + +abstract class JavadocContentNode( + dri: Set, + kind: Kind, + override val sourceSets: Set +) : ContentNode { + abstract val contentMap: Map + override val dci: DCI = DCI(dri, kind) + override val style: Set