package org.jetbrains.dokka.gfm import kotlinx.coroutines.* import org.jetbrains.dokka.CoreExtensions import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.renderers.DefaultRenderer import org.jetbrains.dokka.base.renderers.PackageListCreator import org.jetbrains.dokka.base.renderers.RootCreator import org.jetbrains.dokka.base.resolvers.local.DefaultLocationProvider import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.DokkaPlugin import org.jetbrains.dokka.plugability.plugin import org.jetbrains.dokka.plugability.query import org.jetbrains.dokka.transformers.pages.PageTransformer import java.lang.StringBuilder class GfmPlugin : DokkaPlugin() { val gfmPreprocessors by extensionPoint() val renderer by extending { CoreExtensions.renderer providing { CommonmarkRenderer(it) } applyIf { format == "gfm" } } val locationProvider by extending { plugin().locationProviderFactory providing { MarkdownLocationProviderFactory(it) } applyIf { format == "gfm" } } val rootCreator by extending { gfmPreprocessors with RootCreator } val packageListCreator by extending { gfmPreprocessors providing { PackageListCreator( it, "gfm", "md" ) } order { after(rootCreator) } } } open class CommonmarkRenderer( context: DokkaContext ) : DefaultRenderer(context) { override val preprocessors = context.plugin().query { gfmPreprocessors } override fun StringBuilder.buildHeader(level: Int, content: StringBuilder.() -> Unit) { buildParagraph() append("#".repeat(level) + " ") content() buildNewLine() } override fun StringBuilder.buildLink(address: String, content: StringBuilder.() -> Unit) { append("[") content() append("]($address)") } override fun StringBuilder.buildList(node: ContentList, pageContext: ContentPage, platformRestriction: PlatformData?) { buildParagraph() buildListLevel(node, pageContext) buildParagraph() } private val indent = " ".repeat(4) private fun StringBuilder.buildListItem(items: List, pageContext: ContentPage, bullet: String = "*") { items.forEach { if (it is ContentList) { val builder = StringBuilder() builder.append(indent) builder.buildListLevel(it, pageContext) append(builder.toString().replace(Regex(" \n(?!$)"), " \n$indent")) } else { append("$bullet ") it.build(this, pageContext) buildNewLine() } } } private fun StringBuilder.buildListLevel(node: ContentList, pageContext: ContentPage) { if (node.ordered) { buildListItem( node.children, pageContext, "${node.extra.allOfType().find { it.extraKey == "start" }?.extraValue ?: 1.also { context.logger.error("No starting number specified for ordered list in node ${pageContext.dri.first()}!")}}." ) } else { buildListItem(node.children, pageContext, "*") } } override fun StringBuilder.buildNewLine() { append(" \n") } private fun StringBuilder.buildParagraph() { append("\n\n") } override fun StringBuilder.buildPlatformDependent(content: PlatformHintedContent, pageContext: ContentPage) { val distinct = content.platforms.map { it to StringBuilder().apply {buildContentNode(content.inner, pageContext, it) }.toString() }.groupBy(Pair::second, Pair::first) if (distinct.size == 1) append(distinct.keys.single()) else distinct.forEach { text, platforms -> append(platforms.joinToString(prefix = " [", postfix = "] $text") { it.name }) } } override fun StringBuilder.buildResource(node: ContentEmbeddedResource, pageContext: ContentPage) { append("Resource") } override fun StringBuilder.buildTable(node: ContentTable, pageContext: ContentPage, platformRestriction: PlatformData?) { buildParagraph() val size = node.children.firstOrNull()?.children?.size ?: 0 if (node.header.size > 0) { node.header.forEach { it.children.forEach { append("| ") it.build(this, pageContext) } append("|\n") } } else { append("| ".repeat(size)) if (size > 0) append("|\n") } append("|---".repeat(size)) if (size > 0) append("|\n") node.children.forEach { it.children.forEach { append("| ") it.build(this, pageContext) } append("|\n") } buildParagraph() } override fun StringBuilder.buildText(textNode: ContentText) { val decorators = decorators(textNode.style) append(decorators) append(textNode.text.replace(Regex("[<>]"), "")) append(decorators.reversed()) } override fun StringBuilder.buildNavigation(page: PageNode) { locationProvider.ancestors(page).asReversed().forEach { node -> append("/") if (node.isNavigable) buildLink(node, page) else append(node.name) } buildParagraph() } override fun buildPage(page: ContentPage, content: (StringBuilder, ContentPage) -> Unit): String = StringBuilder().apply { content(this, page) }.toString() override fun buildError(node: ContentNode) { context.logger.warn("Markdown renderer has encountered problem. The unmatched node is $node") } private fun decorators(styles: Set