diff options
author | Marcin Aman <marcin.aman@gmail.com> | 2020-11-25 11:54:20 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-25 11:54:20 +0100 |
commit | 78850b5786b7b2a767db1dbd7132a374b2f4f227 (patch) | |
tree | b2ed4d4ec393a24e4445dc952296106ea30a0b5e | |
parent | b38654fe4f98964a63a4819023332cec03ce6ca1 (diff) | |
download | dokka-78850b5786b7b2a767db1dbd7132a374b2f4f227.tar.gz dokka-78850b5786b7b2a767db1dbd7132a374b2f4f227.tar.bz2 dokka-78850b5786b7b2a767db1dbd7132a374b2f4f227.zip |
Make searchbar an extension point (#1615)
* Make searchbar an extension point
* Change SearchbarDataInstaller to be a preprocessor
* Split navigation and navigation search
6 files changed, 138 insertions, 96 deletions
diff --git a/core/src/main/kotlin/pages/RendererSpecificPage.kt b/core/src/main/kotlin/pages/RendererSpecificPage.kt index d7f4491a..a057b95e 100644 --- a/core/src/main/kotlin/pages/RendererSpecificPage.kt +++ b/core/src/main/kotlin/pages/RendererSpecificPage.kt @@ -5,7 +5,8 @@ import org.jetbrains.dokka.model.DisplaySourceSet import org.jetbrains.dokka.renderers.Renderer import kotlin.reflect.KClass -typealias LocationResolver = (DRI, Set<DisplaySourceSet>) -> String +fun interface DriResolver: (DRI, Set<DisplaySourceSet>) -> String? +fun interface PageResolver: (PageNode, PageNode?) -> String? interface RendererSpecificPage : PageNode { val strategy: RenderingStrategy @@ -33,7 +34,8 @@ 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() - data class LocationResolvableWrite(val contentToResolve: (LocationResolver) -> String) : RenderingStrategy() + data class DriLocationResolvableWrite(val contentToResolve: (DriResolver) -> String) : RenderingStrategy() + data class PageLocationResolvableWrite(val contentToResolve: (PageResolver) -> String) : RenderingStrategy() object DoNothing : RenderingStrategy() companion object { diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt index a5d45282..e92759ff 100644 --- a/plugins/base/src/main/kotlin/DokkaBase.kt +++ b/plugins/base/src/main/kotlin/DokkaBase.kt @@ -39,7 +39,6 @@ class DokkaBase : DokkaPlugin() { val kotlinAnalysis by extensionPoint<KotlinAnalysis>() val tabSortingStrategy by extensionPoint<TabSortingStrategy>() - val descriptorToDocumentableTranslator by extending { CoreExtensions.sourceToDocumentableTranslator providing ::DefaultDescriptorToDocumentableTranslator } @@ -169,7 +168,11 @@ class DokkaBase : DokkaPlugin() { } val navigationPageInstaller by extending { - htmlPreprocessors with NavigationPageInstaller order { after(rootCreator) } + htmlPreprocessors providing ::NavigationPageInstaller order { after(rootCreator) } + } + + val navigationSearchInstaller by extending { + htmlPreprocessors providing ::NavigationSearchInstaller order { after(rootCreator) } } val scriptsInstaller by extending { @@ -207,4 +210,8 @@ class DokkaBase : DokkaPlugin() { MultimodulePageCreator(it) } } + + val baseSearchbarDataInstaller by extending { + htmlPreprocessors providing ::SearchbarDataInstaller order { after(sourceLinksTransformer) } + } } diff --git a/plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt b/plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt index f393c0fa..182ecbc6 100644 --- a/plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt @@ -169,8 +169,11 @@ abstract class DefaultRenderer<T>( 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), ".html") - is RenderingStrategy.LocationResolvableWrite -> outputWriter.write(path, strategy.contentToResolve { dri, sourcesets -> - locationProvider.resolveOrThrow(dri, sourcesets) + is RenderingStrategy.DriLocationResolvableWrite -> outputWriter.write(path, strategy.contentToResolve { dri, sourcesets -> + locationProvider.resolve(dri, sourcesets) + }, "") + is RenderingStrategy.PageLocationResolvableWrite -> outputWriter.write(path, strategy.contentToResolve { pageToLocate, context -> + locationProvider.resolve(pageToLocate, context) }, "") RenderingStrategy.DoNothing -> Unit } diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt index 615bbfc3..d943fefb 100644 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt @@ -41,8 +41,6 @@ open class HtmlRenderer( override val preprocessors = context.plugin<DokkaBase>().query { htmlPreprocessors } - val searchbarDataInstaller = SearchbarDataInstaller() - private val tabSortingStrategy = context.plugin<DokkaBase>().querySingle { tabSortingStrategy } private fun <T : ContentNode> sortTabs(strategy: TabSortingStrategy, tabs: Collection<T>): List<T> { @@ -682,14 +680,6 @@ open class HtmlRenderer( } } - - override suspend fun renderPage(page: PageNode) { - super.renderPage(page) - if (page is ContentPage && page !is ModulePageNode && page !is PackagePageNode) - searchbarDataInstaller.processPage(page, locationProvider.resolve(page) - ?: run { context.logger.error("Cannot resolve path for ${page.dri}"); "" }) - } - override fun FlowContent.buildText(textNode: ContentText) = when { textNode.hasStyle(TextStyle.Indented) -> { @@ -703,11 +693,6 @@ open class HtmlRenderer( override fun render(root: RootPageNode) { shouldRenderSourceSetBubbles = shouldRenderSourceSetBubbles(root) super.render(root) - runBlocking(Dispatchers.Default) { - launch { - outputWriter.write("scripts/pages", "var pages = ${searchbarDataInstaller.generatePagesList()}", ".js") - } - } } private fun PageNode.root(path: String) = locationProvider.pathToRoot(this) + path diff --git a/plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt b/plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt index 3c562315..6ef6a6ec 100644 --- a/plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt +++ b/plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt @@ -4,74 +4,107 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import org.jetbrains.dokka.Platform import org.jetbrains.dokka.model.DisplaySourceSet import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.model.withDescendants import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.transformers.pages.PageTransformer import java.util.concurrent.ConcurrentHashMap -data class SearchRecord(val name: String, val description: String? = null, val location: String, val searchKeys: List<String> = listOf(name)) { - companion object { } +typealias PageId = String +typealias Json = String + +data class SearchRecord( + val name: String, + val description: String? = null, + val location: String, + val searchKeys: List<String> = listOf(name) +) { + companion object {} } -open class SearchbarDataInstaller { - private val mapper = jacksonObjectMapper() +open class SearchbarDataInstaller(val context: DokkaContext) : PageTransformer { + data class PageWithId(val id: PageId, val page: ContentPage) { + val displayableSignature = getSymbolSignature(page)?.let { flattenToText(it) } ?: page.name + } - private val pageList = ConcurrentHashMap<String, Pair<String, String>>() + private val mapper = jacksonObjectMapper() - open fun generatePagesList(): String { - val pages = pageList.entries + open fun generatePagesList(pages: Map<PageId, PageWithId>, locationResolver: PageResolver): Json = + pages.entries .filter { it.key.isNotEmpty() } - .sortedWith(compareBy({ it.key }, { it.value.first }, { it.value.second })) + .sortedWith(compareBy({ it.key }, { it.value.displayableSignature })) .groupBy { it.key.substringAfterLast(".") } .entries .flatMap { entry -> entry.value.map { subentry -> - val name = subentry.value.first createSearchRecord( - name = name, + name = subentry.value.displayableSignature, description = subentry.key, - location = subentry.value.second, - searchKeys = listOf(entry.key, name) + location = resolveLocation(locationResolver, subentry.value.page).orEmpty(), + searchKeys = listOf(entry.key, subentry.value.displayableSignature) ) } - } - return mapper.writeValueAsString(pages) - } + }.run { mapper.writeValueAsString(this) } - open fun createSearchRecord(name: String, description: String?, location: String, searchKeys: List<String>): SearchRecord = + open fun createSearchRecord( + name: String, + description: String?, + location: String, + searchKeys: List<String> + ): SearchRecord = SearchRecord(name, description, location, searchKeys) + open fun processPage(page: PageNode): PageWithId? = + when (page) { + is ContentPage -> page.takeIf { page !is ModulePageNode && page !is PackagePageNode }?.documentable + ?.let { documentable -> + listOfNotNull( + documentable.dri.packageName, + documentable.dri.classNames, + documentable.dri.callable?.name + ).takeIf { it.isNotEmpty() }?.joinToString(".") + }?.let { id -> + PageWithId(id, page) + } + else -> null + } - private fun getSymbolSignature(page: ContentPage) = page.content.dfs { it.dci.kind == ContentKind.Symbol } + private fun resolveLocation(locationResolver: PageResolver, page: ContentPage): String? = + locationResolver(page, null).also { location -> + if (location.isNullOrBlank()) context.logger.warn("Cannot resolve path for ${page.dri}") + } - private fun flattenToText(node: ContentNode): String { - fun getContentTextNodes(node: ContentNode, sourceSetRestriction: DisplaySourceSet): List<ContentText> = - when (node) { - is ContentText -> listOf(node) - is ContentComposite -> node.children - .filter { sourceSetRestriction in it.sourceSets } - .flatMap { getContentTextNodes(it, sourceSetRestriction) } - .takeIf { node.dci.kind != ContentKind.Annotations } - .orEmpty() - else -> emptyList() - } + override fun invoke(input: RootPageNode): RootPageNode { + val page = RendererSpecificResourcePage( + name = "scripts/pages.js", + children = emptyList(), + strategy = RenderingStrategy.PageLocationResolvableWrite { resolver -> + input.withDescendants().fold(emptyMap<PageId, PageWithId>()) { pageList, page -> + processPage(page)?.let { pageList + Pair(it.id, it) } ?: pageList + }.run { + """var pages = ${generatePagesList(this, resolver)}""" + } + }) - val sourceSetRestriction = - node.sourceSets.find { it.platform == Platform.common } ?: node.sourceSets.first() - return getContentTextNodes(node, sourceSetRestriction).joinToString("") { it.text } + return input.modified(children = input.children + page) } +} + +private fun getSymbolSignature(page: ContentPage) = page.content.dfs { it.dci.kind == ContentKind.Symbol } - open fun processPage(page: ContentPage, link: String) { - val signature = getSymbolSignature(page) - val textNodes = signature?.let { flattenToText(it) } - val documentable = page.documentable - if (documentable != null) { - listOf( - documentable.dri.packageName, - documentable.dri.classNames, - documentable.dri.callable?.name - ).filter { !it.isNullOrEmpty() } - .takeIf { it.isNotEmpty() } - ?.joinToString(".") - ?.let { id -> pageList.put(id, Pair(textNodes ?: page.name, link)) } +private fun flattenToText(node: ContentNode): String { + fun getContentTextNodes(node: ContentNode, sourceSetRestriction: DisplaySourceSet): List<ContentText> = + when (node) { + is ContentText -> listOf(node) + is ContentComposite -> node.children + .filter { sourceSetRestriction in it.sourceSets } + .flatMap { getContentTextNodes(it, sourceSetRestriction) } + .takeIf { node.dci.kind != ContentKind.Annotations } + .orEmpty() + else -> emptyList() } - } -} + + val sourceSetRestriction = + node.sourceSets.find { it.platform == Platform.common } ?: node.sourceSets.first() + return getContentTextNodes(node, sourceSetRestriction).joinToString("") { it.text } +}
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt index 8e31fbc6..45159fea 100644 --- a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt +++ b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt @@ -4,39 +4,18 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.DokkaBaseConfiguration import org.jetbrains.dokka.base.renderers.sourceSets -import org.jetbrains.dokka.model.DEnum -import org.jetbrains.dokka.model.DEnumEntry -import org.jetbrains.dokka.model.DFunction -import org.jetbrains.dokka.model.withDescendants +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.* import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.configuration import org.jetbrains.dokka.transformers.pages.PageTransformer -object NavigationPageInstaller : PageTransformer { - private val mapper = jacksonObjectMapper() - - fun SearchRecord.Companion.from(node: NavigationNode, location: String): SearchRecord = - SearchRecord(name = node.name, location = location) - - override fun invoke(input: RootPageNode): RootPageNode { - val nodes = input.children.filterIsInstance<ContentPage>().single() - .let(NavigationPageInstaller::visit) +abstract class NavigationDataProvider { + open fun navigableChildren(input: RootPageNode): NavigationNode = + input.children.filterIsInstance<ContentPage>().single().let { visit(it) } - val page = RendererSpecificResourcePage( - name = "scripts/navigation-pane.json", - children = emptyList(), - strategy = RenderingStrategy.LocationResolvableWrite { resolver -> - mapper.writeValueAsString( - nodes.withDescendants().map { SearchRecord.from(it, resolver(it.dri, it.sourceSets)) }) - }) - - return input.modified( - children = input.children + page + NavigationPage(nodes) - ) - } - - private fun visit(page: ContentPage): NavigationNode = + open fun visit(page: ContentPage): NavigationNode = NavigationNode( name = page.displayableName, dri = page.dri.first(), @@ -61,6 +40,39 @@ object NavigationPageInstaller : PageTransformer { } } +open class NavigationSearchInstaller(val context: DokkaContext) : NavigationDataProvider(), PageTransformer { + private val mapper = jacksonObjectMapper() + + open fun createSearchRecordFromNode(node: NavigationNode, location: String): SearchRecord = + SearchRecord(name = node.name, location = location) + + override fun invoke(input: RootPageNode): RootPageNode { + val page = RendererSpecificResourcePage( + name = "scripts/navigation-pane.json", + children = emptyList(), + strategy = RenderingStrategy.DriLocationResolvableWrite { resolver -> + mapper.writeValueAsString( + navigableChildren(input).withDescendants().map { + createSearchRecordFromNode(it, resolveLocation(resolver, it.dri, it.sourceSets).orEmpty()) + }) + }) + + return input.modified(children = input.children + page) + } + + private fun resolveLocation(locationResolver: DriResolver, dri: DRI, sourceSets: Set<DisplaySourceSet>): String? = + locationResolver(dri, sourceSets).also { location -> + if (location.isNullOrBlank()) context.logger.warn("Cannot resolve path for $dri and sourceSets: ${sourceSets.joinToString { it.name }}") + } + +} + +open class NavigationPageInstaller(val context: DokkaContext) : NavigationDataProvider(), PageTransformer { + + override fun invoke(input: RootPageNode): RootPageNode = + input.modified(children = input.children + NavigationPage(navigableChildren(input))) +} + class CustomResourceInstaller(val dokkaContext: DokkaContext) : PageTransformer { private val configuration = configuration<DokkaBase, DokkaBaseConfiguration>(dokkaContext) @@ -74,7 +86,8 @@ class CustomResourceInstaller(val dokkaContext: DokkaContext) : PageTransformer override fun invoke(input: RootPageNode): RootPageNode { val customResourcesPaths = (customAssets + customStylesheets).map { it.name }.toSet() - val withEmbeddedResources = input.transformContentPagesTree { it.modified(embeddedResources = it.embeddedResources + customResourcesPaths) } + val withEmbeddedResources = + input.transformContentPagesTree { it.modified(embeddedResources = it.embeddedResources + customResourcesPaths) } val (currentResources, otherPages) = withEmbeddedResources.children.partition { it is RendererSpecificResourcePage } return input.modified(children = otherPages + currentResources.filterNot { it.name in customResourcesPaths } + customAssets + customStylesheets) } @@ -166,4 +179,3 @@ class SourcesetDependencyAppender(val context: DokkaContext) : PageTransformer { } } - |