From 8e5c63d035ef44a269b8c43430f43f5c8eebfb63 Mon Sep 17 00:00:00 2001 From: Ignat Beresnev Date: Fri, 10 Nov 2023 11:46:54 +0100 Subject: Restructure the project to utilize included builds (#3174) * Refactor and simplify artifact publishing * Update Gradle to 8.4 * Refactor and simplify convention plugins and build scripts Fixes #3132 --------- Co-authored-by: Adam <897017+aSemy@users.noreply.github.com> Co-authored-by: Oleg Yukhnevich --- .../src/main/kotlin/renderers/DefaultRenderer.kt | 257 ----- .../base/src/main/kotlin/renderers/FileWriter.kt | 109 --- .../base/src/main/kotlin/renderers/OutputWriter.kt | 11 - .../main/kotlin/renderers/PackageListService.kt | 80 -- .../main/kotlin/renderers/TabSortingStrategy.kt | 11 - .../main/kotlin/renderers/contentTypeChecking.kt | 24 - .../src/main/kotlin/renderers/html/HtmlContent.kt | 18 - .../src/main/kotlin/renderers/html/HtmlRenderer.kt | 1013 -------------------- .../renderers/html/NavigationDataProvider.kt | 134 --- .../main/kotlin/renderers/html/NavigationPage.kt | 129 --- .../renderers/html/SearchbarDataInstaller.kt | 128 --- .../base/src/main/kotlin/renderers/html/Tags.kt | 82 -- .../consumers/ImmediateResolutionTagConsumer.kt | 37 - .../html/command/consumers/PathToRootConsumer.kt | 26 - .../command/consumers/ReplaceVersionsConsumer.kt | 29 - .../html/command/consumers/ResolveLinkConsumer.kt | 34 - .../kotlin/renderers/html/htmlFormatingUtils.kt | 67 -- .../kotlin/renderers/html/htmlPreprocessors.kt | 172 ---- .../innerTemplating/DefaultTemplateModelFactory.kt | 234 ----- .../innerTemplating/DefaultTemplateModelMerger.kt | 20 - .../html/innerTemplating/HtmlTemplater.kt | 82 -- .../html/innerTemplating/TemplateModelFactory.kt | 19 - .../html/innerTemplating/TemplateModelMerger.kt | 9 - .../renderers/html/shouldRenderSourceSetBubbles.kt | 20 - plugins/base/src/main/kotlin/renderers/pageId.kt | 31 - .../src/main/kotlin/renderers/preprocessors.kt | 41 - 26 files changed, 2817 deletions(-) delete mode 100644 plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/FileWriter.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/OutputWriter.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/PackageListService.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/TabSortingStrategy.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/contentTypeChecking.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/HtmlContent.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/SearchbarDataInstaller.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/Tags.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/command/consumers/ImmediateResolutionTagConsumer.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/command/consumers/PathToRootConsumer.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/command/consumers/ReplaceVersionsConsumer.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/command/consumers/ResolveLinkConsumer.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/htmlFormatingUtils.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/innerTemplating/DefaultTemplateModelMerger.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/innerTemplating/HtmlTemplater.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/innerTemplating/TemplateModelFactory.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/innerTemplating/TemplateModelMerger.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/html/shouldRenderSourceSetBubbles.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/pageId.kt delete mode 100644 plugins/base/src/main/kotlin/renderers/preprocessors.kt (limited to 'plugins/base/src/main/kotlin/renderers') diff --git a/plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt b/plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt deleted file mode 100644 index eed7794e..00000000 --- a/plugins/base/src/main/kotlin/renderers/DefaultRenderer.kt +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package org.jetbrains.dokka.base.renderers - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import org.jetbrains.dokka.DokkaException -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.resolvers.local.LocationProvider -import org.jetbrains.dokka.model.DisplaySourceSet -import org.jetbrains.dokka.pages.* -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.plugability.plugin -import org.jetbrains.dokka.plugability.querySingle -import org.jetbrains.dokka.renderers.Renderer -import org.jetbrains.dokka.transformers.pages.PageTransformer - -public abstract class DefaultRenderer( - protected val context: DokkaContext -) : Renderer { - - protected val outputWriter: OutputWriter = context.plugin().querySingle { outputWriter } - - protected lateinit var locationProvider: LocationProvider - private set - - protected open val preprocessors: Iterable = emptyList() - - public abstract fun T.buildHeader(level: Int, node: ContentHeader, content: T.() -> Unit) - public abstract fun T.buildLink(address: String, content: T.() -> Unit) - public abstract fun T.buildList( - node: ContentList, - pageContext: ContentPage, - sourceSetRestriction: Set? = null - ) - - public abstract fun T.buildLineBreak() - public open fun T.buildLineBreak(node: ContentBreakLine, pageContext: ContentPage) { - buildLineBreak() - } - - public abstract fun T.buildResource(node: ContentEmbeddedResource, pageContext: ContentPage) - public abstract fun T.buildTable( - node: ContentTable, - pageContext: ContentPage, - sourceSetRestriction: Set? = null - ) - - public abstract fun T.buildText(textNode: ContentText) - public abstract fun T.buildNavigation(page: PageNode) - - public abstract fun buildPage(page: ContentPage, content: (T, ContentPage) -> Unit): String - public abstract fun buildError(node: ContentNode) - - public open fun T.buildPlatformDependent( - content: PlatformHintedContent, - pageContext: ContentPage, - sourceSetRestriction: Set? - ) { - buildContentNode(content.inner, pageContext) - } - - public open fun T.buildGroup( - node: ContentGroup, - pageContext: ContentPage, - sourceSetRestriction: Set? = null - ) { - wrapGroup(node, pageContext) { node.children.forEach { it.build(this, pageContext, sourceSetRestriction) } } - } - - public open fun T.buildDivergent(node: ContentDivergentGroup, pageContext: ContentPage) { - node.children.forEach { it.build(this, pageContext) } - } - - public open fun T.wrapGroup(node: ContentGroup, pageContext: ContentPage, childrenCallback: T.() -> Unit) { - childrenCallback() - } - - public open fun T.buildText( - nodes: List, - pageContext: ContentPage, - sourceSetRestriction: Set? = null - ) { - nodes.forEach { it.build(this, pageContext, sourceSetRestriction) } - } - - public open fun T.buildCodeBlock(code: ContentCodeBlock, pageContext: ContentPage) { - code.children.forEach { it.build(this, pageContext) } - } - - public open fun T.buildCodeInline(code: ContentCodeInline, pageContext: ContentPage) { - code.children.forEach { it.build(this, pageContext) } - } - - public open fun T.buildHeader( - node: ContentHeader, - pageContext: ContentPage, - sourceSetRestriction: Set? = null - ) { - buildHeader(node.level, node) { node.children.forEach { it.build(this, pageContext, sourceSetRestriction) } } - } - - public open fun ContentNode.build( - builder: T, - pageContext: ContentPage, - sourceSetRestriction: Set? = null - ) { - builder.buildContentNode(this, pageContext, sourceSetRestriction) - } - - public fun T.buildContentNode( - node: ContentNode, - pageContext: ContentPage, - sourceSetRestriction: DisplaySourceSet - ) { - buildContentNode(node, pageContext, setOf(sourceSetRestriction)) - } - - public open fun T.buildContentNode( - node: ContentNode, - pageContext: ContentPage, - sourceSetRestriction: Set? = null - ) { - if (sourceSetRestriction.isNullOrEmpty() || node.sourceSets.any { it in sourceSetRestriction }) { - when (node) { - is ContentText -> buildText(node) - is ContentHeader -> buildHeader(node, pageContext, sourceSetRestriction) - is ContentCodeBlock -> buildCodeBlock(node, pageContext) - is ContentCodeInline -> buildCodeInline(node, pageContext) - is ContentDRILink -> buildDRILink(node, pageContext, sourceSetRestriction) - is ContentResolvedLink -> buildResolvedLink(node, pageContext, sourceSetRestriction) - is ContentEmbeddedResource -> buildResource(node, pageContext) - is ContentList -> buildList(node, pageContext, sourceSetRestriction) - is ContentTable -> buildTable(node, pageContext, sourceSetRestriction) - is ContentGroup -> buildGroup(node, pageContext, sourceSetRestriction) - is ContentBreakLine -> buildLineBreak(node, pageContext) - is PlatformHintedContent -> buildPlatformDependent(node, pageContext, sourceSetRestriction) - is ContentDivergentGroup -> buildDivergent(node, pageContext) - is ContentDivergentInstance -> buildDivergentInstance(node, pageContext) - else -> buildError(node) - } - } - } - - public open fun T.buildDRILink( - node: ContentDRILink, - pageContext: ContentPage, - sourceSetRestriction: Set? - ) { - locationProvider.resolve(node.address, node.sourceSets, pageContext)?.let { address -> - buildLink(address) { - buildText(node.children, pageContext, sourceSetRestriction) - } - } ?: buildText(node.children, pageContext, sourceSetRestriction) - } - - public open fun T.buildResolvedLink( - node: ContentResolvedLink, - pageContext: ContentPage, - sourceSetRestriction: Set? - ) { - buildLink(node.address) { - buildText(node.children, pageContext, sourceSetRestriction) - } - } - - public open fun T.buildDivergentInstance(node: ContentDivergentInstance, pageContext: ContentPage) { - node.before?.build(this, pageContext) - node.divergent.build(this, pageContext) - node.after?.build(this, pageContext) - } - - public open fun buildPageContent(context: T, page: ContentPage) { - context.buildNavigation(page) - page.content.build(context, page) - } - - public open suspend fun renderPage(page: PageNode) { - val path by lazy { - locationProvider.resolve(page, skipExtension = true) - ?: throw DokkaException("Cannot resolve path for ${page.name}") - } - when (page) { - is ContentPage -> outputWriter.write(path, buildPage(page) { c, p -> buildPageContent(c, p) }, ".html") - 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), ".html") - 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 - } - else -> throw AssertionError( - "Page ${page.name} cannot be rendered by renderer as it is not renderer specific nor contains content" - ) - } - } - - private suspend fun renderPages(root: PageNode) { - coroutineScope { - renderPage(root) - - root.children.forEach { - launch { renderPages(it) } - } - } - } - - override fun render(root: RootPageNode) { - val newRoot = preprocessors.fold(root) { acc, t -> t(acc) } - - locationProvider = - context.plugin().querySingle { locationProviderFactory }.getLocationProvider(newRoot) - - runBlocking(Dispatchers.Default) { - renderPages(newRoot) - } - } - - protected fun ContentDivergentGroup.groupDivergentInstances( - pageContext: ContentPage, - beforeTransformer: (ContentDivergentInstance, ContentPage, DisplaySourceSet) -> String, - afterTransformer: (ContentDivergentInstance, ContentPage, DisplaySourceSet) -> String - ): Map> = - children.flatMap { instance -> - instance.sourceSets.map { sourceSet -> - Pair(instance, sourceSet) to Pair( - beforeTransformer(instance, pageContext, sourceSet), - afterTransformer(instance, pageContext, sourceSet) - ) - } - }.groupBy( - Pair::second, - Pair::first - ) -} - -internal typealias SerializedBeforeAndAfter = Pair -internal typealias InstanceWithSource = Pair - -public fun ContentPage.sourceSets(): Set = this.content.sourceSets diff --git a/plugins/base/src/main/kotlin/renderers/FileWriter.kt b/plugins/base/src/main/kotlin/renderers/FileWriter.kt deleted file mode 100644 index 1a1c3b42..00000000 --- a/plugins/base/src/main/kotlin/renderers/FileWriter.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package org.jetbrains.dokka.base.renderers - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withContext -import org.jetbrains.dokka.plugability.DokkaContext -import java.io.File -import java.io.IOException -import java.net.URI -import java.nio.file.* - -public class FileWriter( - public val context: DokkaContext -): OutputWriter { - private val createdFiles: MutableSet = mutableSetOf() - private val createdFilesMutex = Mutex() - private val jarUriPrefix = "jar:file:" - private val root = context.configuration.outputDir - - override suspend fun write(path: String, text: String, ext: String) { - if (checkFileCreated(path)) return - - try { - val dir = Paths.get(root.absolutePath, path.dropLastWhile { it != '/' }).toFile() - withContext(Dispatchers.IO) { - dir.mkdirsOrFail() - Files.write(Paths.get(root.absolutePath, "$path$ext"), text.lines()) - } - } catch (e: Throwable) { - context.logger.error("Failed to write $this. ${e.message}") - e.printStackTrace() - } - } - - private suspend fun checkFileCreated(path: String): Boolean = createdFilesMutex.withLock { - if (createdFiles.contains(path)) { - context.logger.error("An attempt to write ${root}/$path several times!") - return true - } - createdFiles.add(path) - return false - } - - override suspend fun writeResources(pathFrom: String, pathTo: String) { - if (javaClass.getResource(pathFrom)?.toURI()?.toString()?.startsWith(jarUriPrefix) == true) { - copyFromJar(pathFrom, pathTo) - } else { - copyFromDirectory(pathFrom, pathTo) - } - } - - - private suspend fun copyFromDirectory(pathFrom: String, pathTo: String) { - val dest = Paths.get(root.path, pathTo).toFile() - val uri = javaClass.getResource(pathFrom)?.toURI() - val file = uri?.let { File(it) } ?: File(pathFrom) - withContext(Dispatchers.IO) { - file.copyRecursively(dest, true) - } - } - - private suspend fun copyFromJar(pathFrom: String, pathTo: String) { - val rebase = fun(path: String) = - "$pathTo/${path.removePrefix(pathFrom)}" - val dest = Paths.get(root.path, pathTo).toFile() - if(dest.isDirectory){ - dest.mkdirsOrFail() - } else { - dest.parentFile.mkdirsOrFail() - } - val uri = javaClass.getResource(pathFrom).toURI() - val fs = getFileSystemForURI(uri) - val path = fs.getPath(pathFrom) - for (file in Files.walk(path).iterator()) { - if (Files.isDirectory(file)) { - val dirPath = file.toAbsolutePath().toString() - withContext(Dispatchers.IO) { - Paths.get(root.path, rebase(dirPath)).toFile().mkdirsOrFail() - } - } else { - val filePath = file.toAbsolutePath().toString() - withContext(Dispatchers.IO) { - Paths.get(root.path, rebase(filePath)).toFile().writeBytes( - this@FileWriter.javaClass.getResourceAsStream(filePath).use { it?.readBytes() } - ?: throw IllegalStateException("Can not get a resource from $filePath") - ) - } - } - } - } - - private fun File.mkdirsOrFail() { - if (!mkdirs() && !exists()) { - throw IOException("Failed to create directory $this") - } - } - - private fun getFileSystemForURI(uri: URI): FileSystem = - try { - FileSystems.newFileSystem(uri, emptyMap()) - } catch (e: FileSystemAlreadyExistsException) { - FileSystems.getFileSystem(uri) - } -} diff --git a/plugins/base/src/main/kotlin/renderers/OutputWriter.kt b/plugins/base/src/main/kotlin/renderers/OutputWriter.kt deleted file mode 100644 index 3fdd1802..00000000 --- a/plugins/base/src/main/kotlin/renderers/OutputWriter.kt +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package org.jetbrains.dokka.base.renderers - -public interface OutputWriter { - - public suspend fun write(path: String, text: String, ext: String) - public suspend fun writeResources(pathFrom: String, pathTo: String) -} diff --git a/plugins/base/src/main/kotlin/renderers/PackageListService.kt b/plugins/base/src/main/kotlin/renderers/PackageListService.kt deleted file mode 100644 index 3ed6cd21..00000000 --- a/plugins/base/src/main/kotlin/renderers/PackageListService.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package org.jetbrains.dokka.base.renderers - -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.resolvers.shared.LinkFormat -import org.jetbrains.dokka.base.resolvers.shared.PackageList.Companion.DOKKA_PARAM_PREFIX -import org.jetbrains.dokka.base.resolvers.shared.PackageList.Companion.MODULE_DELIMITER -import org.jetbrains.dokka.base.resolvers.shared.PackageList.Companion.SINGLE_MODULE_NAME -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.pages.* -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.plugability.plugin -import org.jetbrains.dokka.plugability.querySingle - -public class PackageListService( - public val context: DokkaContext, - public val rootPage: RootPageNode -) { - - public fun createPackageList(module: ModulePage, format: LinkFormat): String { - - val packages = mutableSetOf() - val nonStandardLocations = mutableMapOf() - - val locationProvider = - context.plugin().querySingle { locationProviderFactory }.getLocationProvider(rootPage) - - fun visit(node: PageNode) { - if (node is PackagePage) { - node.name - .takeUnless { name -> name.startsWith("[") && name.endsWith("]") } // Do not include the package name for declarations without one - ?.let { packages.add(it) } - } - - val contentPage = node as? ContentPage - contentPage?.dri?.forEach { dri -> - val nodeLocation = locationProvider.resolve(node, context = module, skipExtension = true) - ?: run { context.logger.error("Cannot resolve path for ${node.name}!"); null } - - if (dri != DRI.topLevel && locationProvider.expectedLocationForDri(dri) != nodeLocation) { - nonStandardLocations[dri.toString()] = "$nodeLocation.${format.linkExtension}" - } - } - - node.children.forEach { visit(it) } - } - - visit(module) - return renderPackageList( - nonStandardLocations = nonStandardLocations, - modules = mapOf(SINGLE_MODULE_NAME to packages), - format = format.formatName, - linkExtension = format.linkExtension - ) - } - - public companion object { - public fun renderPackageList( - nonStandardLocations: Map, - modules: Map>, - format: String, - linkExtension: String - ): String = buildString { - appendLine("$DOKKA_PARAM_PREFIX.format:${format}") - appendLine("$DOKKA_PARAM_PREFIX.linkExtension:${linkExtension}") - nonStandardLocations.map { (signature, location) -> - "$DOKKA_PARAM_PREFIX.location:$signature\u001f$location" - }.sorted().joinTo(this, separator = "\n", postfix = "\n") - - modules.mapNotNull { (module, packages) -> - ("$MODULE_DELIMITER$module\n".takeIf { module != SINGLE_MODULE_NAME }.orEmpty() + - packages.filter(String::isNotBlank).sorted().joinToString(separator = "\n")) - .takeIf { packages.isNotEmpty() } - }.joinTo(this, separator = "\n", postfix = "\n") - } - } -} diff --git a/plugins/base/src/main/kotlin/renderers/TabSortingStrategy.kt b/plugins/base/src/main/kotlin/renderers/TabSortingStrategy.kt deleted file mode 100644 index 665b6717..00000000 --- a/plugins/base/src/main/kotlin/renderers/TabSortingStrategy.kt +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package org.jetbrains.dokka.base.renderers - -import org.jetbrains.dokka.pages.ContentNode - -public interface TabSortingStrategy { - public fun sort(tabs: Collection) : List -} diff --git a/plugins/base/src/main/kotlin/renderers/contentTypeChecking.kt b/plugins/base/src/main/kotlin/renderers/contentTypeChecking.kt deleted file mode 100644 index 0fcb0efb..00000000 --- a/plugins/base/src/main/kotlin/renderers/contentTypeChecking.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package org.jetbrains.dokka.base.renderers - -import org.jetbrains.dokka.base.renderers.HtmlFileExtensions.imageExtensions -import org.jetbrains.dokka.pages.ContentEmbeddedResource -import java.io.File - -public fun ContentEmbeddedResource.isImage(): Boolean { - return File(address).extension.toLowerCase() in imageExtensions -} - -public val String.URIExtension: String - get() = substringBefore('?').substringAfterLast('.') - -public fun String.isImage(): Boolean = - URIExtension in imageExtensions - -public object HtmlFileExtensions { - public val imageExtensions: Set = setOf("png", "jpg", "jpeg", "gif", "bmp", "tif", "webp", "svg") -} - diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlContent.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlContent.kt deleted file mode 100644 index 1ef6e04c..00000000 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlContent.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package org.jetbrains.dokka.base.renderers.html - -import org.jetbrains.dokka.pages.ContentBreakLine -import org.jetbrains.dokka.pages.Style - - -/** - * Html-specific style that represents
tag if used in conjunction with [ContentBreakLine] - */ -internal object HorizontalBreakLineStyle : Style { - // this exists as a simple internal solution to avoid introducing unnecessary public API on content level. - // If you have the need to implement proper horizontal divider (i.e to support `---` markdown element), - // consider removing this and providing proper API for all formats and levels -} diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt deleted file mode 100644 index 083876d5..00000000 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ /dev/null @@ -1,1013 +0,0 @@ -/* - * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package org.jetbrains.dokka.base.renderers.html - -import kotlinx.html.* -import kotlinx.html.stream.createHTML -import org.jetbrains.dokka.DokkaSourceSetID -import org.jetbrains.dokka.Platform -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.renderers.* -import org.jetbrains.dokka.base.renderers.html.command.consumers.ImmediateResolutionTagConsumer -import org.jetbrains.dokka.base.renderers.html.innerTemplating.DefaultTemplateModelFactory -import org.jetbrains.dokka.base.renderers.html.innerTemplating.DefaultTemplateModelMerger -import org.jetbrains.dokka.base.renderers.html.innerTemplating.DokkaTemplateTypes -import org.jetbrains.dokka.base.renderers.html.innerTemplating.HtmlTemplater -import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint -import org.jetbrains.dokka.base.resolvers.local.DokkaBaseLocationProvider -import org.jetbrains.dokka.base.templating.* -import org.jetbrains.dokka.base.transformers.documentables.CallableExtensions -import org.jetbrains.dokka.base.translators.documentables.shouldDocumentConstructors -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.* -import org.jetbrains.dokka.model.properties.PropertyContainer -import org.jetbrains.dokka.model.properties.WithExtraProperties -import org.jetbrains.dokka.pages.* -import org.jetbrains.dokka.pages.HtmlContent -import org.jetbrains.dokka.plugability.* -import org.jetbrains.dokka.transformers.pages.PageTransformer -import org.jetbrains.dokka.utilities.htmlEscape - -internal const val TEMPLATE_REPLACEMENT: String = "###" -internal const val TOGGLEABLE_CONTENT_TYPE_ATTR = "data-togglable" - -public open class HtmlRenderer( - context: DokkaContext -) : DefaultRenderer(context) { - private val sourceSetDependencyMap: Map> = - context.configuration.sourceSets.associate { sourceSet -> - sourceSet.sourceSetID to context.configuration.sourceSets - .map { it.sourceSetID } - .filter { it in sourceSet.dependentSourceSets } - } - - private val templateModelFactories = listOf(DefaultTemplateModelFactory(context)) // TODO: Make extension point - private val templateModelMerger = DefaultTemplateModelMerger() - private val templater = HtmlTemplater(context).apply { - setupSharedModel(templateModelMerger.invoke(templateModelFactories) { buildSharedModel() }) - } - - private var shouldRenderSourceSetTabs: Boolean = false - - override val preprocessors: List = context.plugin().query { htmlPreprocessors } - - /** - * Tabs themselves are created in HTML plugin since, currently, only HTML format supports them. - * [TabbedContentType] is used to mark content that should be inside tab content. - * A tab can display multiple [TabbedContentType]. - * The content style [ContentStyle.TabbedContent] is used to determine where tabs will be generated. - * - * @see TabbedContentType - * @see ContentStyle.TabbedContent - */ - private fun createTabs(pageContext: ContentPage): List { - return when(pageContext) { - is ClasslikePage -> createTabsForClasslikes(pageContext) - is PackagePage -> createTabsForPackage(pageContext) - else -> throw IllegalArgumentException("Page ${pageContext.name} cannot have tabs") - } - } - - private fun createTabsForClasslikes(page: ClasslikePage): List { - val documentables = page.documentables - val csEnum = documentables.filterIsInstance() - val csWithConstructor = documentables.filterIsInstance() - val scopes = documentables.filterIsInstance() - val constructorsToDocumented = csWithConstructor.flatMap { it.constructors } - - val containsRenderableConstructors = constructorsToDocumented.isNotEmpty() && documentables.shouldDocumentConstructors() - val containsRenderableMembers = - containsRenderableConstructors || scopes.any { it.classlikes.isNotEmpty() || it.functions.isNotEmpty() || it.properties.isNotEmpty() } - - @Suppress("UNCHECKED_CAST") - val extensions = (documentables as List>).flatMap { - it.extra[CallableExtensions]?.extensions - ?.filterIsInstance().orEmpty() - } - .distinctBy { it.sourceSets to it.dri } // [Documentable] has expensive equals/hashCode at the moment, see #2620 - return listOfNotNull( - if(!containsRenderableMembers) null else - ContentTab( - "Members", - listOf( - BasicTabbedContentType.CONSTRUCTOR, - BasicTabbedContentType.TYPE, - BasicTabbedContentType.PROPERTY, - BasicTabbedContentType.FUNCTION - ) - ), - if (extensions.isEmpty()) null else ContentTab( - "Members & Extensions", - listOf( - BasicTabbedContentType.CONSTRUCTOR, - BasicTabbedContentType.TYPE, - BasicTabbedContentType.PROPERTY, - BasicTabbedContentType.FUNCTION, - BasicTabbedContentType.EXTENSION_PROPERTY, - BasicTabbedContentType.EXTENSION_FUNCTION - ) - ), - if(csEnum.isEmpty()) null else ContentTab( - "Entries", - listOf( - BasicTabbedContentType.ENTRY - ) - ) - ) - } - - private fun createTabsForPackage(page: PackagePage): List { - val p = page.documentables.single() as DPackage - return listOfNotNull( - if (p.typealiases.isEmpty() && p.classlikes.isEmpty()) null else ContentTab( - "Types", - listOf( - BasicTabbedContentType.TYPE, - ) - ), - if (p.functions.isEmpty()) null else ContentTab( - "Functions", - listOf( - BasicTabbedContentType.FUNCTION, - BasicTabbedContentType.EXTENSION_FUNCTION, - ) - ), - if (p.properties.isEmpty()) null else ContentTab( - "Properties", - listOf( - BasicTabbedContentType.PROPERTY, - BasicTabbedContentType.EXTENSION_PROPERTY, - ) - ) - ) - } - - private fun TagConsumer.prepareForTemplates() = - if (context.configuration.delayTemplateSubstitution || this is ImmediateResolutionTagConsumer) this - else ImmediateResolutionTagConsumer(this, context) - - override fun FlowContent.wrapGroup( - node: ContentGroup, - pageContext: ContentPage, - childrenCallback: FlowContent.() -> Unit - ) { - val additionalClasses = node.style.joinToString(" ") { it.toString().toLowerCase() } - return when { - node.hasStyle(ContentStyle.TabbedContent) -> div(additionalClasses) { - val contentTabs = createTabs(pageContext) - - div(classes = "tabs-section") { - attributes["tabs-section"] = "tabs-section" - contentTabs.forEachIndexed { index, contentTab -> - button(classes = "section-tab") { - if (index == 0) attributes["data-active"] = "" - attributes[TOGGLEABLE_CONTENT_TYPE_ATTR] = - contentTab.tabbedContentTypes.joinToString(",") { it.toHtmlAttribute() } - text(contentTab.text) - } - } - } - div(classes = "tabs-section-body") { - childrenCallback() - } - } - node.hasStyle(ContentStyle.WithExtraAttributes) -> div { - node.extra.extraHtmlAttributes().forEach { attributes[it.extraKey] = it.extraValue } - childrenCallback() - } - node.dci.kind in setOf(ContentKind.Symbol) -> div("symbol $additionalClasses") { - childrenCallback() - } - node.hasStyle(ContentStyle.KDocTag) -> span("kdoc-tag") { childrenCallback() } - node.hasStyle(ContentStyle.Footnote) -> div("footnote") { childrenCallback() } - node.hasStyle(TextStyle.BreakableAfter) -> { - span { childrenCallback() } - wbr { } - } - node.hasStyle(TextStyle.Breakable) -> { - span("breakable-word") { childrenCallback() } - } - node.hasStyle(TextStyle.Span) -> span { childrenCallback() } - node.dci.kind == ContentKind.Symbol -> div("symbol $additionalClasses") { - childrenCallback() - } - node.dci.kind == SymbolContentKind.Parameters -> { - span("parameters $additionalClasses") { - childrenCallback() - } - } - node.dci.kind == SymbolContentKind.Parameter -> { - span("parameter $additionalClasses") { - childrenCallback() - } - } - node.hasStyle(TextStyle.InlineComment) -> div("inline-comment") { childrenCallback() } - node.dci.kind == ContentKind.BriefComment -> div("brief $additionalClasses") { childrenCallback() } - node.dci.kind == ContentKind.Cover -> div("cover $additionalClasses") { //TODO this can be removed - childrenCallback() - } - node.dci.kind == ContentKind.Deprecation -> div("deprecation-content") { childrenCallback() } - node.hasStyle(TextStyle.Paragraph) -> p(additionalClasses) { childrenCallback() } - node.hasStyle(TextStyle.Block) -> div(additionalClasses) { - childrenCallback() - } - node.hasStyle(TextStyle.Quotation) -> blockQuote(additionalClasses) { childrenCallback() } - node.hasStyle(TextStyle.FloatingRight) -> span("clearfix") { span("floating-right") { childrenCallback() } } - node.hasStyle(TextStyle.Strikethrough) -> strike { childrenCallback() } - node.isAnchorable -> buildAnchor( - node.anchor!!, - node.anchorLabel!!, - node.buildSourceSetFilterValues() - ) { childrenCallback() } - node.extra[InsertTemplateExtra] != null -> node.extra[InsertTemplateExtra]?.let { templateCommand(it.command) } - ?: Unit - node.hasStyle(ListStyle.DescriptionTerm) -> DT(emptyMap(), consumer).visit { - this@wrapGroup.childrenCallback() - } - node.hasStyle(ListStyle.DescriptionDetails) -> DD(emptyMap(), consumer).visit { - this@wrapGroup.childrenCallback() - } - node.extra.extraTabbedContentType() != null -> div() { - node.extra.extraTabbedContentType()?.let { attributes[TOGGLEABLE_CONTENT_TYPE_ATTR] = it.value.toHtmlAttribute() } - this@wrapGroup.childrenCallback() - } - else -> childrenCallback() - } - } - - private fun FlowContent.copyButton() = span(classes = "top-right-position") { - span("copy-icon") - copiedPopup("Content copied to clipboard", "popup-to-left") - } - - private fun FlowContent.copiedPopup(notificationContent: String, additionalClasses: String = "") = - div("copy-popup-wrapper $additionalClasses") { - span("copy-popup-icon") - span { - text(notificationContent) - } - } - - override fun FlowContent.buildPlatformDependent( - content: PlatformHintedContent, - pageContext: ContentPage, - sourceSetRestriction: Set? - ) { - buildPlatformDependent( - content.sourceSets.filter { - sourceSetRestriction == null || it in sourceSetRestriction - }.associateWith { setOf(content.inner) }, - pageContext, - content.extra, - content.style - ) - } - - private fun FlowContent.buildPlatformDependent( - nodes: Map>, - pageContext: ContentPage, - extra: PropertyContainer = PropertyContainer.empty(), - styles: Set