aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/Model/PackageDocs.kt
blob: b24efc5d8d9d496434a187019d11ebcb9cd25599 (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
133
134
135
136
package org.jetbrains.dokka

import com.google.inject.Inject
import com.google.inject.Singleton
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.LocalTimeCounter
import com.intellij.openapi.util.text.StringUtil
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.parser.LinkMap
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import java.io.File

@Singleton
class PackageDocs
        @Inject constructor(val linkResolver: DeclarationLinkResolver?,
                            val logger: DokkaLogger,
                            val environment: KotlinCoreEnvironment,
                            val refGraph: NodeReferenceGraph,
                            val elementSignatureProvider: ElementSignatureProvider)
{
    val moduleContent: MutableContent = MutableContent()
    private val _packageContent: MutableMap<String, MutableContent> = hashMapOf()
    val packageContent: Map<String, Content>
        get() = _packageContent

    fun parse(fileName: String, linkResolveContext: List<PackageFragmentDescriptor>) {
        val file = File(fileName)
        if (file.exists()) {
            val text = StringUtil.convertLineSeparators(file.readText())
            val tree = parseMarkdown(text)
            val linkMap = LinkMap.buildLinkMap(tree.node, text)
            var targetContent: MutableContent = moduleContent
            tree.children.forEach {
                if (it.type == MarkdownElementTypes.ATX_1) {
                    val headingText = it.child(MarkdownTokenTypes.ATX_CONTENT)?.text
                    if (headingText != null) {
                        targetContent = findTargetContent(headingText.trimStart())
                    }
                } else {
                    buildContentTo(it, targetContent, LinkResolver(linkMap) { resolveContentLink(fileName, it, linkResolveContext) })
                }
            }
        } else {
            logger.warn("Include file $file was not found.")
        }
    }

    private fun parseHtmlAsJavadoc(text: String, packageName: String, file: File) {
        val javadocText = text
                .replace("*/", "*&#47;")
                .removeSurrounding("<html>", "</html>", true).trim()
                .removeSurrounding("<body>", "</body>", true)
                .lineSequence()
                .map { "* $it" }
                .joinToString (separator = "\n", prefix = "/**\n", postfix = "\n*/")
        parseJavadoc(javadocText, packageName, file)
    }

    private fun CharSequence.removeSurrounding(prefix: CharSequence, suffix: CharSequence, ignoringCase: Boolean = false): CharSequence {
        if ((length >= prefix.length + suffix.length) && startsWith(prefix, ignoringCase) && endsWith(suffix, ignoringCase)) {
            return subSequence(prefix.length, length - suffix.length)
        }
        return subSequence(0, length)
    }


    private fun parseJavadoc(text: String, packageName: String, file: File) {

        val psiFileFactory = PsiFileFactory.getInstance(environment.project)
        val psiFile = psiFileFactory.createFileFromText(
                file.nameWithoutExtension + ".java",
                JavaFileType.INSTANCE,
                "package $packageName; $text\npublic class C {}",
                LocalTimeCounter.currentTime(),
                false,
                true
        )

        val psiClass = PsiTreeUtil.getChildOfType(psiFile, PsiClass::class.java)!!
        val parser = JavadocParser(refGraph, logger, elementSignatureProvider, linkResolver?.externalDocumentationLinkResolver!!)
        findOrCreatePackageContent(packageName).apply {
            val content = parser.parseDocumentation(psiClass).content
            children.addAll(content.children)
            content.sections.forEach {
                addSection(it.tag, it.subjectName).children.addAll(it.children)
            }
        }
    }


    fun parseJava(fileName: String, packageName: String) {
        val file = File(fileName)
        if (file.exists()) {
            val text = file.readText()

            val trimmedText = text.trim()

            if (trimmedText.startsWith("/**")) {
                parseJavadoc(text, packageName, file)
            } else if (trimmedText.toLowerCase().startsWith("<html>")) {
                parseHtmlAsJavadoc(trimmedText, packageName, file)
            }
        }
    }

    private fun findTargetContent(heading: String): MutableContent {
        if (heading.startsWith("Module") || heading.startsWith("module")) {
            return moduleContent
        }
        if (heading.startsWith("Package") || heading.startsWith("package")) {
            return findOrCreatePackageContent(heading.substring("package".length).trim())
        }
        return findOrCreatePackageContent(heading)
    }

    private fun findOrCreatePackageContent(packageName: String) =
        _packageContent.getOrPut(packageName) { MutableContent() }

    private fun resolveContentLink(fileName: String, href: String, linkResolveContext: List<PackageFragmentDescriptor>): ContentBlock {
        if (linkResolver != null) {
            linkResolveContext
                    .asSequence()
                    .map { p -> linkResolver.tryResolveContentLink(p, href) }
                    .filterNotNull()
                    .firstOrNull()
                    ?.let { return it }
        }
        logger.warn("Unresolved link to `$href` in include ($fileName)")
        return ContentExternalLink("#")
    }
}