aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src/main/kotlin/translators/documentables
diff options
context:
space:
mode:
authorPaweł Marks <pmarks@virtuslab.com>2020-07-17 16:36:09 +0200
committerPaweł Marks <pmarks@virtuslab.com>2020-07-17 16:36:09 +0200
commit6996b1135f61c7d2cb60b0652c6a2691dda31990 (patch)
treed568096c25e31c28d14d518a63458b5a7526b896 /plugins/base/src/main/kotlin/translators/documentables
parentde56cab76f556e5b4af0b8c8cb08d8b482b86d0a (diff)
parent1c3530dcbb50c347f80bef694829dbefe89eca77 (diff)
downloaddokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.tar.gz
dokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.tar.bz2
dokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.zip
Merge branch 'dev-0.11.0'
Diffstat (limited to 'plugins/base/src/main/kotlin/translators/documentables')
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/DefaultDocumentableToPageTranslator.kt17
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt525
-rw-r--r--plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt474
3 files changed, 1016 insertions, 0 deletions
diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultDocumentableToPageTranslator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultDocumentableToPageTranslator.kt
new file mode 100644
index 00000000..04251947
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultDocumentableToPageTranslator.kt
@@ -0,0 +1,17 @@
+package org.jetbrains.dokka.base.translators.documentables
+
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.pages.ModulePageNode
+import org.jetbrains.dokka.transformers.documentation.DocumentableToPageTranslator
+import org.jetbrains.dokka.utilities.DokkaLogger
+
+class DefaultDocumentableToPageTranslator(
+ private val commentsToContentConverter: CommentsToContentConverter,
+ private val signatureProvider: SignatureProvider,
+ private val logger: DokkaLogger
+) : DocumentableToPageTranslator {
+ override fun invoke(module: DModule): ModulePageNode =
+ DefaultPageCreator(commentsToContentConverter, signatureProvider, logger).pageForModule(module)
+} \ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
new file mode 100644
index 00000000..02f4b54e
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
@@ -0,0 +1,525 @@
+package org.jetbrains.dokka.base.translators.documentables
+
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.documentables.CallableExtensions
+import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo
+import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
+import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder.DocumentableContentBuilder
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.model.properties.WithExtraProperties
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.utils.addToStdlib.safeAs
+import kotlin.reflect.KClass
+import kotlin.reflect.full.isSubclassOf
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+
+private typealias GroupedTags = Map<KClass<out TagWrapper>, List<Pair<DokkaSourceSet?, TagWrapper>>>
+
+private val specialTags: Set<KClass<out TagWrapper>> =
+ setOf(Property::class, Description::class, Constructor::class, Receiver::class, Param::class, See::class)
+
+open class DefaultPageCreator(
+ commentsToContentConverter: CommentsToContentConverter,
+ signatureProvider: SignatureProvider,
+ val logger: DokkaLogger
+) {
+ protected open val contentBuilder = PageContentBuilder(commentsToContentConverter, signatureProvider, logger)
+
+ open fun pageForModule(m: DModule) =
+ ModulePageNode(m.name.ifEmpty { "<root>" }, contentForModule(m), m, m.packages.map(::pageForPackage))
+
+ open fun pageForPackage(p: DPackage): PackagePageNode = PackagePageNode(
+ p.name, contentForPackage(p), setOf(p.dri), p,
+ p.classlikes.map(::pageForClasslike) +
+ p.functions.map(::pageForFunction)
+ )
+
+ open fun pageForEnumEntry(e: DEnumEntry): ClasslikePageNode =
+ ClasslikePageNode(
+ e.name, contentForEnumEntry(e), setOf(e.dri), e,
+ e.classlikes.map(::pageForClasslike) +
+ e.filteredFunctions.map(::pageForFunction)
+ )
+
+ open fun pageForClasslike(c: DClasslike): ClasslikePageNode {
+ val constructors = if (c is WithConstructors) c.constructors else emptyList()
+
+ return ClasslikePageNode(
+ c.name.orEmpty(), contentForClasslike(c), setOf(c.dri), c,
+ constructors.map(::pageForFunction) +
+ c.classlikes.map(::pageForClasslike) +
+ c.filteredFunctions.map(::pageForFunction) +
+ if (c is DEnum) c.entries.map(::pageForEnumEntry) else emptyList()
+ )
+ }
+
+ open fun pageForFunction(f: DFunction) = MemberPageNode(f.name, contentForFunction(f), setOf(f.dri), f)
+
+ open fun pageForTypeAlias(t: DTypeAlias) = MemberPageNode(t.name, contentForTypeAlias(t), setOf(t.dri), t)
+
+ private val WithScope.filteredFunctions: List<DFunction>
+ get() = functions.mapNotNull { function ->
+ function.takeIf {
+ it.sourceSets.any { sourceSet -> it.extra[InheritedFunction]?.isInherited(sourceSet) != true }
+ }
+ }
+
+ protected open fun contentForModule(m: DModule) = contentBuilder.contentFor(m) {
+ group(kind = ContentKind.Cover) {
+ cover(m.name)
+ if (contentForDescription(m).isNotEmpty()) {
+ sourceSetDependentHint(
+ m.dri,
+ m.sourceSets.toSet(),
+ kind = ContentKind.SourceSetDependentHint,
+ styles = setOf(TextStyle.UnderCoverText)
+ ) {
+ +contentForDescription(m)
+ }
+ }
+ }
+ +contentForComments(m)
+ block("Packages", 2, ContentKind.Packages, m.packages, m.sourceSets.toSet()) {
+ link(it.name, it.dri)
+ }
+// text("Index\n") TODO
+// text("Link to allpage here")
+ }
+
+ protected open fun contentForPackage(p: DPackage) = contentBuilder.contentFor(p) {
+ group(kind = ContentKind.Cover) {
+ cover("Package ${p.name}")
+ if (contentForDescription(p).isNotEmpty()) {
+ sourceSetDependentHint(
+ p.dri,
+ p.sourceSets.toSet(),
+ kind = ContentKind.SourceSetDependentHint,
+ styles = setOf(TextStyle.UnderCoverText)
+ ) {
+ +contentForDescription(p)
+ }
+ }
+ }
+ group(styles = setOf(ContentStyle.TabbedContent)) {
+ +contentForComments(p)
+ +contentForScope(p, p.dri, p.sourceSets)
+ }
+ }
+
+ protected open fun contentForScope(
+ s: WithScope,
+ dri: DRI,
+ sourceSets: Set<DokkaSourceSet>
+ ) = contentBuilder.contentFor(s as Documentable) {
+ val types = listOf(
+ s.classlikes,
+ (s as? DPackage)?.typealiases ?: emptyList()
+ ).flatten()
+ divergentBlock("Types", types, ContentKind.Classlikes, extra = mainExtra + SimpleAttr.header("Types"))
+ divergentBlock(
+ "Functions",
+ s.functions,
+ ContentKind.Functions,
+ extra = mainExtra + SimpleAttr.header("Functions")
+ )
+ block(
+ "Properties",
+ 2,
+ ContentKind.Properties,
+ s.properties,
+ sourceSets.toSet(),
+ needsAnchors = true,
+ extra = mainExtra + SimpleAttr.header("Properties")
+ ) {
+ link(it.name, it.dri, kind = ContentKind.Main)
+ sourceSetDependentHint(it.dri, it.sourceSets.toSet(), kind = ContentKind.SourceSetDependentHint) {
+ contentForBrief(it)
+ +buildSignature(it)
+ }
+ }
+ s.safeAs<WithExtraProperties<Documentable>>()?.let { it.extra[InheritorsInfo] }?.let { inheritors ->
+ val map = inheritors.value.filter { it.value.isNotEmpty() }
+ if (map.values.any()) {
+ header(2, "Inheritors") { }
+ +ContentTable(
+ listOf(contentBuilder.contentFor(mainDRI, mainSourcesetData){
+ text("Name")
+ }),
+ map.entries.flatMap { entry -> entry.value.map { Pair(entry.key, it) } }
+ .groupBy({ it.second }, { it.first }).map { (classlike, platforms) ->
+ buildGroup(setOf(dri), platforms.toSet(), ContentKind.Inheritors) {
+ link(
+ classlike.classNames?.substringBeforeLast(".") ?: classlike.toString()
+ .also { logger.warn("No class name found for DRI $classlike") }, classlike
+ )
+ }
+ },
+ DCI(setOf(dri), ContentKind.Inheritors),
+ sourceSets.toSet(),
+ style = emptySet(),
+ extra = mainExtra + SimpleAttr.header("Inheritors")
+ )
+ }
+ }
+ }
+
+ protected open fun contentForEnumEntry(e: DEnumEntry) = contentBuilder.contentFor(e) {
+ group(kind = ContentKind.Cover) {
+ cover(e.name)
+ sourceSetDependentHint(e.dri, e.sourceSets.toSet()) {
+ +contentForDescription(e)
+ +buildSignature(e)
+ }
+ }
+ group(styles = setOf(ContentStyle.TabbedContent)) {
+ +contentForComments(e)
+ +contentForScope(e, e.dri, e.sourceSets)
+ }
+ }
+
+ protected open fun contentForClasslike(c: DClasslike) = contentBuilder.contentFor(c) {
+ @Suppress("UNCHECKED_CAST")
+ val extensions = (c as WithExtraProperties<DClasslike>)
+ .extra[CallableExtensions]?.extensions
+ ?.filterIsInstance<Documentable>().orEmpty()
+ // Extensions are added to sourceSets since they can be placed outside the sourceSets from classlike
+ // Example would be an Interface in common and extension function in jvm
+ group(kind = ContentKind.Cover, sourceSets = mainSourcesetData + extensions.sourceSets) {
+ cover(c.name.orEmpty())
+ sourceSetDependentHint(c.dri, c.sourceSets) {
+ +contentForDescription(c)
+ +buildSignature(c)
+ }
+ }
+
+ group(styles = setOf(ContentStyle.TabbedContent), sourceSets = mainSourcesetData + extensions.sourceSets) {
+ +contentForComments(c)
+ if (c is WithConstructors) {
+ block(
+ "Constructors",
+ 2,
+ ContentKind.Constructors,
+ c.constructors.filter { it.extra[PrimaryConstructorExtra] == null || it.documentation.isNotEmpty() },
+ c.sourceSets,
+ extra = PropertyContainer.empty<ContentNode>() + SimpleAttr.header("Constructors")
+ ) {
+ link(it.name, it.dri, kind = ContentKind.Main)
+ sourceSetDependentHint(
+ it.dri,
+ it.sourceSets.toSet(),
+ kind = ContentKind.SourceSetDependentHint,
+ styles = emptySet()
+ ) {
+ contentForBrief(it)
+ +buildSignature(it)
+ }
+ }
+ }
+ if (c is DEnum) {
+ block(
+ "Entries",
+ 2,
+ ContentKind.Classlikes,
+ c.entries,
+ c.sourceSets.toSet(),
+ needsSorting = false,
+ extra = mainExtra + SimpleAttr.header("Entries"),
+ styles = emptySet()
+ ) {
+ link(it.name, it.dri)
+ sourceSetDependentHint(it.dri, it.sourceSets.toSet(), kind = ContentKind.SourceSetDependentHint) {
+ contentForBrief(it)
+ +buildSignature(it)
+ }
+ }
+ }
+ +contentForScope(c, c.dri, c.sourceSets)
+
+ divergentBlock("Extensions", extensions, ContentKind.Extensions, extra = mainExtra + SimpleAttr.header("Extensions"))
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private inline fun <reified T : TagWrapper> GroupedTags.withTypeUnnamed(): SourceSetDependent<T> =
+ (this[T::class] as List<Pair<DokkaSourceSet, T>>?)?.toMap().orEmpty()
+
+ @Suppress("UNCHECKED_CAST")
+ private inline fun <reified T : NamedTagWrapper> GroupedTags.withTypeNamed(): Map<String, SourceSetDependent<T>> =
+ (this[T::class] as List<Pair<DokkaSourceSet, T>>?)
+ ?.groupBy { it.second.name }
+ ?.mapValues { (_, v) -> v.toMap() }
+ ?.toSortedMap(String.CASE_INSENSITIVE_ORDER)
+ .orEmpty()
+
+ private inline fun <reified T : TagWrapper> GroupedTags.isNotEmptyForTag(): Boolean =
+ this[T::class]?.isNotEmpty() ?: false
+
+ protected open fun contentForDescription(
+ d: Documentable
+ ): List<ContentNode> {
+ val tags: GroupedTags = d.documentation.flatMap { (pd, doc) ->
+ doc.children.asSequence().map { pd to it }.toList()
+ }.groupBy { it.second::class }
+
+ val platforms = d.sourceSets.toSet()
+
+ return contentBuilder.contentFor(d, styles = setOf(TextStyle.Block)) {
+ val description = tags.withTypeUnnamed<Description>()
+ if (description.any { it.value.root.children.isNotEmpty() }) {
+ platforms.forEach { platform ->
+ description[platform]?.also {
+ group(sourceSets = setOf(platform)) {
+ comment(it.root)
+ }
+ }
+ }
+ }
+
+ val unnamedTags: List<SourceSetDependent<TagWrapper>> =
+ tags.filterNot { (k, _) -> k.isSubclassOf(NamedTagWrapper::class) || k in specialTags }
+ .map { (_, v) -> v.mapNotNull { (k, v) -> k?.let { it to v } }.toMap() }
+ if (unnamedTags.isNotEmpty()) {
+ platforms.forEach { platform ->
+ unnamedTags.forEach { pdTag ->
+ pdTag[platform]?.also { tag ->
+ group(sourceSets = setOf(platform)) {
+ header(4, tag.toHeaderString())
+ comment(tag.root)
+ }
+ }
+ }
+ }
+ }
+
+ contentForSinceKotlin(d)
+ }.children
+ }
+
+ private fun Documentable.getPossibleFallbackSourcesets(sourceSet: DokkaSourceSet) =
+ this.sourceSets.filter { it.sourceSetID in sourceSet.dependentSourceSets }
+
+ private fun <V> Map<DokkaSourceSet, V>.fallback(sourceSets: List<DokkaSourceSet>): V? =
+ sourceSets.firstOrNull { it in this.keys }.let { this[it] }
+
+ protected open fun contentForComments(
+ d: Documentable
+ ): List<ContentNode> {
+ val tags: GroupedTags = d.documentation.flatMap { (pd, doc) ->
+ doc.children.asSequence().map { pd to it }.toList()
+ }.groupBy { it.second::class }
+
+ val platforms = d.sourceSets
+
+ fun DocumentableContentBuilder.contentForParams() {
+ if (tags.isNotEmptyForTag<Param>()) {
+ header(2, "Parameters", kind = ContentKind.Parameters)
+ group(
+ extra = mainExtra + SimpleAttr.header("Parameters"),
+ styles = setOf(ContentStyle.WithExtraAttributes)
+ ) {
+ sourceSetDependentHint(sourceSets = platforms.toSet(), kind = ContentKind.SourceSetDependentHint) {
+ val receiver = tags.withTypeUnnamed<Receiver>()
+ val params = tags.withTypeNamed<Param>()
+ table(kind = ContentKind.Parameters) {
+ platforms.flatMap { platform ->
+ val possibleFallbacks = d.getPossibleFallbackSourcesets(platform)
+ val receiverRow = (receiver[platform] ?: receiver.fallback(possibleFallbacks))?.let {
+ buildGroup(sourceSets = setOf(platform), kind = ContentKind.Parameters) {
+ text("<receiver>", styles = mainStyles + ContentStyle.RowTitle)
+ comment(it.root)
+ }
+ }
+
+ val paramRows = params.mapNotNull { (_, param) ->
+ (param[platform] ?: param.fallback(possibleFallbacks))?.let {
+ buildGroup(sourceSets = setOf(platform), kind = ContentKind.Parameters) {
+ text(
+ it.name,
+ kind = ContentKind.Parameters,
+ styles = mainStyles + ContentStyle.RowTitle
+ )
+ comment(it.root)
+ }
+ }
+ }
+
+ listOfNotNull(receiverRow) + paramRows
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fun DocumentableContentBuilder.contentForSeeAlso() {
+ if (tags.isNotEmptyForTag<See>()) {
+ header(2, "See also", kind = ContentKind.Comment)
+ group(
+ extra = mainExtra + SimpleAttr.header("See also"),
+ styles = setOf(ContentStyle.WithExtraAttributes)
+ ) {
+ sourceSetDependentHint(sourceSets = platforms.toSet(), kind = ContentKind.SourceSetDependentHint) {
+ val seeAlsoTags = tags.withTypeNamed<See>()
+ table(kind = ContentKind.Sample) {
+ platforms.flatMap { platform ->
+ val possibleFallbacks = d.getPossibleFallbackSourcesets(platform)
+ seeAlsoTags.mapNotNull { (_, see) ->
+ (see[platform] ?: see.fallback(possibleFallbacks))?.let {
+ buildGroup(
+ sourceSets = setOf(platform),
+ kind = ContentKind.Comment,
+ styles = mainStyles + ContentStyle.RowTitle
+ ) {
+ if (it.address != null) link(
+ it.name,
+ it.address!!,
+ kind = ContentKind.Comment
+ )
+ else text(it.name, kind = ContentKind.Comment)
+ comment(it.root)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fun DocumentableContentBuilder.contentForSamples() {
+ val samples = tags.withTypeNamed<Sample>()
+ if (samples.isNotEmpty()) {
+ header(2, "Samples", kind = ContentKind.Sample)
+ group(
+ extra = mainExtra + SimpleAttr.header("Samples"),
+ styles = emptySet()
+ ) {
+ sourceSetDependentHint(sourceSets = platforms.toSet(), kind = ContentKind.SourceSetDependentHint) {
+ platforms.map { platformData ->
+ val content = samples.filter { it.value.isEmpty() || platformData in it.value }
+ group(
+ sourceSets = setOf(platformData),
+ kind = ContentKind.Sample,
+ styles = setOf(TextStyle.Monospace, ContentStyle.RunnableSample)
+ ) {
+ content.forEach {
+ text(it.key)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return contentBuilder.contentFor(d) {
+ if (tags.isNotEmpty()) {
+ contentForSamples()
+ contentForSeeAlso()
+ contentForParams()
+ }
+ }.children
+ }
+
+ protected open fun DocumentableContentBuilder.contentForBrief(documentable: Documentable) {
+ documentable.sourceSets.forEach { sourceSet ->
+ documentable.documentation[sourceSet]?.children?.firstOrNull()?.root?.let {
+ group(sourceSets = setOf(sourceSet), kind = ContentKind.BriefComment) {
+ comment(it)
+ }
+ }
+ }
+ }
+
+ protected open fun DocumentableContentBuilder.contentForSinceKotlin(documentable: Documentable) {
+ documentable.documentation.mapValues {
+ it.value.children.find { it is CustomTagWrapper && it.name == "Since Kotlin" } as CustomTagWrapper?
+ }.run {
+ documentable.sourceSets.forEach { sourceSet ->
+ this[sourceSet]?.also { tag ->
+ group(sourceSets = setOf(sourceSet), kind = ContentKind.Comment, styles = setOf(TextStyle.Block)) {
+ header(4, tag.name)
+ comment(tag.root)
+ }
+ }
+ }
+ }
+ }
+
+ protected open fun contentForFunction(f: DFunction) = contentForMember(f)
+ protected open fun contentForTypeAlias(t: DTypeAlias) = contentForMember(t)
+ protected open fun contentForMember(d: Documentable) = contentBuilder.contentFor(d) {
+ group(kind = ContentKind.Cover) {
+ cover(d.name.orEmpty())
+ }
+ divergentGroup(ContentDivergentGroup.GroupID("member")) {
+ instance(setOf(d.dri), d.sourceSets.toSet()) {
+ before {
+ +contentForDescription(d)
+ +contentForComments(d)
+ }
+ divergent(kind = ContentKind.Symbol) {
+ +buildSignature(d)
+ }
+ }
+ }
+ }
+
+ protected open fun DocumentableContentBuilder.divergentBlock(
+ name: String,
+ collection: Collection<Documentable>,
+ kind: ContentKind,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) {
+ if (collection.any()) {
+ header(2, name, kind = kind)
+ table(kind, extra = extra, styles = emptySet()) {
+ collection
+ .groupBy { it.name }
+ // This hacks displaying actual typealias signatures along classlike ones
+ .mapValues { if (it.value.any { it is DClasslike }) it.value.filter { it !is DTypeAlias } else it.value }
+ .toSortedMap(compareBy(nullsLast(String.CASE_INSENSITIVE_ORDER)){it})
+ .map { (elementName, elements) -> // This groupBy should probably use LocationProvider
+ buildGroup(
+ dri = elements.map { it.dri }.toSet(),
+ sourceSets = elements.flatMap { it.sourceSets }.toSet(),
+ kind = kind,
+ styles = emptySet()
+ ) {
+ link(elementName.orEmpty(), elements.first().dri, kind = kind)
+ divergentGroup(
+ ContentDivergentGroup.GroupID(name),
+ elements.map { it.dri }.toSet(),
+ kind = kind
+ ) {
+ elements.map {
+ instance(setOf(it.dri), it.sourceSets.toSet()) {
+ before {
+ contentForBrief(it)
+ contentForSinceKotlin(it)
+ }
+ divergent {
+ group {
+ +buildSignature(it)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ protected open fun TagWrapper.toHeaderString() = this.javaClass.toGenericString().split('.').last()
+
+ private val List<Documentable>.sourceSets: Set<DokkaSourceSet>
+ get() = flatMap { it.sourceSets }.toSet()
+}
diff --git a/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt
new file mode 100644
index 00000000..b7927076
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt
@@ -0,0 +1,474 @@
+package org.jetbrains.dokka.base.translators.documentables
+
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.model.SourceSetDependent
+import org.jetbrains.dokka.model.doc.DocTag
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.utilities.DokkaLogger
+
+@DslMarker
+annotation class ContentBuilderMarker
+
+open class PageContentBuilder(
+ val commentsConverter: CommentsToContentConverter,
+ val signatureProvider: SignatureProvider,
+ val logger: DokkaLogger
+) {
+ fun contentFor(
+ dri: DRI,
+ sourceSets: Set<DokkaSourceSet>,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = emptySet(),
+ extra: PropertyContainer<ContentNode> = PropertyContainer.empty(),
+ block: DocumentableContentBuilder.() -> Unit
+ ): ContentGroup =
+ DocumentableContentBuilder(setOf(dri), sourceSets, styles, extra)
+ .apply(block)
+ .build(sourceSets, kind, styles, extra)
+
+ fun contentFor(
+ dri: Set<DRI>,
+ sourceSets: Set<DokkaSourceSet>,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = emptySet(),
+ extra: PropertyContainer<ContentNode> = PropertyContainer.empty(),
+ block: DocumentableContentBuilder.() -> Unit
+ ): ContentGroup =
+ DocumentableContentBuilder(dri, sourceSets, styles, extra)
+ .apply(block)
+ .build(sourceSets, kind, styles, extra)
+
+ fun contentFor(
+ d: Documentable,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = emptySet(),
+ extra: PropertyContainer<ContentNode> = PropertyContainer.empty(),
+ sourceSets: Set<DokkaSourceSet> = d.sourceSets.toSet(),
+ block: DocumentableContentBuilder.() -> Unit = {}
+ ): ContentGroup =
+ DocumentableContentBuilder(setOf(d.dri), sourceSets, styles, extra)
+ .apply(block)
+ .build(sourceSets, kind, styles, extra)
+
+ @ContentBuilderMarker
+ open inner class DocumentableContentBuilder(
+ val mainDRI: Set<DRI>,
+ val mainSourcesetData: Set<DokkaSourceSet>,
+ val mainStyles: Set<Style>,
+ val mainExtra: PropertyContainer<ContentNode>
+ ) {
+ protected val contents = mutableListOf<ContentNode>()
+
+ fun build(
+ sourceSets: Set<DokkaSourceSet>,
+ kind: Kind,
+ styles: Set<Style>,
+ extra: PropertyContainer<ContentNode>
+ ) = ContentGroup(
+ contents.toList(),
+ DCI(mainDRI, kind),
+ sourceSets,
+ styles,
+ extra
+ )
+
+ operator fun ContentNode.unaryPlus() {
+ contents += this
+ }
+
+ operator fun Collection<ContentNode>.unaryPlus() {
+ contents += this
+ }
+
+ private val defaultHeaders
+ get() = listOf(
+ contentFor(mainDRI, mainSourcesetData){
+ text("Name")
+ },
+ contentFor(mainDRI, mainSourcesetData){
+ text("Summary")
+ }
+ )
+
+ fun header(
+ level: Int,
+ text: String,
+ kind: Kind = ContentKind.Main,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit = {}
+ ) {
+ contents += ContentHeader(
+ level,
+ contentFor(
+ mainDRI,
+ sourceSets,
+ kind,
+ styles,
+ extra + SimpleAttr("anchor", text.replace("\\s".toRegex(), "").toLowerCase())
+ ) {
+ text(text, kind = kind)
+ block()
+ }
+ )
+ }
+
+ fun cover(
+ text: String,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles + TextStyle.Cover,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit = {}
+ ) {
+ header(1, text, sourceSets = sourceSets, styles = styles, extra = extra, block = block)
+ }
+
+ fun text(
+ text: String,
+ kind: Kind = ContentKind.Main,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) {
+ contents += createText(text, kind, sourceSets, styles, extra)
+ }
+
+ fun buildSignature(d: Documentable) = signatureProvider.signature(d)
+
+ fun table(
+ kind: Kind = ContentKind.Main,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ operation: DocumentableContentBuilder.() -> List<ContentGroup>
+ ) {
+ contents += ContentTable(
+ defaultHeaders,
+ operation(),
+ DCI(mainDRI, kind),
+ sourceSets, styles, extra
+ )
+ }
+
+ fun <T : Documentable> block(
+ name: String,
+ level: Int,
+ kind: Kind = ContentKind.Main,
+ elements: Iterable<T>,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ renderWhenEmpty: Boolean = false,
+ needsSorting: Boolean = true,
+ headers: List<ContentGroup>? = null,
+ needsAnchors: Boolean = false,
+ operation: DocumentableContentBuilder.(T) -> Unit
+ ) {
+ if (renderWhenEmpty || elements.any()) {
+ header(level, name, kind = kind) { }
+ contents += ContentTable(
+ headers ?: defaultHeaders,
+ elements
+ .let {
+ if (needsSorting)
+ it.sortedWith(compareBy(nullsLast(String.CASE_INSENSITIVE_ORDER)) { it.name })
+ else it
+ }
+ .map {
+ val newExtra = if (needsAnchors) extra + SymbolAnchorHint else extra
+ buildGroup(setOf(it.dri), it.sourceSets.toSet(), kind, styles, newExtra) {
+ operation(it)
+ }
+ },
+ DCI(mainDRI, kind),
+ sourceSets, styles, extra
+ )
+ }
+ }
+
+ fun <T> list(
+ elements: List<T>,
+ prefix: String = "",
+ suffix: String = "",
+ separator: String = ", ",
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData, // TODO: children should be aware of this platform data
+ operation: DocumentableContentBuilder.(T) -> Unit
+ ) {
+ if (elements.isNotEmpty()) {
+ if (prefix.isNotEmpty()) text(prefix, sourceSets = sourceSets)
+ elements.dropLast(1).forEach {
+ operation(it)
+ text(separator, sourceSets = sourceSets)
+ }
+ operation(elements.last())
+ if (suffix.isNotEmpty()) text(suffix, sourceSets = sourceSets)
+ }
+ }
+
+ fun link(
+ text: String,
+ address: DRI,
+ kind: Kind = ContentKind.Main,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) {
+ contents += linkNode(text, address, kind, sourceSets, styles, extra)
+ }
+
+ fun linkNode(
+ text: String,
+ address: DRI,
+ kind: Kind = ContentKind.Main,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) = ContentDRILink(
+ listOf(createText(text, kind, sourceSets, styles, extra)),
+ address,
+ DCI(mainDRI, kind),
+ sourceSets
+ )
+
+ fun link(
+ text: String,
+ address: String,
+ kind: Kind = ContentKind.Main,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) {
+ contents += ContentResolvedLink(
+ children = listOf(createText(text, kind, sourceSets, styles, extra)),
+ address = address,
+ extra = PropertyContainer.empty(),
+ dci = DCI(mainDRI, kind),
+ sourceSets = sourceSets,
+ style = emptySet()
+ )
+ }
+
+ fun link(
+ address: DRI,
+ kind: Kind = ContentKind.Main,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ) {
+ contents += ContentDRILink(
+ contentFor(mainDRI, sourceSets, kind, styles, extra, block).children,
+ address,
+ DCI(mainDRI, kind),
+ sourceSets
+ )
+ }
+
+ fun comment(
+ docTag: DocTag,
+ kind: Kind = ContentKind.Comment,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) {
+ val content = commentsConverter.buildContent(
+ docTag,
+ DCI(mainDRI, kind),
+ sourceSets
+ )
+ contents += ContentGroup(content, DCI(mainDRI, kind), sourceSets, styles, extra)
+ }
+
+ fun group(
+ dri: Set<DRI> = mainDRI,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ) {
+ contents += buildGroup(dri, sourceSets, kind, styles, extra, block)
+ }
+
+ fun divergentGroup(
+ groupID: ContentDivergentGroup.GroupID,
+ dri: Set<DRI> = mainDRI,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ implicitlySourceSetHinted: Boolean = true,
+ block: DivergentBuilder.() -> Unit
+ ) {
+ contents +=
+ DivergentBuilder(dri, kind, styles, extra)
+ .apply(block)
+ .build(groupID = groupID, implicitlySourceSetHinted = implicitlySourceSetHinted)
+ }
+
+ fun buildGroup(
+ dri: Set<DRI> = mainDRI,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ): ContentGroup = contentFor(dri, sourceSets, kind, styles, extra, block)
+
+ fun sourceSetDependentHint(
+ dri: Set<DRI> = mainDRI,
+ sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ) {
+ contents += PlatformHintedContent(
+ buildGroup(dri, sourceSets, kind, styles, extra, block),
+ sourceSets
+ )
+ }
+
+ fun sourceSetDependentHint(
+ dri: DRI,
+ sourcesetData: Set<DokkaSourceSet> = mainSourcesetData,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ) {
+ contents += PlatformHintedContent(
+ buildGroup(setOf(dri), sourcesetData, kind, styles, extra, block),
+ sourcesetData
+ )
+ }
+
+ protected fun createText(
+ text: String,
+ kind: Kind,
+ sourceSets: Set<DokkaSourceSet>,
+ styles: Set<Style>,
+ extra: PropertyContainer<ContentNode>
+ ) =
+ ContentText(text, DCI(mainDRI, kind), sourceSets, styles, extra)
+
+ fun <T> sourceSetDependentText(
+ value: SourceSetDependent<T>,
+ sourceSets: Set<DokkaSourceSet> = value.keys,
+ transform: (T) -> String
+ ) = value.entries.filter { it.key in sourceSets }.mapNotNull { (p, v) ->
+ transform(v).takeIf { it.isNotBlank() }?.let { it to p }
+ }.groupBy({ it.first }) { it.second }.forEach {
+ text(it.key, sourceSets = it.value.toSet())
+ }
+ }
+
+ @ContentBuilderMarker
+ open inner class DivergentBuilder(
+ private val mainDRI: Set<DRI>,
+ private val mainKind: Kind,
+ private val mainStyles: Set<Style>,
+ private val mainExtra: PropertyContainer<ContentNode>
+ ) {
+ private val instances: MutableList<ContentDivergentInstance> = mutableListOf()
+ fun instance(
+ dri: Set<DRI>,
+ sourceSets: Set<DokkaSourceSet>, // Having correct sourcesetData is crucial here, that's why there's no default
+ kind: Kind = mainKind,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DivergentInstanceBuilder.() -> Unit
+ ) {
+ instances += DivergentInstanceBuilder(dri, sourceSets, styles, extra)
+ .apply(block)
+ .build(kind)
+ }
+
+ fun build(
+ groupID: ContentDivergentGroup.GroupID,
+ implicitlySourceSetHinted: Boolean,
+ kind: Kind = mainKind,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) = ContentDivergentGroup(
+ instances.toList(),
+ DCI(mainDRI, kind),
+ styles,
+ extra,
+ groupID,
+ implicitlySourceSetHinted
+ )
+ }
+
+ @ContentBuilderMarker
+ open inner class DivergentInstanceBuilder(
+ private val mainDRI: Set<DRI>,
+ private val mainSourceSets: Set<DokkaSourceSet>,
+ private val mainStyles: Set<Style>,
+ private val mainExtra: PropertyContainer<ContentNode>
+ ) {
+ private var before: ContentNode? = null
+ private var divergent: ContentNode? = null
+ private var after: ContentNode? = null
+
+ fun before(
+ dri: Set<DRI> = mainDRI,
+ sourceSets: Set<DokkaSourceSet> = mainSourceSets,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ) {
+ contentFor(dri, sourceSets, kind, styles, extra, block)
+ .takeIf { it.hasAnyContent() }
+ .also { before = it }
+ }
+
+ fun divergent(
+ dri: Set<DRI> = mainDRI,
+ sourceSets: Set<DokkaSourceSet> = mainSourceSets,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ) {
+ divergent = contentFor(dri, sourceSets, kind, styles, extra, block)
+ }
+
+ fun after(
+ dri: Set<DRI> = mainDRI,
+ sourceSets: Set<DokkaSourceSet> = mainSourceSets,
+ kind: Kind = ContentKind.Main,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: DocumentableContentBuilder.() -> Unit
+ ) {
+ contentFor(dri, sourceSets, kind, styles, extra, block)
+ .takeIf { it.hasAnyContent() }
+ .also { after = it }
+ }
+
+
+ fun build(
+ kind: Kind,
+ sourceSets: Set<DokkaSourceSet> = mainSourceSets,
+ styles: Set<Style> = mainStyles,
+ extra: PropertyContainer<ContentNode> = mainExtra
+ ) =
+ ContentDivergentInstance(
+ before,
+ divergent ?: throw IllegalStateException("Divergent block needs divergent part"),
+ after,
+ DCI(mainDRI, kind),
+ sourceSets,
+ styles,
+ extra
+ )
+ }
+} \ No newline at end of file