<lambda>null1 package org.jetbrains.dokka
2
3 import com.google.inject.Inject
4 import com.google.inject.Singleton
5 import com.intellij.ide.highlighter.JavaFileType
6 import com.intellij.psi.PsiClass
7 import com.intellij.psi.PsiFileFactory
8 import com.intellij.psi.util.PsiTreeUtil
9 import com.intellij.util.LocalTimeCounter
10 import org.intellij.markdown.MarkdownElementTypes
11 import org.intellij.markdown.MarkdownTokenTypes
12 import org.intellij.markdown.parser.LinkMap
13 import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
14 import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
15 import java.io.File
16
17 @Singleton
18 class PackageDocs
19 @Inject constructor(val linkResolver: DeclarationLinkResolver?,
20 val logger: DokkaLogger,
21 val environment: KotlinCoreEnvironment,
22 val refGraph: NodeReferenceGraph,
23 val elementSignatureProvider: ElementSignatureProvider)
24 {
25 val moduleContent: MutableContent = MutableContent()
26 private val _packageContent: MutableMap<String, MutableContent> = hashMapOf()
27 val packageContent: Map<String, Content>
28 get() = _packageContent
29
30 fun parse(fileName: String, linkResolveContext: List<PackageFragmentDescriptor>) {
31 val file = File(fileName)
32 if (file.exists()) {
33 val text = file.readText()
34 val tree = parseMarkdown(text)
35 val linkMap = LinkMap.buildLinkMap(tree.node, text)
36 var targetContent: MutableContent = moduleContent
37 tree.children.forEach {
38 if (it.type == MarkdownElementTypes.ATX_1) {
39 val headingText = it.child(MarkdownTokenTypes.ATX_CONTENT)?.text
40 if (headingText != null) {
41 targetContent = findTargetContent(headingText.trimStart())
42 }
43 } else {
44 buildContentTo(it, targetContent, LinkResolver(linkMap, { resolveContentLink(fileName, it, linkResolveContext) }))
45 }
46 }
47 } else {
48 logger.warn("Include file $file was not found.")
49 }
50 }
51
52 private fun parseHtmlAsJavadoc(text: String, packageName: String, file: File) {
53 val javadocText = text
54 .replace("*/", "*/")
55 .removeSurrounding("<html>", "</html>", true).trim()
56 .removeSurrounding("<body>", "</body>", true)
57 .lineSequence()
58 .map { "* $it" }
59 .joinToString (separator = "\n", prefix = "/**\n", postfix = "\n*/")
60 parseJavadoc(javadocText, packageName, file)
61 }
62
63 private fun CharSequence.removeSurrounding(prefix: CharSequence, suffix: CharSequence, ignoringCase: Boolean = false): CharSequence {
64 if ((length >= prefix.length + suffix.length) && startsWith(prefix, ignoringCase) && endsWith(suffix, ignoringCase)) {
65 return subSequence(prefix.length, length - suffix.length)
66 }
67 return subSequence(0, length)
68 }
69
70
71 private fun parseJavadoc(text: String, packageName: String, file: File) {
72
73 val psiFileFactory = PsiFileFactory.getInstance(environment.project)
74 val psiFile = psiFileFactory.createFileFromText(
75 file.nameWithoutExtension + ".java",
76 JavaFileType.INSTANCE,
77 "package $packageName; $text\npublic class C {}",
78 LocalTimeCounter.currentTime(),
79 false,
80 true
81 )
82
83 val psiClass = PsiTreeUtil.getChildOfType(psiFile, PsiClass::class.java)!!
84 val parser = JavadocParser(refGraph, logger, elementSignatureProvider, linkResolver?.externalDocumentationLinkResolver!!)
85 findOrCreatePackageContent(packageName).apply {
86 val content = parser.parseDocumentation(psiClass).content
87 children.addAll(content.children)
88 content.sections.forEach {
89 addSection(it.tag, it.subjectName).children.addAll(it.children)
90 }
91 }
92 }
93
94
95 fun parseJava(fileName: String, packageName: String) {
96 val file = File(fileName)
97 if (file.exists()) {
98 val text = file.readText()
99
100 val trimmedText = text.trim()
101
102 if (trimmedText.startsWith("/**")) {
103 parseJavadoc(text, packageName, file)
104 } else if (trimmedText.toLowerCase().startsWith("<html>")) {
105 parseHtmlAsJavadoc(trimmedText, packageName, file)
106 }
107 }
108 }
109
110 private fun findTargetContent(heading: String): MutableContent {
111 if (heading.startsWith("Module") || heading.startsWith("module")) {
112 return moduleContent
113 }
114 if (heading.startsWith("Package") || heading.startsWith("package")) {
115 return findOrCreatePackageContent(heading.substring("package".length).trim())
116 }
117 return findOrCreatePackageContent(heading)
118 }
119
120 private fun findOrCreatePackageContent(packageName: String) =
121 _packageContent.getOrPut(packageName) { -> MutableContent() }
122
123 private fun resolveContentLink(fileName: String, href: String, linkResolveContext: List<PackageFragmentDescriptor>): ContentBlock {
124 if (linkResolver != null) {
125 linkResolveContext
126 .asSequence()
127 .map { p -> linkResolver.tryResolveContentLink(p, href) }
128 .filterNotNull()
129 .firstOrNull()
130 ?.let { return it }
131 }
132 logger.warn("Unresolved link to `$href` in include ($fileName)")
133 return ContentExternalLink("#")
134 }
135 }