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.model.properties.plus import org.jetbrains.dokka.model.toDisplaySourceSets 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.toDisplaySourceSets(), styles, extra ) operator fun ContentNode.unaryPlus() { contents += this } operator fun Collection<ContentNode>.unaryPlus() { contents += this } 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 + SymbolAnchorHint(text.replace("\\s".toRegex(), "").toLowerCase(), kind) ) { 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 constant(text: String) = text(text, styles = mainStyles + TokenStyle.Constant) fun keyword(text: String) = text(text, styles = mainStyles + TokenStyle.Keyword) fun stringLiteral(text: String) = text(text, styles = mainStyles + TokenStyle.String) fun booleanLiteral(value: Boolean) = text(value.toString(), styles = mainStyles + TokenStyle.Boolean) fun punctuation(text: String) = text(text, styles = mainStyles + TokenStyle.Punctuation) fun operator(text: String) = text(text, styles = mainStyles + TokenStyle.Operator) 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: TableBuilder.() -> Unit = {} ) { contents += TableBuilder(mainDRI, sourceSets, kind, styles, extra).apply { operation() }.build() } fun unorderedList( kind: Kind = ContentKind.Main, sourceSets: Set<DokkaSourceSet> = mainSourcesetData, styles: Set<Style> = mainStyles, extra: PropertyContainer<ContentNode> = mainExtra, operation: ListBuilder.() -> Unit = {} ) { contents += ListBuilder(false, mainDRI, sourceSets, kind, styles, extra).apply(operation).build() } fun orderedList( kind: Kind = ContentKind.Main, sourceSets: Set<DokkaSourceSet> = mainSourcesetData, styles: Set<Style> = mainStyles, extra: PropertyContainer<ContentNode> = mainExtra, operation: ListBuilder.() -> Unit = {} ) { contents += ListBuilder(true, mainDRI, sourceSets, kind, styles, extra).apply(operation).build() } fun descriptionList( kind: Kind = ContentKind.Main, sourceSets: Set<DokkaSourceSet> = mainSourcesetData, styles: Set<Style> = mainStyles, extra: PropertyContainer<ContentNode> = mainExtra, operation: ListBuilder.() -> Unit = {} ) { contents += ListBuilder(false, mainDRI, sourceSets, kind, styles + ListStyle.DescriptionList, extra) .apply(operation) .build() } internal fun headers(vararg label: String) = contentFor(mainDRI, mainSourcesetData) { label.forEach { text(it) } } 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> = emptyList(), needsAnchors: Boolean = false, operation: DocumentableContentBuilder.(T) -> Unit ) { if (renderWhenEmpty || elements.any()) { header(level, name, kind = kind) { } contents += ContentTable( header = headers, children = elements .let { if (needsSorting) it.sortedWith(compareBy(nullsLast(String.CASE_INSENSITIVE_ORDER)) { it.name }) else it } .map { val newExtra = if (needsAnchors) extra + SymbolAnchorHint.from(it, kind) else extra buildGroup(setOf(it.dri), it.sourceSets.toSet(), kind, styles, newExtra) { operation(it) } }, dci = DCI(mainDRI, kind), sourceSets = sourceSets.toDisplaySourceSets(), style = styles, extra = extra ) } } fun <T : Pair<String, List<Documentable>>> multiBlock( name: String, level: Int, kind: Kind = ContentKind.Main, groupedElements: Iterable<T>, sourceSets: Set<DokkaSourceSet> = mainSourcesetData, styles: Set<Style> = mainStyles, extra: PropertyContainer<ContentNode> = mainExtra, renderWhenEmpty: Boolean = false, needsSorting: Boolean = true, headers: List<ContentGroup> = emptyList(), needsAnchors: Boolean = false, operation: DocumentableContentBuilder.(String, List<Documentable>) -> Unit ) { if (renderWhenEmpty || groupedElements.any()) { header(level, name, kind = kind) { } contents += ContentTable( header = headers, children = groupedElements .let { if (needsSorting) it.sortedWith(compareBy(nullsLast(String.CASE_INSENSITIVE_ORDER)) { it.first }) else it } .map { val newExtra = if (needsAnchors) extra + SymbolAnchorHint(it.first, kind) else extra val documentables = it.second buildGroup(documentables.map { it.dri }.toSet(), documentables.flatMap { it.sourceSets }.toSet(), kind, styles, newExtra) { operation(it.first, documentables) } }, dci = DCI(mainDRI, kind), sourceSets = sourceSets.toDisplaySourceSets(), style = styles, extra = 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 surroundingCharactersStyle: Set<Style> = mainStyles, separatorStyles: Set<Style> = mainStyles, operation: DocumentableContentBuilder.(T) -> Unit ) { if (elements.isNotEmpty()) { if (prefix.isNotEmpty()) text(prefix, sourceSets = sourceSets, styles = surroundingCharactersStyle) elements.dropLast(1).forEach { operation(it) text(separator, sourceSets = sourceSets, styles = separatorStyles) } operation(elements.last()) if (suffix.isNotEmpty()) text(suffix, sourceSets = sourceSets, styles = surroundingCharactersStyle) } } 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, DCI(mainDRI, kind), sourceSets, styles, extra) } fun linkNode( text: String, address: DRI, dci: DCI = DCI(mainDRI, ContentKind.Main), sourceSets: Set<DokkaSourceSet> = mainSourcesetData, styles: Set<Style> = mainStyles, extra: PropertyContainer<ContentNode> = mainExtra ) = ContentDRILink( listOf(createText(text, dci.kind, sourceSets, styles, extra)), address, dci, sourceSets.toDisplaySourceSets(), extra = extra ) 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.toDisplaySourceSets(), 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.toDisplaySourceSets(), extra = extra ) } 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.toDisplaySourceSets(), styles, extra) } fun firstParagraphComment( content: DocTag, kind: Kind = ContentKind.Comment, sourceSets: Set<DokkaSourceSet> = mainSourcesetData, styles: Set<Style> = mainStyles, extra: PropertyContainer<ContentNode> = mainExtra ) { firstParagraphBrief(content)?.let { brief -> val builtDescription = commentsConverter.buildContent( brief, DCI(mainDRI, kind), sourceSets ) contents += ContentGroup( builtDescription, DCI(mainDRI, kind), sourceSets.toDisplaySourceSets(), styles, extra ) } } fun firstSentenceComment( content: DocTag, kind: Kind = ContentKind.Comment, sourceSets: Set<DokkaSourceSet> = mainSourcesetData, styles: Set<Style> = mainStyles, extra: PropertyContainer<ContentNode> = mainExtra ){ val builtDescription = commentsConverter.buildContent( content, DCI(mainDRI, kind), sourceSets ) contents += ContentGroup( firstSentenceBriefFromContentNodes(builtDescription), DCI(mainDRI, kind), sourceSets.toDisplaySourceSets(), 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.toDisplaySourceSets() ) } 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.toDisplaySourceSets() ) } protected fun createText( text: String, kind: Kind, sourceSets: Set<DokkaSourceSet>, styles: Set<Style>, extra: PropertyContainer<ContentNode> ) = ContentText(text, DCI(mainDRI, kind), sourceSets.toDisplaySourceSets(), styles, extra) fun <T> sourceSetDependentText( value: SourceSetDependent<T>, sourceSets: Set<DokkaSourceSet> = value.keys, styles: Set<Style> = mainStyles, 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(), styles = styles) } } @ContentBuilderMarker open inner class TableBuilder( private val mainDRI: Set<DRI>, private val mainSourceSets: Set<DokkaSourceSet>, private val mainKind: Kind, private val mainStyles: Set<Style>, private val mainExtra: PropertyContainer<ContentNode> ) { private val headerRows: MutableList<ContentGroup> = mutableListOf() private val rows: MutableList<ContentGroup> = mutableListOf() private var caption: ContentGroup? = null fun header( dri: Set<DRI> = mainDRI, sourceSets: Set<DokkaSourceSet> = mainSourceSets, kind: Kind = mainKind, styles: Set<Style> = mainStyles, extra: PropertyContainer<ContentNode> = mainExtra, block: DocumentableContentBuilder.() -> Unit ) { headerRows += contentFor(dri, sourceSets, kind, styles, extra, block) } fun row( dri: Set<DRI> = mainDRI, sourceSets: Set<DokkaSourceSet> = mainSourceSets, kind: Kind = mainKind, styles: Set<Style> = mainStyles, extra: PropertyContainer<ContentNode> = mainExtra, block: DocumentableContentBuilder.() -> Unit ) { rows += contentFor(dri, sourceSets, kind, styles, extra, block) } fun caption( dri: Set<DRI> = mainDRI, sourceSets: Set<DokkaSourceSet> = mainSourceSets, kind: Kind = mainKind, styles: Set<Style> = mainStyles, extra: PropertyContainer<ContentNode> = mainExtra, block: DocumentableContentBuilder.() -> Unit ) { caption = contentFor(dri, sourceSets, kind, styles, extra, block) } fun build( sourceSets: Set<DokkaSourceSet> = mainSourceSets, kind: Kind = mainKind, styles: Set<Style> = mainStyles, extra: PropertyContainer<ContentNode> = mainExtra ) = ContentTable( headerRows, caption, rows, DCI(mainDRI, kind), sourceSets.toDisplaySourceSets(), styles, extra ) } @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.toDisplaySourceSets(), styles, extra ) } @ContentBuilderMarker open inner class ListBuilder( val ordered: Boolean, private val mainDRI: Set<DRI>, private val mainSourceSets: Set<DokkaSourceSet>, private val mainKind: Kind, private val mainStyles: Set<Style>, private val mainExtra: PropertyContainer<ContentNode> ) { private val contentNodes: MutableList<ContentNode> = mutableListOf() fun item( dri: Set<DRI> = mainDRI, sourceSets: Set<DokkaSourceSet> = mainSourceSets, kind: Kind = mainKind, styles: Set<Style> = mainStyles, extra: PropertyContainer<ContentNode> = mainExtra, block: DocumentableContentBuilder.() -> Unit ) { contentNodes += contentFor(dri, sourceSets, kind, styles, extra, block) } fun build( sourceSets: Set<DokkaSourceSet> = mainSourceSets, kind: Kind = mainKind, styles: Set<Style> = mainStyles, extra: PropertyContainer<ContentNode> = mainExtra ) = ContentList( contentNodes, ordered, DCI(mainDRI, kind), sourceSets.toDisplaySourceSets(), styles, extra ) } }