diff options
author | Paweł Marks <pmarks@virtuslab.com> | 2020-07-17 16:36:09 +0200 |
---|---|---|
committer | Paweł Marks <pmarks@virtuslab.com> | 2020-07-17 16:36:09 +0200 |
commit | 6996b1135f61c7d2cb60b0652c6a2691dda31990 (patch) | |
tree | d568096c25e31c28d14d518a63458b5a7526b896 /plugins/base/src/main/kotlin/translators/documentables | |
parent | de56cab76f556e5b4af0b8c8cb08d8b482b86d0a (diff) | |
parent | 1c3530dcbb50c347f80bef694829dbefe89eca77 (diff) | |
download | dokka-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')
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 |