From 8ff57cd803cabf733e1adf2ad878f0caac070193 Mon Sep 17 00:00:00 2001 From: mkondratek Date: Fri, 23 Oct 2020 18:19:45 +0200 Subject: [PATCH 1/5] Use dokka md parser --- .../virtuslab/dokka/site/StaticSiteContext.kt | 12 +- .../kotlin/com/virtuslab/dokka/site/parser.kt | 359 ------------------ src/test/kotlin/parserTests.kt | 34 +- 3 files changed, 26 insertions(+), 379 deletions(-) delete mode 100644 src/main/kotlin/com/virtuslab/dokka/site/parser.kt diff --git a/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt b/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt index b6603d2..1fcdc92 100644 --- a/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt +++ b/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt @@ -1,5 +1,6 @@ package com.virtuslab.dokka.site +import org.jetbrains.dokka.base.parsers.MarkdownParser import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.doc.DocTag @@ -13,7 +14,7 @@ import org.jetbrains.dokka.pages.PageNode import org.jetbrains.dokka.plugability.DokkaContext import java.io.File -class StaticSiteContext(val root: File, cxt: DokkaContext){ +class StaticSiteContext(val root: File, cxt: DokkaContext) { val docsFile = File(root, "docs") fun indexPage(): BaseStaticSiteProcessor.StaticPageNode? { @@ -65,7 +66,7 @@ class StaticSiteContext(val root: File, cxt: DokkaContext){ private fun parseMarkdown(page: PreResolvedPage, dri: DRI, allDRIs: Map): ContentNode { val nodes = if (page.hasMarkdown) { - val parser = ExtendableMarkdownParser(page.code) { link -> + val parser = MarkdownParser { link -> val driKey = if (link.startsWith("/")) { // handle root related links link.replace('/', '.').removePrefix(".") @@ -78,7 +79,7 @@ class StaticSiteContext(val root: File, cxt: DokkaContext){ } val docTag = try { - parser.parse() + parser.parseStringToDocNode(page.code) } catch (e: Throwable) { val msg = "Error rendering (dri = $dri): ${e.message}" println("ERROR: $msg") // TODO (#14): provide proper error handling @@ -105,7 +106,10 @@ class StaticSiteContext(val root: File, cxt: DokkaContext){ PropertyContainer.empty() ) - fun loadFiles(files: List, customChildren: List = emptyList()): List { + fun loadFiles( + files: List, + customChildren: List = emptyList() + ): List { val all = files.mapNotNull { loadTemplate(it) } fun flatten(it: BaseStaticSiteProcessor.LoadedTemplate): List = listOf(it.relativePath(root)) + it.children.flatMap { flatten(it) } diff --git a/src/main/kotlin/com/virtuslab/dokka/site/parser.kt b/src/main/kotlin/com/virtuslab/dokka/site/parser.kt deleted file mode 100644 index 541d5c1..0000000 --- a/src/main/kotlin/com/virtuslab/dokka/site/parser.kt +++ /dev/null @@ -1,359 +0,0 @@ -package com.virtuslab.dokka.site - -import org.intellij.markdown.MarkdownElementTypes -import org.intellij.markdown.MarkdownTokenTypes -import org.intellij.markdown.ast.ASTNode -import org.intellij.markdown.ast.CompositeASTNode -import org.intellij.markdown.ast.LeafASTNode -import org.intellij.markdown.ast.impl.ListItemCompositeNode -import org.intellij.markdown.flavours.gfm.GFMElementTypes -import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor -import org.intellij.markdown.flavours.gfm.GFMTokenTypes -import org.intellij.markdown.parser.MarkdownParser -import org.jetbrains.dokka.base.parsers.factories.DocTagsFromIElementFactory -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.doc.DocTag -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import java.net.MalformedURLException -import java.net.URL - - -// TODO (#24): This is a code taken from dokka, will be removed after PR with fixes is merged into dokka -// The dokka logic was pulled to `externalDir` callback and `linksHandler` was simplified -// Bug in imagesHandler was fixed (NullPointer) -open class ExtendableMarkdownParser(private val text: String, private val externalDri: (String) -> DRI?) { - - fun parse(): DocTag { - val flavourDescriptor = GFMFlavourDescriptor() - val markdownAstRoot: ASTNode = MarkdownParser(flavourDescriptor).buildMarkdownTreeFromString(text) - return visitNode(markdownAstRoot) - } - - private fun headersHandler(node: ASTNode): DocTag = - DocTagsFromIElementFactory.getInstance( - node.type, - visitNode(node.children.find { it.type == MarkdownTokenTypes.ATX_CONTENT } - ?: throw IllegalStateException("Wrong AST Tree. ATX Header does not contain expected content")).children - ) - - private fun horizontalRulesHandler(node: ASTNode): DocTag = - DocTagsFromIElementFactory.getInstance(MarkdownTokenTypes.HORIZONTAL_RULE) - - private fun emphasisHandler(node: ASTNode): DocTag = - DocTagsFromIElementFactory.getInstance( - node.type, - children = node.children.evaluateChildrenWithDroppedEnclosingTokens(1) - ) - - private fun strongHandler(node: ASTNode): DocTag = - DocTagsFromIElementFactory.getInstance( - node.type, - children = node.children.evaluateChildrenWithDroppedEnclosingTokens(2) - ) - - private fun List.evaluateChildrenWithDroppedEnclosingTokens(count: Int) = - drop(count).dropLast(count).evaluateChildren() - - private fun blockquotesHandler(node: ASTNode): DocTag = - DocTagsFromIElementFactory.getInstance( - node.type, children = node.children - .filterIsInstance() - .evaluateChildren() - ) - - private fun listsHandler(node: ASTNode): DocTag { - - val children = node.children.filterIsInstance().flatMap { - if (it.children.last().type in listOf( - MarkdownElementTypes.ORDERED_LIST, - MarkdownElementTypes.UNORDERED_LIST - ) - ) { - val nestedList = it.children.last() - (it.children as MutableList).removeAt(it.children.lastIndex) - listOf(it, nestedList) - } else - listOf(it) - } - - return DocTagsFromIElementFactory.getInstance( - node.type, - children = - children - .map { - if (it.type == MarkdownElementTypes.LIST_ITEM) - DocTagsFromIElementFactory.getInstance( - it.type, - children = it - .children - .filterIsInstance() - .evaluateChildren() - ) - else - visitNode(it) - }, - params = - if (node.type == MarkdownElementTypes.ORDERED_LIST) { - val listNumberNode = node.children.first().children.first() - mapOf( - "start" to text.substring( - listNumberNode.startOffset, - listNumberNode.endOffset - ).trim().dropLast(1) - ) - } else - emptyMap() - ) - } - - private fun resolveDRI(mdLink: String): DRI? = - mdLink - .removePrefix("[") - .removeSuffix("]") - .let { link -> - try { - URL(link) - null - } catch (e: MalformedURLException) { - externalDri(link) - } - } - - private fun Collection.sorted() = sortedWith( - compareBy( - { it is ClassDescriptor }, - { (it as? FunctionDescriptor)?.name }, - { (it as? FunctionDescriptor)?.valueParameters?.size }, - { (it as? FunctionDescriptor)?.valueParameters?.joinToString { it.type.toString() } } - ) - ) - - private fun referenceLinksHandler(node: ASTNode): DocTag { - val linkLabel = node.children.find { it.type == MarkdownElementTypes.LINK_LABEL } - ?: throw IllegalStateException("Wrong AST Tree. Reference link does not contain expected content") - val linkText = node.children.findLast { it.type == MarkdownElementTypes.LINK_TEXT } ?: linkLabel - - val linkKey = text.substring(linkLabel.startOffset, linkLabel.endOffset) - - return linksHandler(linkText, linkKey) - } - - private fun inlineLinksHandler(node: ASTNode): DocTag { - val linkText = node.children.find { it.type == MarkdownElementTypes.LINK_TEXT } - ?: throw IllegalStateException("Wrong AST Tree. Inline link does not contain expected content") - val linkDestination = node.children.find { it.type == MarkdownElementTypes.LINK_DESTINATION } - ?: throw IllegalStateException("Wrong AST Tree. Inline link does not contain expected content") - val linkTitle = node.children.find { it.type == MarkdownElementTypes.LINK_TITLE } - - val link = text.substring(linkDestination.startOffset, linkDestination.endOffset) - - return linksHandler(linkText, link, linkTitle) - } - - private fun autoLinksHandler(node: ASTNode): DocTag { - val link = text.substring(node.startOffset + 1, node.endOffset - 1) - - return linksHandler(node, link) - } - - private fun linksHandler(linkText: ASTNode, link: String, linkTitle: ASTNode? = null): DocTag { - val dri: DRI? = resolveDRI(link) - val linkTextString = - if (linkTitle == null) link else text.substring(linkTitle.startOffset + 1, linkTitle.endOffset - 1) - - val params = if (linkTitle == null) - mapOf("href" to link) - else - mapOf("href" to link, "title" to linkTextString) - - return DocTagsFromIElementFactory.getInstance( - MarkdownElementTypes.INLINE_LINK, - params = params, - children = linkText.children.drop(1).dropLast(1).evaluateChildren(), - dri = dri - ) - } - - private fun imagesHandler(node: ASTNode): DocTag { - val linkNode = - node.children.last().children.find { it.type == MarkdownElementTypes.LINK_LABEL }?.children?.lastOrNull() - val link = linkNode?.let{ text.substring(it.startOffset, it.endOffset)} ?: "_" - val src = mapOf("src" to link, "href" to link) - return DocTagsFromIElementFactory.getInstance( - node.type, - params = src, - children = listOf(visitNode(node.children.last().children.find { it.type == MarkdownElementTypes.LINK_TEXT }!!)) - ) - } - - private fun codeSpansHandler(node: ASTNode): DocTag = - DocTagsFromIElementFactory.getInstance( - node.type, - children = listOf( - DocTagsFromIElementFactory.getInstance( - MarkdownTokenTypes.TEXT, - body = text.substring(node.startOffset + 1, node.endOffset - 1).replace('\n', ' ').trimIndent() - ) - - ) - ) - - private fun codeFencesHandler(node: ASTNode): DocTag = - DocTagsFromIElementFactory.getInstance( - node.type, - children = node - .children - .dropWhile { it.type != MarkdownTokenTypes.CODE_FENCE_CONTENT } - .dropLastWhile { it.type != MarkdownTokenTypes.CODE_FENCE_CONTENT } - .map { - if (it.type == MarkdownTokenTypes.EOL) - LeafASTNode(MarkdownTokenTypes.HARD_LINE_BREAK, 0, 0) - else - it - }.evaluateChildren(), - params = node - .children - .find { it.type == MarkdownTokenTypes.FENCE_LANG } - ?.let { mapOf("lang" to text.substring(it.startOffset, it.endOffset)) } - ?: emptyMap() - ) - - private fun codeBlocksHandler(node: ASTNode): DocTag = - DocTagsFromIElementFactory.getInstance(node.type, children = node.children.mergeLeafASTNodes().map { - DocTagsFromIElementFactory.getInstance( - MarkdownTokenTypes.TEXT, - body = text.substring(it.startOffset, it.endOffset) - ) - }) - - private fun defaultHandler(node: ASTNode): DocTag = - DocTagsFromIElementFactory.getInstance( - MarkdownElementTypes.PARAGRAPH, - children = node.children.evaluateChildren() - ) - - fun visitNode(node: ASTNode): DocTag = - when (node.type) { - MarkdownElementTypes.ATX_1, - MarkdownElementTypes.ATX_2, - MarkdownElementTypes.ATX_3, - MarkdownElementTypes.ATX_4, - MarkdownElementTypes.ATX_5, - MarkdownElementTypes.ATX_6 -> headersHandler(node) - MarkdownTokenTypes.HORIZONTAL_RULE -> horizontalRulesHandler(node) - MarkdownElementTypes.STRONG -> strongHandler(node) - MarkdownElementTypes.EMPH -> emphasisHandler(node) - MarkdownElementTypes.FULL_REFERENCE_LINK, - MarkdownElementTypes.SHORT_REFERENCE_LINK -> referenceLinksHandler(node) - MarkdownElementTypes.INLINE_LINK -> inlineLinksHandler(node) - MarkdownElementTypes.AUTOLINK -> autoLinksHandler(node) - MarkdownElementTypes.BLOCK_QUOTE -> blockquotesHandler(node) - MarkdownElementTypes.UNORDERED_LIST, - MarkdownElementTypes.ORDERED_LIST -> listsHandler(node) - MarkdownElementTypes.CODE_BLOCK -> codeBlocksHandler(node) - MarkdownElementTypes.CODE_FENCE -> codeFencesHandler(node) - MarkdownElementTypes.CODE_SPAN -> codeSpansHandler(node) - MarkdownElementTypes.IMAGE -> imagesHandler(node) - MarkdownTokenTypes.HARD_LINE_BREAK -> DocTagsFromIElementFactory.getInstance(node.type) - MarkdownTokenTypes.CODE_FENCE_CONTENT, - MarkdownTokenTypes.CODE_LINE -> DocTagsFromIElementFactory.getInstance( - MarkdownElementTypes.CODE_BLOCK, - body = text.substring(node.startOffset, node.endOffset) - ) - MarkdownTokenTypes.TEXT -> DocTagsFromIElementFactory.getInstance( - MarkdownTokenTypes.TEXT, - body = text.substring(node.startOffset, node.endOffset).transform() - ) - MarkdownElementTypes.MARKDOWN_FILE -> if (node.children.size == 1) visitNode(node.children.first()) else defaultHandler( - node - ) - GFMElementTypes.STRIKETHROUGH -> DocTagsFromIElementFactory.getInstance( - GFMElementTypes.STRIKETHROUGH, - body = text - .substring(node.startOffset, node.endOffset).transform() - ) - GFMElementTypes.TABLE -> DocTagsFromIElementFactory.getInstance( - GFMElementTypes.TABLE, - children = node.children.filterTabSeparators().evaluateChildren() - ) - GFMElementTypes.HEADER -> DocTagsFromIElementFactory.getInstance( - GFMElementTypes.HEADER, - children = node.children.filterTabSeparators().evaluateChildren() - ) - GFMElementTypes.ROW -> DocTagsFromIElementFactory.getInstance( - GFMElementTypes.ROW, - children = node.children.filterTabSeparators().evaluateChildren() - ) - else -> defaultHandler(node) - } - - private fun List.filterTabSeparators() = - this.filterNot { it.type == GFMTokenTypes.TABLE_SEPARATOR } - - private fun List.evaluateChildren(): List = - this.removeUselessTokens().mergeLeafASTNodes().map { visitNode(it) } - - private fun List.removeUselessTokens(): List = - this.filterIndexed { index, node -> - !(node.type == MarkdownElementTypes.LINK_DEFINITION || ( - node.type == MarkdownTokenTypes.EOL && - this.getOrNull(index - 1)?.type == MarkdownTokenTypes.HARD_LINE_BREAK - )) - } - - private val notLeafNodes = listOf(MarkdownTokenTypes.HORIZONTAL_RULE, MarkdownTokenTypes.HARD_LINE_BREAK) - - private fun List.isNotLeaf(index: Int): Boolean = - if (index in 0..this.lastIndex) - (this[index] is CompositeASTNode) || this[index].type in notLeafNodes - else - false - - private fun List.mergeLeafASTNodes(): List { - val children: MutableList = mutableListOf() - var index = 0 - while (index <= this.lastIndex) { - if (this.isNotLeaf(index)) { - children += this[index] - } else { - val startOffset = this[index].startOffset - val sIndex = index - while (index < this.lastIndex) { - if (this.isNotLeaf(index + 1) || this[index + 1].startOffset != this[index].endOffset) { - mergedLeafNode(this, index, startOffset, sIndex)?.run { - children += this - } - break - } - index++ - } - if (index == this.lastIndex) { - mergedLeafNode(this, index, startOffset, sIndex)?.run { - children += this - } - } - } - index++ - } - return children - } - - private fun mergedLeafNode(nodes: List, index: Int, startOffset: Int, sIndex: Int): LeafASTNode? { - val endOffset = nodes[index].endOffset - if (text.substring(startOffset, endOffset).transform().trim().isNotEmpty()) { - val type = if (nodes.subList(sIndex, index) - .any { it.type == MarkdownTokenTypes.CODE_LINE } - ) MarkdownTokenTypes.CODE_LINE else MarkdownTokenTypes.TEXT - return LeafASTNode(type, startOffset, endOffset) - } - return null - } - - private fun String.transform() = this - .replace(Regex("\n\n+"), "") // Squashing new lines between paragraphs - .replace(Regex("\n"), " ") - .replace(Regex(" >+ +"), " ") // Replacement used in blockquotes, get rid of garbage -} - diff --git a/src/test/kotlin/parserTests.kt b/src/test/kotlin/parserTests.kt index 87a2e2b..eabad14 100644 --- a/src/test/kotlin/parserTests.kt +++ b/src/test/kotlin/parserTests.kt @@ -1,21 +1,25 @@ package com.virtuslab.dokka.site +import org.intellij.markdown.MarkdownElementTypes.MARKDOWN_FILE +import org.jetbrains.dokka.base.parsers.MarkdownParser +import org.jetbrains.dokka.base.parsers.factories.DocTagsFromIElementFactory import org.jetbrains.dokka.model.doc.* import org.junit.Assert.assertEquals import org.junit.Test class ParserTest { - private fun runTest(md: String, expected: DocTag) { - val parser = ExtendableMarkdownParser(md) { null } - val compiled = parser.parse() - assertEquals(expected, compiled) + private fun runTest(md: String, expected: List) { + val parser = MarkdownParser { null } + val compiled = parser.parseStringToDocNode(md) + val expectedWrapped = DocTagsFromIElementFactory.getInstance(MARKDOWN_FILE, expected) + assertEquals(expectedWrapped, compiled) } @Test - fun simpleTest() = runTest("ala", P(listOf(Text("ala")))) + fun simpleTest() = runTest("ala", listOf(P(listOf(Text("ala"))))) @Test fun code() = runTest( @@ -24,7 +28,7 @@ class ParserTest { def ala() = 123 ``` """.trimIndent(), - CodeBlock(listOf(Text("def ala() = 123")), mapOf("lang" to "scala")) + listOf(CodeBlock(listOf(Text("def ala() = 123")), mapOf("lang" to "scala"))) ) @Test @@ -32,7 +36,7 @@ class ParserTest { """ [link](ala/maKota.md) """.trimIndent(), - P(listOf(A(listOf(Text("link")), mapOf("href" to "ala/maKota.md")))) + listOf(P(listOf(Text(body = "ala/maKota.md", listOf(Text("link")), mapOf("href" to "ala/maKota.md"))))) ) @Test @@ -43,16 +47,14 @@ class ParserTest { - element 1 - element 2 """.trimIndent(), - P( - listOf( - P(listOf(Text("List:"))), - Ul( - listOf( - Li(listOf(P(listOf(Text("element 1"))))), - Li(listOf(P(listOf(Text("element 2"))))) - ) + listOf( + P(listOf(Text("List:"))), + Ul( + listOf( + Li(listOf(P(listOf(Text("element 1"))))), + Li(listOf(P(listOf(Text("element 2"))))) ) ) ) ) -} \ No newline at end of file +} From f66df58d2e1939d1119006de4efd07f397227d12 Mon Sep 17 00:00:00 2001 From: mkondratek Date: Thu, 17 Sep 2020 11:56:08 +0200 Subject: [PATCH 2/5] Handle api links --- documentation/docs/static-page/links.md | 13 +++-- .../virtuslab/dokka/site/StaticSiteContext.kt | 53 +++++++++++++++---- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/documentation/docs/static-page/links.md b/documentation/docs/static-page/links.md index 96438dd..3413b1c 100644 --- a/documentation/docs/static-page/links.md +++ b/documentation/docs/static-page/links.md @@ -6,7 +6,12 @@ title: Links ## Links Our side supports standard markdown links: - - TODO Using standard [urls](https://pl.wikipedia.org/wiki/Uniform_Resource_Locator) - - TODO To [other (html) pages](/docs/static-page/samples/plain_html_file.html) (or [md based files](/docs/static-page/samples/plain_md_file.md)) in our documentation that using paths relative to root od documentation e.g. `/docs/static-page/sample/plain_html_file.html` for this project - - TODO To [other (html) pages](samples/plain_html_file.html) (or [md based files](samples/plain_md_file.md)) in our documentation that using paths relative to this file e.g. using `samples/plain_html_file.html` - - TODO To API using `[fully.quallifty.Name]` + - Using standard [urls](https://pl.wikipedia.org/wiki/Uniform_Resource_Locator) + - To [other (html) pages](/docs/static-page/samples/plain_html_file.html) (or [md based files](/docs/static-page/samples/plain_md_file.md)) in our documentation that using paths relative to root od documentation e.g. `/docs/static-page/sample/plain_html_file.html` for this project + - To [other (html) pages](samples/plain_html_file.html) (or [md based files](samples/plain_md_file.md)) in our documentation that using paths relative to this file e.g. using `samples/plain_html_file.html` + - To API (using `[fully.quallifty.Name]`) + - TODO [package](com.virtuslab.dokka.site) + - TODO [top level fun](com.virtuslab.dokka.site::loadTemplateFile) + - TODO [class](com.virtuslab.dokka.site#ExtendableMarkdownParser) + - TODO [member fun](com.virtuslab.dokka.site#TemplateFile::resolveMarkdown) + - TODO [member fun (no params)](com.virtuslab.dokka.site#ExtendableMarkdownParser::parse) diff --git a/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt b/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt index 1fcdc92..d7763e1 100644 --- a/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt +++ b/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt @@ -2,6 +2,7 @@ package com.virtuslab.dokka.site import org.jetbrains.dokka.base.parsers.MarkdownParser import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter +import org.jetbrains.dokka.links.Callable import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.doc.DocTag import org.jetbrains.dokka.model.doc.Text @@ -66,17 +67,8 @@ class StaticSiteContext(val root: File, cxt: DokkaContext) { private fun parseMarkdown(page: PreResolvedPage, dri: DRI, allDRIs: Map): ContentNode { val nodes = if (page.hasMarkdown) { - val parser = MarkdownParser { link -> - val driKey = if (link.startsWith("/")) { - // handle root related links - link.replace('/', '.').removePrefix(".") - } else { - val unSuffixedDri = dri.packageName!!.removeSuffix(".html").removeSuffix(".md") - val parentDri = unSuffixedDri.take(unSuffixedDri.indexOfLast('.'::equals)).removePrefix("_.") - "${parentDri}.${link.replace('/', '.')}" - } - allDRIs[driKey] - } + val externalDri = getExternalDriResolver(dri, allDRIs) + val parser = MarkdownParser(externalDri) val docTag = try { parser.parseStringToDocNode(page.code) @@ -143,5 +135,44 @@ class StaticSiteContext(val root: File, cxt: DokkaContext) { } return all.map { templateToPage(it) } } + + private fun getExternalDriResolver(dri: DRI, allDRIs: Map): (String) -> DRI? = { + if (it.endsWith(".html") || it.endsWith(".md")) { + it.resolveLinkToFile(dri, allDRIs) + + } else { + it.resolveLinkToApi() + } + } + + private fun String.resolveLinkToFile(dri: DRI, allDRIs: Map) = + if (startsWith("/")) { // handle root related links + replace('/', '.').removePrefix(".") + + } else { // handle relative links + val unSuffixedDri = dri.packageName!!.removeSuffix(".html").removeSuffix(".md") + val parentDri = unSuffixedDri.take(unSuffixedDri.indexOfLast('.'::equals)).removePrefix("_.") + "${parentDri}.${replace('/', '.')}" + + }.let { allDRIs[it] } + + private fun String.resolveLinkToApi(): DRI = + if ('#' in this) { // member fun + val (packageName, classNameAndRest) = split('#') + if ("::" in classNameAndRest) { + val (classNames, callableName) = classNameAndRest.split("::") + val callable = Callable(name = callableName, params = emptyList()) + DRI(packageName = packageName, classNames = classNames, callable = callable) + + } else { + DRI(packageName = packageName, classNames = classNameAndRest) + } + + } else if ("::" in this) { // top level fun + val (packageName, callableName) = split("::") + val callable = Callable(name = callableName, params = emptyList()) + DRI(packageName = packageName, callable = callable) + + } else DRI(packageName = this) // package } From dbd758145cc2372ab6ced1abb16eb5e1382a7426 Mon Sep 17 00:00:00 2001 From: mkondratek Date: Tue, 27 Oct 2020 18:46:26 +0100 Subject: [PATCH 3/5] Properly resolve api links --- documentation/docs/static-page/links.md | 13 +++-- .../virtuslab/dokka/site/StaticSiteContext.kt | 56 ++++++++++++------- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/documentation/docs/static-page/links.md b/documentation/docs/static-page/links.md index 3413b1c..61934aa 100644 --- a/documentation/docs/static-page/links.md +++ b/documentation/docs/static-page/links.md @@ -10,8 +10,11 @@ Our side supports standard markdown links: - To [other (html) pages](/docs/static-page/samples/plain_html_file.html) (or [md based files](/docs/static-page/samples/plain_md_file.md)) in our documentation that using paths relative to root od documentation e.g. `/docs/static-page/sample/plain_html_file.html` for this project - To [other (html) pages](samples/plain_html_file.html) (or [md based files](samples/plain_md_file.md)) in our documentation that using paths relative to this file e.g. using `samples/plain_html_file.html` - To API (using `[fully.quallifty.Name]`) - - TODO [package](com.virtuslab.dokka.site) - - TODO [top level fun](com.virtuslab.dokka.site::loadTemplateFile) - - TODO [class](com.virtuslab.dokka.site#ExtendableMarkdownParser) - - TODO [member fun](com.virtuslab.dokka.site#TemplateFile::resolveMarkdown) - - TODO [member fun (no params)](com.virtuslab.dokka.site#ExtendableMarkdownParser::parse) + - [package](com.virtuslab.dokka.site) + - [class](com.virtuslab.dokka.site#StaticSiteContext) + - [val](com.virtuslab.dokka.site::apiPageDRI) + - [prop](com.virtuslab.dokka.site#AContentPage::content) + - TODO [top level fun](com.virtuslab.dokka.site::loadTemplateFile(java.io.File)) + - TODO [member fun](com.virtuslab.dokka.site#TemplateFile::resolveMarkdown(com.virtuslab.dokka.site.RenderingContext)) + - TODO [member fun (no params)](com.virtuslab.dokka.site#StaticSiteContext::resolveLinkToApi) + diff --git a/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt b/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt index d7763e1..f6dabd0 100644 --- a/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt +++ b/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt @@ -4,6 +4,7 @@ import org.jetbrains.dokka.base.parsers.MarkdownParser import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter import org.jetbrains.dokka.links.Callable import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.JavaClassReference import org.jetbrains.dokka.model.doc.DocTag import org.jetbrains.dokka.model.doc.Text import org.jetbrains.dokka.model.properties.PropertyContainer @@ -123,7 +124,7 @@ class StaticSiteContext(val root: File, cxt: DokkaContext) { PreResolvedPage("", null, true) } val content = parseMarkdown(page, dri, driMap) - val children = myTemplate.children.map { templateToPage(it) } + val children = myTemplate.children.map(::templateToPage) return BaseStaticSiteProcessor.StaticPageNode( myTemplate.templateFile.title(), children + customChildren, @@ -133,15 +134,14 @@ class StaticSiteContext(val root: File, cxt: DokkaContext) { content ) } - return all.map { templateToPage(it) } + return all.map(::templateToPage) } private fun getExternalDriResolver(dri: DRI, allDRIs: Map): (String) -> DRI? = { if (it.endsWith(".html") || it.endsWith(".md")) { it.resolveLinkToFile(dri, allDRIs) - } else { - it.resolveLinkToApi() + it.replace("\\s".toRegex(), "").resolveLinkToApi() } } @@ -154,25 +154,39 @@ class StaticSiteContext(val root: File, cxt: DokkaContext) { val parentDri = unSuffixedDri.take(unSuffixedDri.indexOfLast('.'::equals)).removePrefix("_.") "${parentDri}.${replace('/', '.')}" - }.let { allDRIs[it] } + }.let(allDRIs::get) - private fun String.resolveLinkToApi(): DRI = - if ('#' in this) { // member fun + private fun String.resolveLinkToApi() = when { + '#' in this -> { val (packageName, classNameAndRest) = split('#') - if ("::" in classNameAndRest) { - val (classNames, callableName) = classNameAndRest.split("::") - val callable = Callable(name = callableName, params = emptyList()) - DRI(packageName = packageName, classNames = classNames, callable = callable) - - } else { - DRI(packageName = packageName, classNames = classNameAndRest) + when { + "::" in classNameAndRest -> { + val (className, callableAndParams) = classNameAndRest.split("::") + makeDRI(callableAndParams, packageName, className) + } + else -> DRI(packageName = packageName, classNames = classNameAndRest) } + } + "::" in this -> { + val (packageName, callableAndParams) = split("::") + makeDRI(callableAndParams, packageName) + } + else -> DRI(packageName = this) + } - } else if ("::" in this) { // top level fun - val (packageName, callableName) = split("::") - val callable = Callable(name = callableName, params = emptyList()) - DRI(packageName = packageName, callable = callable) - - } else DRI(packageName = this) // package + private fun makeDRI( + callableAndParams: String, + packageName: String, + className: String? = null + ): DRI { + val callableName = callableAndParams.takeWhile { it != '(' } + val params = callableAndParams.dropWhile { it != '(' } + .removePrefix("(") + .removeSuffix(")") + .split(',') + .filter(String::isNotBlank) + .map(::JavaClassReference) + val callable = Callable(name = callableName, params = params) + return DRI(packageName = packageName, classNames = className, callable = callable) + } } - From 35019db91f179b5da64517a98779b4bbddd0c686 Mon Sep 17 00:00:00 2001 From: mkondratek Date: Wed, 28 Oct 2020 12:44:03 +0100 Subject: [PATCH 4/5] Try to create url before dri resolution --- .../com/virtuslab/dokka/site/StaticSiteContext.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt b/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt index f6dabd0..925d208 100644 --- a/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt +++ b/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt @@ -15,6 +15,8 @@ import org.jetbrains.dokka.pages.DCI import org.jetbrains.dokka.pages.PageNode import org.jetbrains.dokka.plugability.DokkaContext import java.io.File +import java.net.MalformedURLException +import java.net.URL class StaticSiteContext(val root: File, cxt: DokkaContext) { val docsFile = File(root, "docs") @@ -138,10 +140,15 @@ class StaticSiteContext(val root: File, cxt: DokkaContext) { } private fun getExternalDriResolver(dri: DRI, allDRIs: Map): (String) -> DRI? = { - if (it.endsWith(".html") || it.endsWith(".md")) { - it.resolveLinkToFile(dri, allDRIs) - } else { - it.replace("\\s".toRegex(), "").resolveLinkToApi() + try { + URL(it) + null + } catch (e: MalformedURLException) { + if (it.endsWith(".html") || it.endsWith(".md")) { + it.resolveLinkToFile(dri, allDRIs) + } else { + it.replace("\\s".toRegex(), "").resolveLinkToApi() + } } } From d6e65ec3a5c4ce188f5c8794ba20569035c884e0 Mon Sep 17 00:00:00 2001 From: mkondratek Date: Wed, 28 Oct 2020 13:13:08 +0100 Subject: [PATCH 5/5] Register pages as both md and html --- .../virtuslab/dokka/site/StaticSiteContext.kt | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt b/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt index 925d208..9299fe5 100644 --- a/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt +++ b/src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt @@ -17,6 +17,7 @@ import org.jetbrains.dokka.plugability.DokkaContext import java.io.File import java.net.MalformedURLException import java.net.URL +import java.nio.file.Path class StaticSiteContext(val root: File, cxt: DokkaContext) { val docsFile = File(root, "docs") @@ -68,9 +69,14 @@ class StaticSiteContext(val root: File, cxt: DokkaContext) { null } - private fun parseMarkdown(page: PreResolvedPage, dri: DRI, allDRIs: Map): ContentNode { + private fun parseMarkdown( + page: PreResolvedPage, + dri: DRI, + allDRIs: Map, + proceededFilePath: String + ): ContentNode { val nodes = if (page.hasMarkdown) { - val externalDri = getExternalDriResolver(dri, allDRIs) + val externalDri = getExternalDriResolver(allDRIs, proceededFilePath) val parser = MarkdownParser(externalDri) val docTag = try { @@ -111,7 +117,10 @@ class StaticSiteContext(val root: File, cxt: DokkaContext) { fun pathToDri(path: String) = DRI("_.$path") - val driMap = all.flatMap { flatten(it) }.map { it to pathToDri(it) }.toMap() + val driMap = all.flatMap(::flatten) + .flatMap(::createBothMdAndHtmlKeys) + .map { Path.of(it).normalize().run { this to pathToDri(this.toString()) } } + .toMap() fun templateToPage(myTemplate: BaseStaticSiteProcessor.LoadedTemplate): BaseStaticSiteProcessor.StaticPageNode { val dri = pathToDri(myTemplate.relativePath(root)) @@ -125,7 +134,12 @@ class StaticSiteContext(val root: File, cxt: DokkaContext) { println("ERROR: $msg") // TODO (#14): provide proper error handling PreResolvedPage("", null, true) } - val content = parseMarkdown(page, dri, driMap) + + val proceededFilePath = myTemplate.file.path + .removePrefix("documentation") + .dropLastWhile { x -> x != File.separatorChar } + .removeSuffix(File.separator) + val content = parseMarkdown(page, dri, driMap, proceededFilePath) val children = myTemplate.children.map(::templateToPage) return BaseStaticSiteProcessor.StaticPageNode( myTemplate.templateFile.title(), @@ -139,49 +153,51 @@ class StaticSiteContext(val root: File, cxt: DokkaContext) { return all.map(::templateToPage) } - private fun getExternalDriResolver(dri: DRI, allDRIs: Map): (String) -> DRI? = { + private fun createBothMdAndHtmlKeys(x: String) = + listOf( + if (x.endsWith(".md")) x.removeSuffix(".md").plus(".html") else x, + if (x.endsWith(".html")) x.removeSuffix(".html").plus(".md") else x + ) + + private fun getExternalDriResolver(allDRIs: Map, proceededFilePath: String): (String) -> DRI? = { it -> try { URL(it) null } catch (e: MalformedURLException) { - if (it.endsWith(".html") || it.endsWith(".md")) { - it.resolveLinkToFile(dri, allDRIs) - } else { - it.replace("\\s".toRegex(), "").resolveLinkToApi() + val resolvePath = { x: String -> + if (it.startsWith('/')) docsFile.resolve(x) + else File(proceededFilePath).resolve(x) } + val path = Path.of(it).normalize().toString() + .let { s -> resolvePath(s) } + .toString() + .removePrefix(File.separator) + .replace(File.separatorChar, '.') + .let(Path::of) + + allDRIs[path] ?: it.replace("\\s".toRegex(), "").resolveLinkToApi() } } - private fun String.resolveLinkToFile(dri: DRI, allDRIs: Map) = - if (startsWith("/")) { // handle root related links - replace('/', '.').removePrefix(".") - - } else { // handle relative links - val unSuffixedDri = dri.packageName!!.removeSuffix(".html").removeSuffix(".md") - val parentDri = unSuffixedDri.take(unSuffixedDri.indexOfLast('.'::equals)).removePrefix("_.") - "${parentDri}.${replace('/', '.')}" - - }.let(allDRIs::get) - private fun String.resolveLinkToApi() = when { '#' in this -> { val (packageName, classNameAndRest) = split('#') when { "::" in classNameAndRest -> { val (className, callableAndParams) = classNameAndRest.split("::") - makeDRI(callableAndParams, packageName, className) + makeApiDRI(callableAndParams, packageName, className) } else -> DRI(packageName = packageName, classNames = classNameAndRest) } } "::" in this -> { val (packageName, callableAndParams) = split("::") - makeDRI(callableAndParams, packageName) + makeApiDRI(callableAndParams, packageName) } else -> DRI(packageName = this) } - private fun makeDRI( + private fun makeApiDRI( callableAndParams: String, packageName: String, className: String? = null