aboutsummaryrefslogtreecommitdiff
path: root/plugins/all-modules-page/src/main/kotlin/MultimodulePageCreator.kt
blob: 4285046a1608d2af9d7c9bd9448d1bfcfc4361ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package org.jetbrains.dokka.allModulesPage

import org.jetbrains.dokka.DokkaConfiguration.DokkaModuleDescription
import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.base.parsers.MarkdownParser
import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentation.Classifier.Module
import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentationParsingContext
import org.jetbrains.dokka.base.parsers.moduleAndPackage.parseModuleAndPackageDocumentation
import org.jetbrains.dokka.base.parsers.moduleAndPackage.parseModuleAndPackageDocumentationFragments
import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint
import org.jetbrains.dokka.base.templating.InsertTemplateExtra
import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter
import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.doc.DocTag
import org.jetbrains.dokka.model.doc.DocumentationNode
import org.jetbrains.dokka.model.doc.P
import org.jetbrains.dokka.model.properties.PropertyContainer
import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.configuration
import org.jetbrains.dokka.plugability.plugin
import org.jetbrains.dokka.plugability.querySingle
import org.jetbrains.dokka.transformers.pages.PageCreator
import org.jetbrains.dokka.utilities.DokkaLogger
import org.jetbrains.dokka.versioning.ReplaceVersionsCommand
import org.jetbrains.dokka.versioning.VersioningConfiguration
import org.jetbrains.dokka.versioning.VersioningPlugin
import java.io.File

class MultimodulePageCreator(
    private val context: DokkaContext,
) : PageCreator<AllModulesPageGeneration.DefaultAllModulesContext> {
    private val logger: DokkaLogger = context.logger

    private val commentsConverter by lazy { context.plugin<DokkaBase>().querySingle { commentsToContentConverter } }
    private val signatureProvider by lazy { context.plugin<DokkaBase>().querySingle { signatureProvider } }

    override fun invoke(creationContext: AllModulesPageGeneration.DefaultAllModulesContext): RootPageNode {
        val modules = context.configuration.modules
        val sourceSetData = emptySet<DokkaSourceSet>()
        val builder = PageContentBuilder(commentsConverter, signatureProvider, context.logger)
        val contentNode = builder.contentFor(
            dri = DRI(MULTIMODULE_PACKAGE_PLACEHOLDER),
            kind = ContentKind.Cover,
            sourceSets = sourceSetData
        ) {
            /* The line below checks if there is a provided configuration for versioning.
             If not, we are skipping the template for inserting versions navigation */
            configuration<VersioningPlugin, VersioningConfiguration>(context)?.let {
                group(extra = PropertyContainer.withAll(InsertTemplateExtra(ReplaceVersionsCommand))) { }
            }
            getMultiModuleDocumentation(context.configuration.includes).takeIf { it.isNotEmpty() }?.let { nodes ->
                group(kind = ContentKind.Cover) {
                    nodes.forEach { node ->
                        group {
                            node.children.forEach { comment(it.root) }
                        }
                    }
                }
            }
            header(2, "All modules:")
            table(styles = setOf(MultimoduleTable)) {
                header { group { text("Name") } }
                modules.filter { it.name in creationContext.nonEmptyModules }.sortedBy { it.name }
                    .forEach { module ->
                        val displayedModuleDocumentation = getDisplayedModuleDocumentation(module)
                        val dri = DRI(packageName = MULTIMODULE_PACKAGE_PLACEHOLDER, classNames = module.name)
                        val dci = DCI(setOf(dri), ContentKind.Comment)
                        val extraWithAnchor = PropertyContainer.withAll(SymbolAnchorHint(module.name, ContentKind.Main))
                        row(setOf(dri), emptySet(), styles = emptySet(), extra = extraWithAnchor) {
                            +linkNode(module.name, dri, DCI(setOf(dri), ContentKind.Main), extra = extraWithAnchor)
                            +ContentGroup(
                                children =
                                if (displayedModuleDocumentation != null)
                                    DocTagToContentConverter().buildContent(
                                        displayedModuleDocumentation,
                                        dci,
                                        emptySet()
                                    )
                                else emptyList(),
                                dci = dci,
                                sourceSets = emptySet(),
                                style = emptySet()
                            )
                        }
                    }
            }
        }
        return MultimoduleRootPageNode(
            setOf(MULTIMODULE_ROOT_DRI),
            contentNode
        )
    }

    private fun getMultiModuleDocumentation(files: Set<File>): List<DocumentationNode> =
        files.map { MarkdownParser({ null }, it.absolutePath).parse(it.readText()) }

    private fun getDisplayedModuleDocumentation(module: DokkaModuleDescription): P? {
        val parsingContext = ModuleAndPackageDocumentationParsingContext(logger)

        val documentationFragment = module.includes
            .flatMap { include -> parseModuleAndPackageDocumentationFragments(include) }
            .firstOrNull { fragment -> fragment.classifier == Module && fragment.name == module.name }
            ?: return null

        val moduleDocumentation = parseModuleAndPackageDocumentation(parsingContext, documentationFragment)
        return moduleDocumentation.documentation.firstParagraph()
    }

    private fun DocumentationNode.firstParagraph(): P? =
        this.children
            .map { it.root }
            .mapNotNull { it.firstParagraph() }
            .firstOrNull()

    /**
     * @return The very first, most inner paragraph. If any [P] is wrapped inside another [P], the inner one
     * is preferred.
     */
    private fun DocTag.firstParagraph(): P? {
        val firstChildParagraph = children.mapNotNull { it.firstParagraph() }.firstOrNull()
        return if (firstChildParagraph == null && this is P) this
        else firstChildParagraph
    }

    companion object {
        const val MULTIMODULE_PACKAGE_PLACEHOLDER = ".ext"
        val MULTIMODULE_ROOT_DRI = DRI(packageName = MULTIMODULE_PACKAGE_PLACEHOLDER, classNames = "allModules")
    }
}