diff options
author | Paweł Marks <pmarks@virtuslab.com> | 2020-01-27 09:34:16 +0100 |
---|---|---|
committer | Paweł Marks <Kordyjan@users.noreply.github.com> | 2020-01-31 15:07:06 +0100 |
commit | 885ecd28153b484277c9ddcbf4a7f9d761a59545 (patch) | |
tree | db453e66762ebebb3ee05c0301b38c4465ea20a3 /core/src | |
parent | c29605d92d1999434ecc79e774281a8280ae2823 (diff) | |
download | dokka-885ecd28153b484277c9ddcbf4a7f9d761a59545.tar.gz dokka-885ecd28153b484277c9ddcbf4a7f9d761a59545.tar.bz2 dokka-885ecd28153b484277c9ddcbf4a7f9d761a59545.zip |
Unifing model for pages with content ant technical renderer specific pages
Diffstat (limited to 'core/src')
15 files changed, 302 insertions, 211 deletions
diff --git a/core/src/main/kotlin/DokkaGenerator.kt b/core/src/main/kotlin/DokkaGenerator.kt index 95f9e647..add7eedb 100644 --- a/core/src/main/kotlin/DokkaGenerator.kt +++ b/core/src/main/kotlin/DokkaGenerator.kt @@ -8,6 +8,7 @@ import org.jetbrains.dokka.analysis.DokkaResolutionFacade import org.jetbrains.dokka.model.Module import org.jetbrains.dokka.pages.ModulePageNode import org.jetbrains.dokka.pages.PlatformData +import org.jetbrains.dokka.pages.RootPageNode import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.single import org.jetbrains.dokka.utilities.DokkaLogger @@ -88,12 +89,12 @@ class DokkaGenerator( ) = context.single(CoreExtensions.documentationToPageTranslator).invoke(transformedDocumentation, context) fun transformPages( - pages: ModulePageNode, + pages: RootPageNode, context: DokkaContext - ) = context[CoreExtensions.pageTransformer].fold(pages) { acc, t -> t(acc, context) } + ) = context[CoreExtensions.pageTransformer].fold(pages) { acc, t -> t(acc) } fun render( - transformedPages: ModulePageNode, + transformedPages: RootPageNode, context: DokkaContext ) { val renderer = context.single(CoreExtensions.renderer) diff --git a/core/src/main/kotlin/pages/PageNodes.kt b/core/src/main/kotlin/pages/PageNodes.kt index 9274ac2b..80b21547 100644 --- a/core/src/main/kotlin/pages/PageNodes.kt +++ b/core/src/main/kotlin/pages/PageNodes.kt @@ -7,18 +7,57 @@ import java.util.* interface PageNode { val name: String + val children: List<PageNode> + + fun modified( + name: String = this.name, + children: List<PageNode> = this.children + ): PageNode +} + +interface ContentPage: PageNode { val content: ContentNode val dri: DRI val documentable: Documentable? val embeddedResources: List<String> - val children: List<PageNode> fun modified( name: String = this.name, content: ContentNode = this.content, embeddedResources: List<String> = this.embeddedResources, children: List<PageNode> = this.children - ): PageNode + ): ContentPage +} + +abstract class RootPageNode: PageNode { + val parentMap: Map<PageNode, PageNode> by lazy { + IdentityHashMap<PageNode, PageNode>().apply { + fun process(parent: PageNode) { + parent.children.forEach { child -> + put(child, parent) + process(child) + } + } + process(this@RootPageNode) + } + } + + fun transformPageNodeTree(operation: (PageNode) -> PageNode) = + this.transformNode(operation) as RootPageNode + + fun transformContentPagesTree(operation: (ContentPage) -> ContentPage) = transformPageNodeTree { + if (it is ContentPage) operation(it) else it + } as RootPageNode + + private fun PageNode.transformNode(operation: (PageNode) -> PageNode): PageNode = + operation(this).let { newNode -> + newNode.modified(children = newNode.children.map { it.transformNode(operation) }) + } + + abstract override fun modified( + name: String, + children: List<PageNode> + ): RootPageNode } class ModulePageNode( @@ -28,9 +67,12 @@ class ModulePageNode( override val documentable: Documentable?, override val children: List<PageNode>, override val embeddedResources: List<String> = listOf() -) : PageNode { +) : RootPageNode(), ContentPage { override val dri: DRI = DRI.topLevel + override fun modified(name: String, children: List<PageNode>): ModulePageNode = + modified(name = name, content = this.content, children = children) + override fun modified( name: String, content: ContentNode, @@ -39,26 +81,6 @@ class ModulePageNode( ): ModulePageNode = if (name == this.name && content === this.content && embeddedResources === this.embeddedResources && children shallowEq this.children) this else ModulePageNode(name, content, documentable, children, embeddedResources) - - private fun PageNode.transformNode(operation: (PageNode) -> PageNode): PageNode = - operation(this).let { newNode -> - newNode.modified(children = newNode.children.map { it.transformNode(operation) }) - } - - fun transformPageNodeTree(operation: (PageNode) -> PageNode) = - this.transformNode(operation) as ModulePageNode - - val parentMap: IdentityHashMap<PageNode, PageNode> by lazy { - IdentityHashMap<PageNode, PageNode>().apply { - fun addParent(parent: PageNode) { - parent.children.forEach { child -> - put(child, parent) - addParent(child) - } - } - addParent(this@ModulePageNode) - } - } } class PackagePageNode( @@ -69,7 +91,9 @@ class PackagePageNode( override val documentable: Documentable?, override val children: List<PageNode>, override val embeddedResources: List<String> = listOf() -) : PageNode { +) : ContentPage { + override fun modified(name: String, children: List<PageNode>): PackagePageNode = + modified(name = name, content = this.content, children = children) override fun modified( name: String, @@ -88,7 +112,9 @@ class ClassPageNode( override val documentable: Documentable?, override val children: List<PageNode>, override val embeddedResources: List<String> = listOf() -) : PageNode { +) : ContentPage { + override fun modified(name: String, children: List<PageNode>): ClassPageNode = + modified(name = name, content = this.content, children = children) override fun modified( name: String, @@ -107,7 +133,9 @@ class MemberPageNode( override val documentable: Documentable?, override val children: List<PageNode> = emptyList(), override val embeddedResources: List<String> = listOf() -) : PageNode { +) : ContentPage { + override fun modified(name: String, children: List<PageNode>): MemberPageNode = + modified(name = name, content = this.content, children = children) override fun modified( name: String, @@ -129,10 +157,12 @@ fun PageNode.dfs(predicate: (PageNode) -> Boolean): PageNode? = if (predicate(th this.children.asSequence().mapNotNull { it.dfs(predicate) }.firstOrNull() } -private infix fun <T> List<T>.shallowEq(other: List<T>) = - this === other || (this.size == other.size && (this zip other).all { (a, b) -> a === b }) +fun PageNode.asSequence(): Sequence<PageNode> = sequence { + yield(this@asSequence) + children.asSequence().flatMap { it.asSequence() }.forEach { yield(it) } +} -// Navigation?? +inline fun <reified T: PageNode> PageNode.children() = children.filterIsInstance<T>() -// content modifier? -//data class ContentLink(val link: String): ContentNode
\ No newline at end of file +private infix fun <T> List<T>.shallowEq(other: List<T>) = + this === other || (this.size == other.size && (this zip other).all { (a, b) -> a === b }) diff --git a/core/src/main/kotlin/pages/RendererSpecificPage.kt b/core/src/main/kotlin/pages/RendererSpecificPage.kt new file mode 100644 index 00000000..85e6d530 --- /dev/null +++ b/core/src/main/kotlin/pages/RendererSpecificPage.kt @@ -0,0 +1,40 @@ +package org.jetbrains.dokka.pages + +import org.jetbrains.dokka.renderers.Renderer +import kotlin.reflect.KClass + +interface RendererSpecificPage : PageNode { + val strategy: RenderingStrategy +} + +class RendererSpecificRootPage( + override val name: String, + override val children: List<PageNode>, + override val strategy: RenderingStrategy +) : RootPageNode(), RendererSpecificPage { + override fun modified(name: String, children: List<PageNode>): RendererSpecificRootPage = + RendererSpecificRootPage(name, children, strategy) +} + +class RendererSpecificResourcePage( + override val name: String, + override val children: List<PageNode>, + override val strategy: RenderingStrategy +): RendererSpecificPage { + override fun modified(name: String, children: List<PageNode>): RendererSpecificResourcePage = + RendererSpecificResourcePage(name, children, strategy) +} + +sealed class RenderingStrategy { + class Callback(val instructions: Renderer.(PageNode) -> String): RenderingStrategy() + data class Copy(val from: String) : RenderingStrategy() + data class Write(val text: String) : RenderingStrategy() + object DoNothing : RenderingStrategy() + + companion object { + inline operator fun <reified T: Renderer> invoke(crossinline instructions: T.(PageNode) -> String) = + Callback { if (this is T) instructions(it) else throw WrongRendererTypeException(T::class) } + } +} + +data class WrongRendererTypeException(val expectedType: KClass<*>): Exception()
\ No newline at end of file diff --git a/core/src/main/kotlin/renderers/DefaultRenderer.kt b/core/src/main/kotlin/renderers/DefaultRenderer.kt index 5e3eadbe..606eea54 100644 --- a/core/src/main/kotlin/renderers/DefaultRenderer.kt +++ b/core/src/main/kotlin/renderers/DefaultRenderer.kt @@ -5,55 +5,62 @@ import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.single import org.jetbrains.dokka.resolvers.LocationProvider +import org.jetbrains.dokka.transformers.pages.PageNodeTransformer abstract class DefaultRenderer<T>( protected val outputWriter: OutputWriter, protected val context: DokkaContext ) : Renderer { + private val extension = context.single(CoreExtensions.fileExtension) + protected lateinit var locationProvider: LocationProvider private set + protected open val preprocessors: Iterable<PageNodeTransformer> = emptyList() + protected abstract fun T.buildHeader(level: Int, content: T.() -> Unit) protected abstract fun T.buildLink(address: String, content: T.() -> Unit) - protected abstract fun T.buildList(node: ContentList, pageContext: PageNode) + protected abstract fun T.buildList(node: ContentList, pageContext: ContentPage) protected abstract fun T.buildNewLine() - protected abstract fun T.buildResource(node: ContentEmbeddedResource, pageContext: PageNode) - protected abstract fun T.buildTable(node: ContentTable, pageContext: PageNode) + protected abstract fun T.buildResource(node: ContentEmbeddedResource, pageContext: ContentPage) + protected abstract fun T.buildTable(node: ContentTable, pageContext: ContentPage) protected abstract fun T.buildText(textNode: ContentText) protected abstract fun T.buildNavigation(page: PageNode) - protected abstract fun buildPage(page: PageNode, content: (T, PageNode) -> Unit): String + protected abstract fun buildPage(page: ContentPage, content: (T, ContentPage) -> Unit): String protected abstract fun buildError(node: ContentNode) - protected open fun T.buildGroup(node: ContentGroup, pageContext: PageNode){ + protected open fun T.buildGroup(node: ContentGroup, pageContext: ContentPage) { node.children.forEach { it.build(this, pageContext) } } - protected open fun T.buildLinkText(nodes: List<ContentNode>, pageContext: PageNode) { + protected open fun T.buildLinkText(nodes: List<ContentNode>, pageContext: ContentPage) { nodes.forEach { it.build(this, pageContext) } } - protected open fun T.buildCode(code: List<ContentNode>, language: String, pageContext: PageNode) { + protected open fun T.buildCode(code: List<ContentNode>, language: String, pageContext: ContentPage) { code.forEach { it.build(this, pageContext) } } - protected open fun T.buildHeader(node: ContentHeader, pageContext: PageNode) { + protected open fun T.buildHeader(node: ContentHeader, pageContext: ContentPage) { buildHeader(node.level) { node.children.forEach { it.build(this, pageContext) } } } - protected open fun ContentNode.build(builder: T, pageContext: PageNode) = builder.buildContentNode(this, pageContext) + protected open fun ContentNode.build(builder: T, pageContext: ContentPage) = + builder.buildContentNode(this, pageContext) - protected open fun T.buildContentNode(node: ContentNode, pageContext: PageNode) { + protected open fun T.buildContentNode(node: ContentNode, pageContext: ContentPage) { when (node) { is ContentText -> buildText(node) is ContentHeader -> buildHeader(node, pageContext) is ContentCode -> buildCode(node.children, node.language, pageContext) is ContentDRILink -> buildLink( - locationProvider.resolve(node.address, node.platforms.toList(), pageContext)) { + locationProvider.resolve(node.address, node.platforms.toList(), pageContext) + ) { buildLinkText(node.children, pageContext) } - is ContentResolvedLink -> buildLink(node.address) {buildLinkText(node.children, pageContext)} + is ContentResolvedLink -> buildLink(node.address) { buildLinkText(node.children, pageContext) } is ContentEmbeddedResource -> buildResource(node, pageContext) is ContentList -> buildList(node, pageContext) is ContentTable -> buildTable(node, pageContext) @@ -62,22 +69,34 @@ abstract class DefaultRenderer<T>( } } - protected open fun buildPageContent(context: T, page: PageNode) { + protected open fun buildPageContent(context: T, page: ContentPage) { context.buildNavigation(page) page.content.build(context, page) } - protected open fun renderPage(page: PageNode) = - outputWriter.write(locationProvider.resolve(page), buildPage(page, ::buildPageContent), "") + protected open fun renderPage(page: PageNode) { + val path by lazy { locationProvider.resolve(page, skipExtension = true) } + when (page) { + is ContentPage -> outputWriter.write(path, buildPage(page) { c, p -> buildPageContent(c, p) }, extension) + is RendererSpecificPage -> when (val strategy = page.strategy) { + is RenderingStrategy.Copy -> outputWriter.writeResources(strategy.from, path) + is RenderingStrategy.Write -> outputWriter.write(path, strategy.text, "") + is RenderingStrategy.Callback -> outputWriter.write(path, strategy.instructions(this, page)) + RenderingStrategy.DoNothing -> Unit + } + else -> throw AssertionError( + "Page ${page.name} cannot be rendered by renderer as it is not renderer specific nor contains content" + ) + } + } protected open fun renderPages(root: PageNode) { renderPage(root) root.children.forEach { renderPages(it) } } - protected open fun buildSupportFiles() {} - - protected open fun renderPackageList(root: PageNode) = + // reimplement this as preprocessor + protected open fun renderPackageList(root: ContentPage) = getPackageNamesAndPlatforms(root) .keys .joinToString("\n") @@ -93,12 +112,16 @@ abstract class DefaultRenderer<T>( emptyMap() } - override fun render(root: PageNode) { - locationProvider = context.single(CoreExtensions.locationProviderFactory).getLocationProvider(root as ModulePageNode) - renderPackageList(root) - buildSupportFiles() - renderPages(root) + override fun render(root: RootPageNode) { + val newRoot = preprocessors.fold(root) { acc, t -> t(acc) } + + locationProvider = + context.single(CoreExtensions.locationProviderFactory).getLocationProvider(newRoot) + + root.children<ModulePageNode>().forEach { renderPackageList(it) } + + renderPages(newRoot) } } -fun PageNode.platforms() = this.content.platforms.toList()
\ No newline at end of file +fun ContentPage.platforms() = this.content.platforms.toList()
\ No newline at end of file diff --git a/core/src/main/kotlin/renderers/FileWriter.kt b/core/src/main/kotlin/renderers/FileWriter.kt index 22a2e8f9..2fba649d 100644 --- a/core/src/main/kotlin/renderers/FileWriter.kt +++ b/core/src/main/kotlin/renderers/FileWriter.kt @@ -1,6 +1,5 @@ package org.jetbrains.dokka.renderers -import com.intellij.util.io.isDirectory import java.io.File import java.io.IOException import java.net.URI @@ -17,10 +16,9 @@ class FileWriter(val root: String, override val extension: String): OutputWriter createdFiles.add(path) try { -// println("Writing $root/$path$ext") val dir = Paths.get(root, path.dropLastWhile { it != '/' }).toFile() dir.mkdirsOrFail() - Paths.get(root, "$path$ext").toFile().writeText(text) + Files.write(Paths.get(root, "$path$ext"), text.lines()) } catch (e: Throwable) { println("Failed to write $this. ${e.message}") e.printStackTrace() @@ -36,7 +34,7 @@ class FileWriter(val root: String, override val extension: String): OutputWriter val fs = getFileSystemForURI(uri) val path = fs.getPath(pathFrom) for (file in Files.walk(path).iterator()) { - if (file.isDirectory()) { + if (Files.isDirectory(file)) { val dirPath = file.toAbsolutePath().toString() Paths.get(root, rebase(dirPath)).toFile().mkdirsOrFail() } else { diff --git a/core/src/main/kotlin/renderers/HtmlRenderer.kt b/core/src/main/kotlin/renderers/HtmlRenderer.kt index ae120795..af99d349 100644 --- a/core/src/main/kotlin/renderers/HtmlRenderer.kt +++ b/core/src/main/kotlin/renderers/HtmlRenderer.kt @@ -1,15 +1,12 @@ package org.jetbrains.dokka.renderers import kotlinx.html.* -import kotlinx.html.dom.document -import kotlinx.html.stream.appendHTML -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.Documentable +import kotlinx.html.stream.createHTML +import org.jetbrains.dokka.model.Function import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty import java.io.File -import java.net.URL open class HtmlRenderer( outputWriter: OutputWriter, @@ -39,7 +36,9 @@ open class HtmlRenderer( } } - override fun FlowContent.buildList(node: ContentList, pageContext: PageNode) = + override val preprocessors = listOf(RootCreator, SearchPageInstaller, ResourceInstaller, StyleAndScriptsAppender) + + override fun FlowContent.buildList(node: ContentList, pageContext: ContentPage) = if (node.ordered) ol { buildListItems(node.children, pageContext) } @@ -47,7 +46,7 @@ open class HtmlRenderer( buildListItems(node.children, pageContext) } - protected open fun OL.buildListItems(items: List<ContentNode>, pageContext: PageNode) { + protected open fun OL.buildListItems(items: List<ContentNode>, pageContext: ContentPage) { items.forEach { if (it is ContentList) buildList(it, pageContext) @@ -56,7 +55,7 @@ open class HtmlRenderer( } } - protected open fun UL.buildListItems(items: List<ContentNode>, pageContext: PageNode) { + protected open fun UL.buildListItems(items: List<ContentNode>, pageContext: ContentPage) { items.forEach { if (it is ContentList) buildList(it, pageContext) @@ -67,7 +66,7 @@ open class HtmlRenderer( override fun FlowContent.buildResource( node: ContentEmbeddedResource, - pageContext: PageNode + pageContext: ContentPage ) { // TODO: extension point there val imageExtensions = setOf("png", "jpg", "jpeg", "gif", "bmp", "tif", "webp", "svg") return if (File(node.address).extension.toLowerCase() in imageExtensions) { @@ -79,7 +78,7 @@ open class HtmlRenderer( } } - override fun FlowContent.buildTable(node: ContentTable, pageContext: PageNode) { + override fun FlowContent.buildTable(node: ContentTable, pageContext: ContentPage) { table { thead { node.header.forEach { @@ -138,60 +137,48 @@ open class HtmlRenderer( override fun FlowContent.buildLink(address: String, content: FlowContent.() -> Unit) = a(href = address, block = content) - override fun FlowContent.buildCode(code: List<ContentNode>, language: String, pageContext: PageNode) { + override fun FlowContent.buildCode(code: List<ContentNode>, language: String, pageContext: ContentPage) { buildNewLine() code.forEach { - + ((it as? ContentText)?.text ?: run { context.logger.error("Cannot cast $it as ContentText!"); ""} ) + +((it as? ContentText)?.text ?: run { context.logger.error("Cannot cast $it as ContentText!"); "" }) buildNewLine() } } override fun renderPage(page: PageNode) { super.renderPage(page) - pageList.add("""{ "name": "${page.name}", ${if (page is ClassPageNode) "\"class\": \"${page.name}\"," else ""} "location": "${locationProvider.resolve(page)}" }""") + if (page is ContentPage) { + pageList.add( + """{ "name": "${page.name}", ${if (page is ClassPageNode) "\"class\": \"${page.name}\"," else ""} "location": "${locationProvider.resolve( + page + )}" }""" + ) + } } override fun FlowContent.buildText(textNode: ContentText) { text(textNode.text) } - override fun render(root: PageNode) { + override fun render(root: RootPageNode) { super.render(root) outputWriter.write("scripts/pages", "var pages = [\n${pageList.joinToString(",\n")}\n]", ".js") } - override fun buildSupportFiles() { // TODO copy file instead of reading - outputWriter.write( - "style.css", - javaClass.getResourceAsStream("/dokka/styles/style.css").reader().readText() - ) - renderPage(searchPageNode) - outputWriter.writeResources("/dokka/styles", "styles") - outputWriter.writeResources("/dokka/scripts", "scripts") - outputWriter.writeResources("/dokka/images", "images") - } + private fun PageNode.root(path: String) = locationProvider.resolveRoot(this) + path - private fun PageNode.root(path: String) = - "${if (this != searchPageNode) locationProvider.resolveRoot(this) else ""}$path" + override fun buildPage(page: ContentPage, content: (FlowContent, ContentPage) -> Unit): String = + buildHtml(page, page.embeddedResources) { content(this, page) } - override fun buildPage(page: PageNode, content: (FlowContent, PageNode) -> Unit): String = - StringBuilder().appendHTML().html { - document { - - } + open fun buildHtml(page: PageNode, resources: List<String>, content: FlowContent.() -> Unit) = + createHTML().html { head { title(page.name) - link(rel = LinkRel.stylesheet, href = page.root("styles/style.css")) - page.embeddedResources.filter { - URL(it).path.substringAfterLast('.') == "js" - }.forEach { - script(type = ScriptType.textJavaScript, src = it) { async = true } - } - if (page == searchPageNode) { - script( - type = ScriptType.textJavaScript, - src = page.root("scripts/pages.js") - ) { async = true } + with(resources) { + filter { it.substringAfterLast('.') == "css" } + .forEach { link(rel = LinkRel.stylesheet, href = page.root(it)) } + filter { it.substringAfterLast('.') == "js" } + .forEach { script(type = ScriptType.textJavaScript, src = it) { async = true } } } } body { @@ -207,65 +194,24 @@ open class HtmlRenderer( } div { id = "sideMenu" - img(src = page.root("images/logo-icon.svg")) - img(src = page.root("images/logo-text.svg")) - hr() - input(type = InputType.search) { - id = "navigationFilter" - } - script( - type = ScriptType.textJavaScript, - src = page.root("scripts/scripts.js") - ) { async = true } - buildSideMenu(page, locationProvider.top()) } } div { id = "content" - if (page != searchPageNode) content(this, page) - else { - h1 { - id = "searchTitle" - text("Search results for ") - } - table { - tbody { - id = "searchTable" - } - } - script( - type = ScriptType.textJavaScript, - src = page.root("scripts/search.js") - ) { async = true } - } + content() } } - }.toString() - - protected open fun List<HTMLMetadata>.joinAttr() = this.joinToString(" ") { it.key + "=" + it.value } - - private val searchPageNode = - object: PageNode { - override val name: String - get() = "Search" - override val content = object: ContentNode{ - override val dci: DCI = DCI(DRI.topLevel, ContentKind.Main) - override val platforms: Set<PlatformData> = emptySet() - override val style: Set<Style> = emptySet() - override val extras: Set<Extra> = emptySet() - - } - override val dri: DRI = DRI.topLevel - override val documentable: Documentable? = null - override val embeddedResources: List<String> = emptyList() - override val children: List<PageNode> = emptyList() + } +} - override fun modified( - name: String, - content: ContentNode, - embeddedResources: List<String>, - children: List<PageNode> - ): PageNode = this +fun List<HTMLMetadata>.joinAttr() = joinToString(" ") { it.key + "=" + it.value } - } -}
\ No newline at end of file +private fun PageNode.pageKind() = when (this) { + is PackagePageNode -> "package" + is ClassPageNode -> "class" + is MemberPageNode -> when (this.documentable) { + is Function -> "function" + else -> "other" + } + else -> "other" +} diff --git a/core/src/main/kotlin/renderers/Renderer.kt b/core/src/main/kotlin/renderers/Renderer.kt index 271bdf77..10235f21 100644 --- a/core/src/main/kotlin/renderers/Renderer.kt +++ b/core/src/main/kotlin/renderers/Renderer.kt @@ -1,7 +1,7 @@ package org.jetbrains.dokka.renderers -import org.jetbrains.dokka.pages.PageNode +import org.jetbrains.dokka.pages.RootPageNode interface Renderer { - fun render(root: PageNode) + fun render(root: RootPageNode) }
\ No newline at end of file diff --git a/core/src/main/kotlin/renderers/htmlPreprocessors.kt b/core/src/main/kotlin/renderers/htmlPreprocessors.kt new file mode 100644 index 00000000..a6ad267b --- /dev/null +++ b/core/src/main/kotlin/renderers/htmlPreprocessors.kt @@ -0,0 +1,57 @@ +package org.jetbrains.dokka.renderers + +import kotlinx.html.h1 +import kotlinx.html.id +import kotlinx.html.table +import kotlinx.html.tbody +import org.jetbrains.dokka.pages.RendererSpecificResourcePage +import org.jetbrains.dokka.pages.RendererSpecificRootPage +import org.jetbrains.dokka.pages.RenderingStrategy +import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.transformers.pages.PageNodeTransformer + +object RootCreator : PageNodeTransformer { + override fun invoke(input: RootPageNode) = + RendererSpecificRootPage("", listOf(input), RenderingStrategy.DoNothing) +} + +object SearchPageInstaller : PageNodeTransformer { + override fun invoke(input: RootPageNode) = input.modified(children = input.children + searchPage) + + private val searchPage = RendererSpecificResourcePage( + name = "Search", + children = emptyList(), + strategy = RenderingStrategy<HtmlRenderer> { + buildHtml(it, listOf("styles/style.css", "scripts/pages.js")) { + h1 { + id = "searchTitle" + text("Search results for ") + } + table { + tbody { + id = "searchTable" + } + } + } + }) + +} + +object ResourceInstaller : PageNodeTransformer { + override fun invoke(input: RootPageNode) = input.modified(children = input.children + resourcePages) + + private val resourcePages = listOf("styles", "scripts", "images").map { + RendererSpecificResourcePage(it, emptyList(), RenderingStrategy.Copy("/dokka/$it")) + } +} + +object StyleAndScriptsAppender : PageNodeTransformer { + override fun invoke(input: RootPageNode) = input.transformContentPagesTree { + it.modified( + embeddedResources = it.embeddedResources + listOf( + "styles/style.css", + "scripts/navigationLoader.js" + ) + ) + } +} diff --git a/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt b/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt index 8e848b63..48ea5316 100644 --- a/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt +++ b/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt @@ -8,19 +8,35 @@ import org.jetbrains.dokka.plugability.single import org.jetbrains.dokka.utilities.htmlEscape import java.util.* +private const val PAGE_WITH_CHILDREN_SUFFIX = "index" + open class DefaultLocationProvider( - private val pageGraphRoot: ModulePageNode, - private val dokkaContext: DokkaContext -) : LocationProvider { // TODO: cache - private val extension = dokkaContext.single(CoreExtensions.fileExtension) + protected val pageGraphRoot: RootPageNode, + protected val dokkaContext: DokkaContext +) : LocationProvider { + protected val extension = dokkaContext.single(CoreExtensions.fileExtension) + + protected val pagesIndex: Map<DRI, ContentPage> = pageGraphRoot.asSequence().filterIsInstance<ContentPage>() + .groupingBy { it.dri } + .aggregate { dri, _, page, first -> + if (first) page else throw AssertionError("Multiple pages associated with dri: $dri") + } - private val pagesCache = mutableMapOf<DRI, Maybe<PageNode>>() - private val pathCache: MutableMap<PageNode, List<String>> = IdentityHashMap<PageNode, List<String>>() + protected val pathsIndex: Map<PageNode, List<String>> = IdentityHashMap<PageNode, List<String>>().apply { + fun registerPath(page: PageNode, prefix: List<String>) { + val newPrefix = prefix + page.pathName + put(page, newPrefix) + page.children.forEach { registerPath(it, newPrefix) } + } + put(pageGraphRoot, emptyList()) + pageGraphRoot.children.forEach { registerPath(it, emptyList()) } + } - override fun resolve(node: PageNode, context: PageNode?): String = pathTo(node, context) + extension + override fun resolve(node: PageNode, context: PageNode?, skipExtension: Boolean): String = + pathTo(node, context) + if (!skipExtension) extension else "" override fun resolve(dri: DRI, platforms: List<PlatformData>, context: PageNode?): String = - findInPageGraph(dri, platforms)?.let { resolve(it, context) } ?: + pagesIndex[dri]?.let { resolve(it, context) } ?: // Not found in PageGraph, that means it's an external link ExternalLocationProvider.getLocation(dri, this.dokkaContext.configuration.passesConfigurations @@ -31,50 +47,30 @@ open class DefaultLocationProvider( .flatMap { it.externalDocumentationLinks }.distinct() ) - override fun resolveRoot(node: PageNode): String = "../${pathTo(pageGraphRoot, node).removeSuffix( - PAGE_WITH_CHILDREN_SUFFIX - )}" - - private fun PageNode.parent() = pageGraphRoot.parentMap[this] - - override fun ancestors(node: PageNode?): List<PageNode> = when (node) { - null -> emptyList() - else -> ancestors(node.parent()) + node - } - - override fun top(): PageNode = pageGraphRoot - - protected open fun findInPageGraph(dri: DRI, platforms: List<PlatformData>): PageNode? = - pagesCache.getOrPut(dri) { pageGraphRoot.dfs { it.dri == dri }.wrapped }.unwrapped + override fun resolveRoot(node: PageNode): String = + pathTo(pageGraphRoot, node).removeSuffix(PAGE_WITH_CHILDREN_SUFFIX) + override fun ancestors(node: PageNode): List<PageNode> = + generateSequence(node) { it.parent() }.toList() protected open fun pathTo(node: PageNode, context: PageNode?): String { - - fun PageNode.pathName(): String = - if (this is PackagePageNode) name - else identifierToFilename(name) - - fun getPathInternal(pathNode: PageNode?, path: List<String>): List<String> = when (pathNode) { - null -> path - else -> getPathInternal(pathNode.parent(), path + pathNode.pathName().ifEmpty { "root" }) - } - - fun getPath(pathNode: PageNode) = pathCache.getOrPut(pathNode) { getPathInternal(pathNode, emptyList()) } + fun pathFor(page: PageNode) = pathsIndex[page] ?: throw AssertionError( + "${page::class.simpleName}(${page.name}) does not belong to current page graph so it is impossible to compute its path" + ) val contextNode = if (context?.children?.isEmpty() == true && context.parent() != null) context.parent() else context - val nodePath = getPath(node).reversed() - val contextPath = contextNode?.let(::getPath).orEmpty().reversed() + val nodePath = pathFor(node) + val contextPath = contextNode?.let { pathFor(it) }.orEmpty() - val commonPathElements = nodePath.zip(contextPath).takeWhile { (a, b) -> a == b }.size + val commonPathElements = nodePath.asSequence().zip(contextPath.asSequence()) + .takeWhile { (a, b) -> a == b }.count() return (List(contextPath.size - commonPathElements) { ".." } + nodePath.drop(commonPathElements) + if (node.children.isNotEmpty()) listOf(PAGE_WITH_CHILDREN_SUFFIX) else emptyList()).joinToString("/") } - private companion object { - const val PAGE_WITH_CHILDREN_SUFFIX = "index" - } + private fun PageNode.parent() = pageGraphRoot.parentMap[this] } fun DRI.toJavadocLocation(jdkVersion: Int): String { // TODO: classes without packages? @@ -122,6 +118,5 @@ private fun identifierToFilename(name: String): String { return if (lowercase in reservedFilenames) "--$lowercase--" else lowercase } -private class Maybe<T : Any>(val unwrapped: T?) - -private val <T : Any> T?.wrapped: Maybe<T> get() = Maybe(this)
\ No newline at end of file +private val PageNode.pathName: String + get() = if (this is PackagePageNode) name else identifierToFilename(name) diff --git a/core/src/main/kotlin/resolvers/LocationProvider.kt b/core/src/main/kotlin/resolvers/LocationProvider.kt index 7d77ccb8..3bc9ab72 100644 --- a/core/src/main/kotlin/resolvers/LocationProvider.kt +++ b/core/src/main/kotlin/resolvers/LocationProvider.kt @@ -1,13 +1,13 @@ package org.jetbrains.dokka.resolvers import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.pages.ContentPage import org.jetbrains.dokka.pages.PageNode import org.jetbrains.dokka.pages.PlatformData interface LocationProvider { fun resolve(dri: DRI, platforms: List<PlatformData>, context: PageNode? = null): String - fun resolve(node: PageNode, context: PageNode? = null): String + fun resolve(node: PageNode, context: PageNode? = null, skipExtension: Boolean = false): String fun resolveRoot(node: PageNode): String - fun ancestors(node: PageNode?): List<PageNode> - fun top(): PageNode + fun ancestors(node: PageNode): List<PageNode> } diff --git a/core/src/main/kotlin/resolvers/LocationProviderFactory.kt b/core/src/main/kotlin/resolvers/LocationProviderFactory.kt index c657846a..782795de 100644 --- a/core/src/main/kotlin/resolvers/LocationProviderFactory.kt +++ b/core/src/main/kotlin/resolvers/LocationProviderFactory.kt @@ -1,13 +1,14 @@ package org.jetbrains.dokka.resolvers import org.jetbrains.dokka.pages.ModulePageNode +import org.jetbrains.dokka.pages.RootPageNode import org.jetbrains.dokka.plugability.DokkaContext interface LocationProviderFactory { - fun getLocationProvider(pageNode: ModulePageNode): LocationProvider + fun getLocationProvider(pageNode: RootPageNode): LocationProvider } class DefaultLocationProviderFactory(val context: DokkaContext) : LocationProviderFactory { - override fun getLocationProvider(pageNode: ModulePageNode) = DefaultLocationProvider(pageNode, context) + override fun getLocationProvider(pageNode: RootPageNode) = DefaultLocationProvider(pageNode, context) }
\ No newline at end of file diff --git a/core/src/main/kotlin/transformers/documentation/DefaultDocumentationNodeMerger.kt b/core/src/main/kotlin/transformers/documentation/DefaultDocumentationNodeMerger.kt index a2867504..cac9d44c 100644 --- a/core/src/main/kotlin/transformers/documentation/DefaultDocumentationNodeMerger.kt +++ b/core/src/main/kotlin/transformers/documentation/DefaultDocumentationNodeMerger.kt @@ -1,6 +1,5 @@ package org.jetbrains.dokka.transformers.documentation -import com.intellij.openapi.diagnostic.logger import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.Function import org.jetbrains.dokka.plugability.DokkaContext diff --git a/core/src/main/kotlin/transformers/pages/PageNodeTransformer.kt b/core/src/main/kotlin/transformers/pages/PageNodeTransformer.kt index 286835f9..45357060 100644 --- a/core/src/main/kotlin/transformers/pages/PageNodeTransformer.kt +++ b/core/src/main/kotlin/transformers/pages/PageNodeTransformer.kt @@ -1,8 +1,9 @@ package org.jetbrains.dokka.transformers.pages import org.jetbrains.dokka.pages.ModulePageNode +import org.jetbrains.dokka.pages.RootPageNode import org.jetbrains.dokka.plugability.DokkaContext interface PageNodeTransformer { - operator fun invoke(input: ModulePageNode, dokkaContext: DokkaContext): ModulePageNode + operator fun invoke(input: RootPageNode): RootPageNode }
\ No newline at end of file diff --git a/core/src/main/kotlin/utilities/nodeDebug.kt b/core/src/main/kotlin/utilities/nodeDebug.kt index 984f13ed..0e8c61f7 100644 --- a/core/src/main/kotlin/utilities/nodeDebug.kt +++ b/core/src/main/kotlin/utilities/nodeDebug.kt @@ -34,7 +34,7 @@ fun Documentable.pretty(prefix: String = "", isLast: Boolean = true): String { //} private fun Any.stringify() = when(this) { is ContentNode -> toString() + this.dci - is PageNode -> this.name + this::class.simpleName + is ContentPage -> this.name + this::class.simpleName else -> toString() } //private fun Any.allChildren() = when(this){ diff --git a/core/src/main/resources/dokka/scripts/navigationLoader.js b/core/src/main/resources/dokka/scripts/navigationLoader.js new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/core/src/main/resources/dokka/scripts/navigationLoader.js |