From c203be9fb65ee221875b4e1c865bcd289a85e69c Mon Sep 17 00:00:00 2001 From: Błażej Kardyś Date: Fri, 13 Nov 2020 22:05:15 +0100 Subject: Adding multimodule all modules page creation for gfm --- .../kotlin/org/jetbrains/dokka/gfm/GfmPlugin.kt | 36 +++ .../dokka/gfm/location/MarkdownLocationProvider.kt | 16 + .../dokka/gfm/renderer/CommonmarkRenderer.kt | 322 +++++++++++++++++++++ 3 files changed, 374 insertions(+) create mode 100644 plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/GfmPlugin.kt create mode 100644 plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/location/MarkdownLocationProvider.kt create mode 100644 plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/renderer/CommonmarkRenderer.kt (limited to 'plugins/gfm/src/main/kotlin/org') diff --git a/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/GfmPlugin.kt b/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/GfmPlugin.kt new file mode 100644 index 00000000..3f2eae4d --- /dev/null +++ b/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/GfmPlugin.kt @@ -0,0 +1,36 @@ +package org.jetbrains.dokka.gfm + +import org.jetbrains.dokka.CoreExtensions +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.renderers.PackageListCreator +import org.jetbrains.dokka.base.renderers.RootCreator +import org.jetbrains.dokka.base.resolvers.shared.RecognizedLinkFormat +import org.jetbrains.dokka.gfm.location.MarkdownLocationProviderFactory +import org.jetbrains.dokka.gfm.renderer.CommonmarkRenderer +import org.jetbrains.dokka.plugability.DokkaPlugin +import org.jetbrains.dokka.transformers.pages.PageTransformer + +class GfmPlugin : DokkaPlugin() { + + val gfmPreprocessors by extensionPoint() + + private val dokkaBase by lazy { plugin() } + + val renderer by extending { + CoreExtensions.renderer providing ::CommonmarkRenderer override dokkaBase.htmlRenderer + } + + val locationProvider by extending { + dokkaBase.locationProviderFactory providing ::MarkdownLocationProviderFactory override dokkaBase.locationProvider + } + + val rootCreator by extending { + gfmPreprocessors with RootCreator + } + + val packageListCreator by extending { + (gfmPreprocessors + providing { PackageListCreator(it, RecognizedLinkFormat.DokkaGFM) } + order { after(rootCreator) }) + } +} diff --git a/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/location/MarkdownLocationProvider.kt b/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/location/MarkdownLocationProvider.kt new file mode 100644 index 00000000..6f96dbd5 --- /dev/null +++ b/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/location/MarkdownLocationProvider.kt @@ -0,0 +1,16 @@ +package org.jetbrains.dokka.gfm.location + +import org.jetbrains.dokka.base.resolvers.local.DokkaLocationProvider +import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory +import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.plugability.DokkaContext + +class MarkdownLocationProviderFactory(val context: DokkaContext) : LocationProviderFactory { + override fun getLocationProvider(pageNode: RootPageNode) = MarkdownLocationProvider(pageNode, context) +} + +class MarkdownLocationProvider( + pageGraphRoot: RootPageNode, + dokkaContext: DokkaContext +) : DokkaLocationProvider(pageGraphRoot, dokkaContext, ".md") + diff --git a/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/renderer/CommonmarkRenderer.kt b/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/renderer/CommonmarkRenderer.kt new file mode 100644 index 00000000..0655ce20 --- /dev/null +++ b/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/renderer/CommonmarkRenderer.kt @@ -0,0 +1,322 @@ +package org.jetbrains.dokka.gfm.renderer + +import org.jetbrains.dokka.DokkaException +import org.jetbrains.dokka.base.renderers.DefaultRenderer +import org.jetbrains.dokka.base.renderers.isImage +import org.jetbrains.dokka.gfm.GfmPlugin +import org.jetbrains.dokka.model.DisplaySourceSet +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.query + +open class CommonmarkRenderer( + context: DokkaContext +) : DefaultRenderer(context) { + + override val preprocessors = context.plugin().query { gfmPreprocessors } + + override fun StringBuilder.wrapGroup( + node: ContentGroup, + pageContext: ContentPage, + childrenCallback: StringBuilder.() -> Unit + ) { + return when { + node.hasStyle(TextStyle.Block) -> { + childrenCallback() + buildNewLine() + } + node.hasStyle(TextStyle.Paragraph) -> { + buildParagraph() + childrenCallback() + buildParagraph() + } + else -> childrenCallback() + } + } + + override fun StringBuilder.buildHeader(level: Int, node: ContentHeader, 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, + sourceSetRestriction: Set? + ) { + buildListLevel(node, pageContext) + } + + private fun StringBuilder.buildListItem(items: List, pageContext: ContentPage) { + items.forEach { + if (it is ContentList) { + buildList(it, pageContext) + } else { + append("
  • ") + append(buildString { it.build(this, pageContext, it.sourceSets) }.trim()) + append("
  • ") + } + } + } + + private fun StringBuilder.buildListLevel(node: ContentList, pageContext: ContentPage) { + if (node.ordered) { + append("
      ") + buildListItem(node.children, pageContext) + append("
    ") + } else { + append("
      ") + buildListItem(node.children, pageContext) + append("
    ") + } + } + + override fun StringBuilder.buildNewLine() { + append(" \n") + } + + private fun StringBuilder.buildParagraph() { + append("\n\n") + } + + override fun StringBuilder.buildPlatformDependent( + content: PlatformHintedContent, + pageContext: ContentPage, + sourceSetRestriction: Set? + ) { + buildPlatformDependentItem(content.inner, content.sourceSets, pageContext) + } + + private fun StringBuilder.buildPlatformDependentItem( + content: ContentNode, + sourceSets: Set, + pageContext: ContentPage, + ) { + if (content is ContentGroup && content.children.firstOrNull { it is ContentTable } != null) { + buildContentNode(content, pageContext, sourceSets) + } else { + val distinct = sourceSets.map { + it to buildString { buildContentNode(content, pageContext, setOf(it)) } + }.groupBy(Pair::second, Pair::first) + + distinct.filter { it.key.isNotBlank() }.forEach { (text, platforms) -> + append(" ") + buildSourceSetTags(platforms.toSet()) + append(" $text ") + buildNewLine() + } + } + } + + override fun StringBuilder.buildResource(node: ContentEmbeddedResource, pageContext: ContentPage) { + if(node.isImage()){ + append("!") + } + append("[${node.altText}](${node.address})") + } + + override fun StringBuilder.buildTable( + node: ContentTable, + pageContext: ContentPage, + sourceSetRestriction: Set? + ) { + buildNewLine() + if (node.dci.kind == ContentKind.Sample || node.dci.kind == ContentKind.Parameters) { + node.sourceSets.forEach { sourcesetData -> + append(sourcesetData.name) + buildNewLine() + buildTable( + node.copy( + children = node.children.filter { it.sourceSets.contains(sourcesetData) }, + dci = node.dci.copy(kind = ContentKind.Main) + ), pageContext, sourceSetRestriction + ) + buildNewLine() + } + } else { + val size = node.header.size + + if (node.header.isNotEmpty()) { + append("| ") + node.header.forEach { + it.children.forEach { + append(" ") + it.build(this, pageContext, it.sourceSets) + } + append("| ") + } + append("\n") + } else { + append("| ".repeat(size)) + if (size > 0) append("|\n") + } + + append("|---".repeat(size)) + if (size > 0) append("|\n") + + node.children.forEach { + val builder = StringBuilder() + it.children.forEach { + builder.append("| ") + builder.append("") + builder.append( + buildString { it.build(this, pageContext) }.replace( + Regex("#+ "), + "" + ) + ) // Workaround for headers inside tables + } + append(builder.toString().withEntersAsHtml()) + append(" | ".repeat(size - it.children.size)) + append("\n") + } + } + } + + override fun StringBuilder.buildText(textNode: ContentText) { + if (textNode.text.isNotBlank()) { + val decorators = decorators(textNode.style) + append(textNode.text.takeWhile { it == ' ' }) + append(decorators) + append(textNode.text.trim()) + append(decorators.reversed()) + append(textNode.text.takeLastWhile { it == ' ' }) + } + } + + 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 = + buildString { + content(this, page) + } + + override fun buildError(node: ContentNode) { + context.logger.warn("Markdown renderer has encountered problem. The unmatched node is $node") + } + + override fun StringBuilder.buildDivergent(node: ContentDivergentGroup, pageContext: ContentPage) { + + val distinct = + node.groupDivergentInstances(pageContext, { instance, contentPage, sourceSet -> + instance.before?.let { before -> + buildString { buildContentNode(before, pageContext, sourceSet) } + } ?: "" + }, { instance, contentPage, sourceSet -> + instance.after?.let { after -> + buildString { buildContentNode(after, pageContext, sourceSet) } + } ?: "" + }) + + distinct.values.forEach { entry -> + val (instance, sourceSets) = entry.getInstanceAndSourceSets() + + buildSourceSetTags(sourceSets) + buildNewLine() + instance.before?.let { + append("Brief description") + buildNewLine() + buildContentNode( + it, + pageContext, + sourceSets.first() + ) // It's workaround to render content only once + buildNewLine() + } + + append("Content") + buildNewLine() + entry.groupBy { buildString { buildContentNode(it.first.divergent, pageContext, setOf(it.second)) } } + .values.forEach { innerEntry -> + val (innerInstance, innerSourceSets) = innerEntry.getInstanceAndSourceSets() + if (sourceSets.size > 1) { + buildSourceSetTags(innerSourceSets) + buildNewLine() + } + innerInstance.divergent.build( + this@buildDivergent, + pageContext, + setOf(innerSourceSets.first()) + ) // It's workaround to render content only once + buildNewLine() + } + + instance.after?.let { + append("More info") + buildNewLine() + buildContentNode( + it, + pageContext, + sourceSets.first() + ) // It's workaround to render content only once + buildNewLine() + } + + buildParagraph() + } + } + + private fun decorators(styles: Set