All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.dokka.base.renderers.DefaultRenderer.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * Copyright 2014-2024 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




© 2015 - 2025 Weber Informatics LLC | Privacy Policy