Merge branch 'java-layout-html-format' into devsite-with-java-layout-html
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormat.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormat.kt
new file mode 100644
index 0000000..5148097
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormat.kt
@@ -0,0 +1,131 @@
+package org.jetbrains.dokka.Formats
+
+import com.google.inject.Binder
+import kotlinx.html.*
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.Utilities.bind
+import org.jetbrains.dokka.Utilities.lazyBind
+import org.jetbrains.dokka.Utilities.toOptional
+import org.jetbrains.dokka.Utilities.toType
+import java.net.URI
+import java.net.URLEncoder
+import kotlin.reflect.KClass
+
+
+abstract class JavaLayoutHtmlFormatDescriptorBase : FormatDescriptor, DefaultAnalysisComponent {
+
+ override fun configureOutput(binder: Binder): Unit = with(binder) {
+ bind<Generator>() toType generatorServiceClass
+ bind<LanguageService>() toType languageServiceClass
+ bind<JavaLayoutHtmlTemplateService>() toType templateServiceClass
+ bind<JavaLayoutHtmlUriProvider>() toType generatorServiceClass
+ lazyBind<JavaLayoutHtmlFormatOutlineFactoryService>() toOptional outlineFactoryClass
+ bind<PackageListService>() toType packageListServiceClass
+ bind<JavaLayoutHtmlFormatOutputBuilderFactory>() toType outputBuilderFactoryClass
+ }
+
+ val generatorServiceClass = JavaLayoutHtmlFormatGenerator::class
+ abstract val languageServiceClass: KClass<out LanguageService>
+ abstract val templateServiceClass: KClass<out JavaLayoutHtmlTemplateService>
+ abstract val outlineFactoryClass: KClass<out JavaLayoutHtmlFormatOutlineFactoryService>?
+ abstract val packageListServiceClass: KClass<out PackageListService>
+ abstract val outputBuilderFactoryClass: KClass<out JavaLayoutHtmlFormatOutputBuilderFactory>
+}
+
+class JavaLayoutHtmlFormatDescriptor : JavaLayoutHtmlFormatDescriptorBase(), DefaultAnalysisComponentServices by KotlinAsKotlin {
+ override val outputBuilderFactoryClass: KClass<out JavaLayoutHtmlFormatOutputBuilderFactory> = JavaLayoutHtmlFormatOutputBuilderFactoryImpl::class
+ override val packageListServiceClass: KClass<out PackageListService> = JavaLayoutHtmlPackageListService::class
+ override val languageServiceClass = KotlinLanguageService::class
+ override val templateServiceClass = JavaLayoutHtmlTemplateService.Default::class
+ override val outlineFactoryClass = null
+}
+
+interface JavaLayoutHtmlFormatOutlineFactoryService {
+ fun generateOutlines(outputProvider: (URI) -> Appendable, nodes: Iterable<DocumentationNode>)
+}
+
+
+interface JavaLayoutHtmlUriProvider {
+ fun tryGetContainerUri(node: DocumentationNode): URI?
+ fun tryGetMainUri(node: DocumentationNode): URI?
+ fun containerUri(node: DocumentationNode): URI = tryGetContainerUri(node) ?: error("Unsupported ${node.kind}")
+ fun mainUri(node: DocumentationNode): URI = tryGetMainUri(node) ?: error("Unsupported ${node.kind}")
+
+ fun linkTo(to: DocumentationNode, from: URI): String {
+ return mainUri(to).relativeTo(from).toString()
+ }
+
+ fun mainUriOrWarn(node: DocumentationNode): URI? = tryGetMainUri(node) ?: (null).also {
+ AssertionError("Not implemented mainUri for ${node.kind}").printStackTrace()
+ }
+}
+
+
+interface JavaLayoutHtmlTemplateService {
+ fun composePage(
+ nodes: List<DocumentationNode>,
+ tagConsumer: TagConsumer<Appendable>,
+ headContent: HEAD.() -> Unit,
+ bodyContent: BODY.() -> Unit
+ )
+
+ class Default : JavaLayoutHtmlTemplateService {
+ override fun composePage(
+ nodes: List<DocumentationNode>,
+ tagConsumer: TagConsumer<Appendable>,
+ headContent: HEAD.() -> Unit,
+ bodyContent: BODY.() -> Unit
+ ) {
+ tagConsumer.html {
+ head {
+ meta(charset = "UTF-8")
+ headContent()
+ }
+ body(block = bodyContent)
+ }
+ }
+ }
+}
+
+val DocumentationNode.companion get() = members(NodeKind.Object).find { it.details(NodeKind.Modifier).any { it.name == "companion" } }
+
+fun DocumentationNode.signatureForAnchor(logger: DokkaLogger): String {
+
+ fun StringBuilder.appendReceiverIfSo() {
+ detailOrNull(NodeKind.Receiver)?.let {
+ append("(")
+ append(it.detail(NodeKind.Type).qualifiedNameFromType())
+ append(").")
+ }
+ }
+
+ return when (kind) {
+ NodeKind.Function, NodeKind.Constructor, NodeKind.CompanionObjectFunction -> buildString {
+ if (kind == NodeKind.CompanionObjectFunction) {
+ append("Companion.")
+ }
+ appendReceiverIfSo()
+ append(name)
+ details(NodeKind.Parameter).joinTo(this, prefix = "(", postfix = ")") { it.detail(NodeKind.Type).qualifiedNameFromType() }
+ }
+ NodeKind.Property, NodeKind.CompanionObjectProperty -> buildString {
+ if (kind == NodeKind.CompanionObjectProperty) {
+ append("Companion.")
+ }
+ appendReceiverIfSo()
+ append(name)
+ append(":")
+ append(detail(NodeKind.Type).qualifiedNameFromType())
+ }
+ NodeKind.TypeParameter, NodeKind.Parameter -> owner!!.signatureForAnchor(logger) + "/" + name
+ else -> "Not implemented signatureForAnchor $this".also { logger.warn(it) }
+ }
+
+}
+
+fun String.urlEncoded(): String = URLEncoder.encode(this, "UTF-8")
+
+fun DocumentationNode.classNodeNameWithOuterClass(): String {
+ assert(kind in NodeKind.classLike)
+ return path.dropWhile { it.kind == NodeKind.Package || it.kind == NodeKind.Module }.joinToString(separator = ".") { it.name }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt
new file mode 100644
index 0000000..0c95a41
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt
@@ -0,0 +1,545 @@
+package org.jetbrains.dokka.Formats
+
+import kotlinx.html.*
+import kotlinx.html.Entities.nbsp
+import kotlinx.html.stream.appendHTML
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.LanguageService.RenderMode.FULL
+import org.jetbrains.dokka.LanguageService.RenderMode.SUMMARY
+import org.jetbrains.dokka.NodeKind.Companion.classLike
+import java.net.URI
+import javax.inject.Inject
+
+
+open class JavaLayoutHtmlFormatOutputBuilder(
+ val output: Appendable,
+ val languageService: LanguageService,
+ val uriProvider: JavaLayoutHtmlUriProvider,
+ val templateService: JavaLayoutHtmlTemplateService,
+ val logger: DokkaLogger,
+ val uri: URI
+) {
+
+ val htmlConsumer = output.appendHTML()
+
+ val contentToHtmlBuilder = ContentToHtmlBuilder(uriProvider, uri)
+
+ open fun <T> FlowContent.summaryNodeGroup(nodes: Iterable<T>, header: String, headerAsRow: Boolean = false, row: TBODY.(T) -> Unit) {
+ if (nodes.none()) return
+ if (!headerAsRow) {
+ h2 { +header }
+ }
+ table {
+ if (headerAsRow) thead { tr { td { h3 { +header } } } }
+ tbody {
+ nodes.forEach { node ->
+ row(node)
+ }
+ }
+ }
+ }
+
+ fun FlowContent.metaMarkup(content: ContentNode) = with(contentToHtmlBuilder) {
+ appendContent(content)
+ }
+
+ fun FlowContent.metaMarkup(content: List<ContentNode>) = with(contentToHtmlBuilder) {
+ appendContent(content)
+ }
+
+ open fun TBODY.classLikeRow(node: DocumentationNode) = tr {
+ td { a(href = uriProvider.linkTo(node, uri)) { +node.simpleName() } }
+ td { metaMarkup(node.summary) }
+ }
+
+ fun FlowContent.modifiers(node: DocumentationNode) {
+ for (modifier in node.details(NodeKind.Modifier)) {
+ renderedSignature(modifier, SUMMARY)
+ }
+ }
+
+ fun FlowContent.shortFunctionParametersList(func: DocumentationNode) {
+ val params = func.details(NodeKind.Parameter)
+ .map { languageService.render(it, FULL) }
+ .run {
+ drop(1).fold(listOfNotNull(firstOrNull())) { acc, node ->
+ acc + ContentText(", ") + node
+ }
+ }
+ metaMarkup(listOf(ContentText("(")) + params + listOf(ContentText(")")))
+ }
+
+
+ open fun TBODY.functionLikeSummaryRow(node: DocumentationNode) = tr {
+ if (node.kind != NodeKind.Constructor) {
+ td {
+ modifiers(node)
+ renderedSignature(node.detail(NodeKind.Type), SUMMARY)
+ }
+ }
+ td {
+ div {
+ code {
+ val receiver = node.detailOrNull(NodeKind.Receiver)
+ if (receiver != null) {
+ renderedSignature(receiver.detail(NodeKind.Type), SUMMARY)
+ +"."
+ }
+ a(href = uriProvider.linkTo(node, uri)) { +node.name }
+ shortFunctionParametersList(node)
+ }
+ }
+
+ metaMarkup(node.summary)
+ }
+ }
+
+ open fun TBODY.propertyLikeSummaryRow(node: DocumentationNode) = tr {
+ td {
+ modifiers(node)
+ renderedSignature(node.detail(NodeKind.Type), SUMMARY)
+ }
+ td {
+ div {
+ code {
+ a(href = uriProvider.linkTo(node, uri)) { +node.name }
+ }
+ }
+
+ metaMarkup(node.summary)
+ }
+ }
+
+ open fun TBODY.nestedClassSummaryRow(node: DocumentationNode) = tr {
+ td {
+ modifiers(node)
+ }
+ td {
+ div {
+ code {
+ a(href = uriProvider.linkTo(node, uri)) { +node.name }
+ }
+ }
+
+ metaMarkup(node.summary)
+ }
+ }
+
+ open fun TBODY.inheritRow(entry: Map.Entry<DocumentationNode, List<DocumentationNode>>, summaryRow: TBODY.(DocumentationNode) -> Unit) = tr {
+ td {
+ val (from, nodes) = entry
+ +"From class "
+ a(href = uriProvider.linkTo(from.owner!!, uri)) { +from.qualifiedName() }
+ table {
+ tbody {
+ for (node in nodes) {
+ summaryRow(node)
+ }
+ }
+ }
+ }
+ }
+
+ open fun TBODY.extensionRow(entry: Map.Entry<DocumentationNode, List<DocumentationNode>>, summaryRow: TBODY.(DocumentationNode) -> Unit) = tr {
+ td {
+ val (from, nodes) = entry
+ +"From "
+ a(href = uriProvider.linkTo(from, uri)) { +from.qualifiedName() }
+ table {
+ tbody {
+ for (node in nodes) {
+ summaryRow(node)
+ }
+ }
+ }
+ }
+ }
+
+ private fun FlowContent.renderedSignature(node: DocumentationNode, mode: LanguageService.RenderMode = SUMMARY) {
+ metaMarkup(languageService.render(node, mode))
+ }
+
+ open fun FlowContent.memberDocs(node: DocumentationNode) {
+ div {
+ id = node.signatureForAnchor(logger)
+ h3 { +node.name }
+ pre { renderedSignature(node, FULL) }
+ metaMarkup(node.content)
+ for ((name, sections) in node.content.sections.groupBy { it.tag }) {
+ table {
+ thead { tr { td { h3 { +name } } } }
+ tbody {
+ sections.forEach {
+ tr {
+ td { it.subjectName?.let { +it } }
+ td {
+ metaMarkup(it.children)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fun appendPackage(node: DocumentationNode) = templateService.composePage(
+ listOf(node),
+ htmlConsumer,
+ headContent = {
+
+ },
+ bodyContent = {
+ h1 { +node.name }
+ metaMarkup(node.content)
+ summaryNodeGroup(node.members(NodeKind.Class), "Classes") { classLikeRow(it) }
+ summaryNodeGroup(node.members(NodeKind.Exception), "Exceptions") { classLikeRow(it) }
+ summaryNodeGroup(node.members(NodeKind.TypeAlias), "Type-aliases") { classLikeRow(it) }
+ summaryNodeGroup(node.members(NodeKind.AnnotationClass), "Annotations") { classLikeRow(it) }
+ summaryNodeGroup(node.members(NodeKind.Enum), "Enums") { classLikeRow(it) }
+
+ summaryNodeGroup(node.members(NodeKind.Function), "Top-level functions summary") { functionLikeSummaryRow(it) }
+ summaryNodeGroup(node.members(NodeKind.Property), "Top-level properties summary") { propertyLikeSummaryRow(it) }
+
+
+ fullDocs(node.members(NodeKind.Function), "Top-level functions") { memberDocs(it) }
+ fullDocs(node.members(NodeKind.Property), "Top-level properties") { memberDocs(it) }
+ }
+ )
+
+ fun FlowContent.qualifiedTypeReference(node: DocumentationNode) {
+ if (node.kind in classLike) {
+ a(href = uriProvider.linkTo(node, uri)) {
+ +node.qualifiedName()
+ }
+ return
+ }
+
+ val targetLink = node.links.single()
+
+ if (targetLink.kind == NodeKind.TypeParameter) {
+ +node.name
+ return
+ }
+
+ val href = if (targetLink.kind == NodeKind.ExternalLink)
+ targetLink.name
+ else
+ uriProvider.linkTo(targetLink, uri)
+
+ a(href = href) {
+ +node.qualifiedNameFromType()
+ }
+ val typeParameters = node.details(NodeKind.Type)
+ if (typeParameters.isNotEmpty()) {
+ +"<"
+ typeParameters.forEach {
+ if (it != typeParameters.first()) {
+ +", "
+ }
+ qualifiedTypeReference(it)
+ }
+ +">"
+ }
+ }
+
+ open fun FlowContent.classHierarchy(node: DocumentationNode) {
+
+ val superclasses = generateSequence(node.superclass) { it.links.single().superclass }.toList().asReversed() + node
+ table {
+ superclasses.forEach {
+ tr {
+ if (it != superclasses.first()) {
+ td {
+ +" ↳"
+ }
+ }
+ td {
+ qualifiedTypeReference(it)
+ }
+ }
+ }
+ }
+ }
+
+ open fun FlowContent.subclasses(inheritors: List<DocumentationNode>, direct: Boolean) {
+ if (inheritors.isEmpty()) return
+ div {
+ table {
+ thead {
+ tr {
+ td {
+ if (direct)
+ +"Known Direct Subclasses"
+ else
+ +"Known Indirect Subclasses"
+ }
+ }
+ }
+ tbody {
+ inheritors.forEach {
+ tr {
+ td {
+ a(href = uriProvider.linkTo(it, uri)) { +it.classNodeNameWithOuterClass() }
+ }
+ td {
+ metaMarkup(it.summary)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fun appendClassLike(node: DocumentationNode) = templateService.composePage(
+ listOf(node),
+ htmlConsumer,
+ headContent = {
+
+ },
+ bodyContent = {
+ h1 { +node.name }
+ pre { renderedSignature(node, FULL) }
+ classHierarchy(node)
+
+ val inheritors = generateSequence(node.inheritors) { inheritors ->
+ inheritors
+ .flatMap { it.inheritors }
+ .takeUnless { it.isEmpty() }
+ }
+ subclasses(inheritors.first(), true)
+ subclasses(inheritors.drop(1).flatten().toList(), false)
+
+
+ metaMarkup(node.content)
+
+ h2 { +"Summary" }
+
+ val isCompanion = node.details(NodeKind.Modifier).any { it.name == "companion" }
+ val hasMeaningfulCompanion = !isCompanion && node.companion != null
+
+ fun DocumentationNode.thisTypeExtension() = detail(NodeKind.Receiver).detail(NodeKind.Type).links.any { it == node }
+
+ val functionKind = if (!isCompanion) NodeKind.Function else NodeKind.CompanionObjectFunction
+ val propertyKind = if (!isCompanion) NodeKind.Property else NodeKind.CompanionObjectProperty
+
+ fun DocumentationNode.isFunction() = kind == functionKind
+ fun DocumentationNode.isProperty() = kind == propertyKind
+
+ val functions = node.members(functionKind)
+ val properties = node.members(propertyKind)
+ val inheritedFunctionsByReceiver = node.inheritedMembers(functionKind).groupBy { it.owner!! }
+ val inheritedPropertiesByReceiver = node.inheritedMembers(propertyKind).groupBy { it.owner!! }
+
+
+ val originalExtensions = if (!isCompanion) node.extensions else node.owner!!.extensions
+ val (extensions, inheritedExtensions) = originalExtensions.partition { it.thisTypeExtension() }
+ val extensionFunctions = extensions.filter(DocumentationNode::isFunction).groupBy { it.owner!! }
+ val extensionProperties = extensions.filter(DocumentationNode::isProperty).groupBy { it.owner!! }
+ val inheritedExtensionFunctions = inheritedExtensions.filter(DocumentationNode::isFunction).groupBy { it.owner!! }
+ val inheritedExtensionProperties = inheritedExtensions.filter(DocumentationNode::isProperty).groupBy { it.owner!! }
+
+ val companionFunctions = node.members(NodeKind.CompanionObjectFunction)
+ val companionProperties = node.members(NodeKind.CompanionObjectProperty)
+
+ summaryNodeGroup(node.members.filter { it.kind in NodeKind.classLike }, "Nested classes", headerAsRow = true) { nestedClassSummaryRow(it) }
+
+ summaryNodeGroup(node.members(NodeKind.Constructor), "Constructors", headerAsRow = true) { functionLikeSummaryRow(it) }
+
+ summaryNodeGroup(functions, "Functions", headerAsRow = true) { functionLikeSummaryRow(it) }
+ if (!isCompanion) {
+ summaryNodeGroup(companionFunctions, "Companion functions", headerAsRow = true) { functionLikeSummaryRow(it) }
+ }
+ summaryNodeGroup(inheritedFunctionsByReceiver.entries, "Inherited functions", headerAsRow = true) { inheritRow(it) { functionLikeSummaryRow(it) } }
+ summaryNodeGroup(extensionFunctions.entries, "Extension functions", headerAsRow = true) { extensionRow(it) { functionLikeSummaryRow(it) } }
+ summaryNodeGroup(inheritedExtensionFunctions.entries, "Inherited extension functions", headerAsRow = true) { extensionRow(it) { functionLikeSummaryRow(it) } }
+
+
+ summaryNodeGroup(properties, "Properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
+ if (!isCompanion) {
+ summaryNodeGroup(companionProperties, "Companion properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
+ }
+ summaryNodeGroup(inheritedPropertiesByReceiver.entries, "Inherited properties", headerAsRow = true) { inheritRow(it) { propertyLikeSummaryRow(it) } }
+ summaryNodeGroup(extensionProperties.entries, "Extension properties", headerAsRow = true) { extensionRow(it) { propertyLikeSummaryRow(it) } }
+ summaryNodeGroup(inheritedExtensionProperties.entries, "Inherited extension properties", headerAsRow = true) { extensionRow(it) { propertyLikeSummaryRow(it) } }
+
+
+ fullDocs(node.members(NodeKind.Constructor), "Constructors") { memberDocs(it) }
+ fullDocs(functions, "Functions") { memberDocs(it) }
+ fullDocs(properties, "Properties") { memberDocs(it) }
+ if (!isCompanion && !hasMeaningfulCompanion) {
+ fullDocs(companionFunctions, "Companion functions") { memberDocs(it) }
+ fullDocs(companionProperties, "Companion properties") { memberDocs(it) }
+ }
+ }
+ )
+
+ fun generateClassesIndex(allTypesNode: DocumentationNode) = templateService.composePage(
+ listOf(allTypesNode),
+ htmlConsumer,
+ headContent = {
+
+ },
+ bodyContent = {
+ h1 { +"Class Index" }
+
+ fun DocumentationNode.classWithNestedClasses(): List<DocumentationNode> =
+ members.filter { it.kind in classLike }.flatMap(DocumentationNode::classWithNestedClasses) + this
+
+ val classesByFirstLetter = allTypesNode.members
+ .filterNot { it.kind == NodeKind.ExternalClass }
+ .flatMap(DocumentationNode::classWithNestedClasses)
+ .groupBy {
+ it.classNodeNameWithOuterClass().first().toString()
+ }
+ .entries
+ .sortedBy { (letter) -> letter }
+
+ ul {
+ classesByFirstLetter.forEach { (letter) ->
+ li { a(href = "#letter_$letter") { +letter } }
+ }
+ }
+
+ classesByFirstLetter.forEach { (letter, nodes) ->
+ h2 {
+ id = "letter_$letter"
+ +letter
+ }
+ table {
+ tbody {
+ for (node in nodes.sortedBy { it.classNodeNameWithOuterClass() }) {
+ tr {
+ td {
+ a(href = uriProvider.linkTo(node, uri)) { +node.classNodeNameWithOuterClass() }
+ }
+ td {
+ metaMarkup(node.content)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+
+ fun generatePackageIndex(nodes: List<DocumentationNode>) = templateService.composePage(nodes,
+ htmlConsumer,
+ headContent = {
+
+ },
+ bodyContent = {
+ h1 { +"Package Index" }
+ table {
+ tbody {
+ for (node in nodes.sortedBy { it.name }) {
+ tr {
+ td {
+ a(href = uriProvider.linkTo(node, uri)) { +node.name }
+ }
+ td {
+ metaMarkup(node.content)
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+
+ private fun FlowContent.fullDocs(
+ nodes: List<DocumentationNode>,
+ header: String,
+ renderNode: FlowContent.(DocumentationNode) -> Unit
+ ) {
+ if (nodes.none()) return
+ h2 {
+ +header
+ }
+ for (node in nodes) {
+ renderNode(node)
+ }
+ }
+}
+
+class ContentToHtmlBuilder(val uriProvider: JavaLayoutHtmlUriProvider, val uri: URI) {
+ fun FlowContent.appendContent(content: List<ContentNode>): Unit = content.forEach { appendContent(it) }
+
+ private fun FlowContent.hN(level: Int, classes: String? = null, block: CommonAttributeGroupFacadeFlowHeadingPhrasingContent.() -> Unit) {
+ when (level) {
+ 1 -> h1(classes, block)
+ 2 -> h2(classes, block)
+ 3 -> h3(classes, block)
+ 4 -> h4(classes, block)
+ 5 -> h5(classes, block)
+ 6 -> h6(classes, block)
+ }
+ }
+
+ fun FlowContent.appendContent(content: ContentNode) {
+ when (content) {
+ is ContentText -> +content.text
+ is ContentSymbol -> span("symbol") { +content.text }
+ is ContentKeyword -> span("keyword") { +content.text }
+ is ContentIdentifier -> span("identifier") {
+ content.signature?.let { id = it }
+ +content.text
+ }
+
+ is ContentHeading -> hN(level = content.level) { appendContent(content.children) }
+
+ is ContentEntity -> +content.text
+
+ is ContentStrong -> strong { appendContent(content.children) }
+ is ContentStrikethrough -> del { appendContent(content.children) }
+ is ContentEmphasis -> em { appendContent(content.children) }
+
+ is ContentOrderedList -> ol { appendContent(content.children) }
+ is ContentUnorderedList -> ul { appendContent(content.children) }
+ is ContentListItem -> consumer.li {
+ (content.children.singleOrNull() as? ContentParagraph)
+ ?.let { paragraph -> appendContent(paragraph.children) }
+ ?: appendContent(content.children)
+ }
+
+
+ is ContentCode -> pre { code { appendContent(content.children) } }
+ is ContentBlockSampleCode -> pre { code {} }
+ is ContentBlockCode -> pre { code {} }
+
+
+ is ContentNonBreakingSpace -> +nbsp
+ is ContentSoftLineBreak, is ContentIndentedSoftLineBreak -> {
+ }
+
+ is ContentParagraph -> p { appendContent(content.children) }
+
+ is ContentNodeLink -> {
+ a(href = content.node?.let { uriProvider.linkTo(it, uri) }
+ ?: "#unresolved") { appendContent(content.children) }
+ }
+ is ContentExternalLink -> {
+ a(href = content.href) { appendContent(content.children) }
+ }
+
+ is ContentBlock -> appendContent(content.children)
+ }
+ }
+}
+
+class JavaLayoutHtmlFormatOutputBuilderFactoryImpl @Inject constructor(
+ val uriProvider: JavaLayoutHtmlUriProvider,
+ val languageService: LanguageService,
+ val templateService: JavaLayoutHtmlTemplateService,
+ val logger: DokkaLogger
+) : JavaLayoutHtmlFormatOutputBuilderFactory {
+ override fun createOutputBuilder(output: Appendable, node: DocumentationNode): JavaLayoutHtmlFormatOutputBuilder {
+ return createOutputBuilder(output, uriProvider.mainUri(node))
+ }
+
+ override fun createOutputBuilder(output: Appendable, uri: URI): JavaLayoutHtmlFormatOutputBuilder {
+ return JavaLayoutHtmlFormatOutputBuilder(output, languageService, uriProvider, templateService, logger, uri)
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlGenerator.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlGenerator.kt
new file mode 100644
index 0000000..63668e7
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlGenerator.kt
@@ -0,0 +1,145 @@
+package org.jetbrains.dokka.Formats
+
+import com.google.inject.Inject
+import com.google.inject.name.Named
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.NodeKind.Companion.classLike
+import org.jetbrains.kotlin.preprocessor.mkdirsOrFail
+import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
+import java.io.BufferedWriter
+import java.io.File
+import java.net.URI
+
+
+class JavaLayoutHtmlFormatGenerator @Inject constructor(
+ @Named("outputDir") val root: File,
+ val languageService: LanguageService,
+ val templateService: JavaLayoutHtmlTemplateService,
+ val packageListService: PackageListService,
+ val outputBuilderFactoryService: JavaLayoutHtmlFormatOutputBuilderFactory,
+ val logger: DokkaLogger
+) : Generator, JavaLayoutHtmlUriProvider {
+
+ @set:Inject(optional = true)
+ var outlineFactoryService: JavaLayoutHtmlFormatOutlineFactoryService? = null
+
+ fun createOutputBuilderForNode(node: DocumentationNode, output: Appendable)
+ = outputBuilderFactoryService.createOutputBuilder(output, node)
+
+ override fun tryGetContainerUri(node: DocumentationNode): URI? {
+ return when (node.kind) {
+ NodeKind.Module -> URI("/").resolve(node.name + "/")
+ NodeKind.Package -> tryGetContainerUri(node.owner!!)?.resolve(node.name.replace('.', '/') + '/')
+ in NodeKind.classLike -> tryGetContainerUri(node.owner!!)?.resolve("${node.classNodeNameWithOuterClass()}.html")
+ else -> null
+ }
+ }
+
+ override fun tryGetMainUri(node: DocumentationNode): URI? {
+ return when (node.kind) {
+ NodeKind.Package -> tryGetContainerUri(node)?.resolve("package-summary.html")
+ in NodeKind.classLike -> tryGetContainerUri(node)?.resolve("#")
+ in NodeKind.memberLike -> {
+ val owner = if (node.owner?.kind != NodeKind.ExternalClass) node.owner else node.owner?.owner
+ if (owner!!.kind in classLike &&
+ (node.kind == NodeKind.CompanionObjectProperty || node.kind == NodeKind.CompanionObjectFunction) &&
+ owner.companion != null
+ ) {
+ val signature = node.detail(NodeKind.Signature)
+ val originalFunction = owner.companion!!.members.first { it.detailOrNull(NodeKind.Signature)?.name == signature.name }
+ tryGetMainUri(owner.companion!!)?.resolveInPage(originalFunction)
+ } else {
+ tryGetMainUri(owner)?.resolveInPage(node)
+ }
+ }
+ NodeKind.TypeParameter, NodeKind.Parameter -> node.path.asReversed().drop(1).firstNotNullResult(this::tryGetMainUri)?.resolveInPage(node)
+ NodeKind.AllTypes -> tryGetContainerUri(node.owner!!)?.resolve("classes.html")
+ else -> null
+ }
+ }
+
+ fun URI.resolveInPage(node: DocumentationNode): URI = resolve("#${node.signatureForAnchor(logger).urlEncoded()}")
+
+ fun buildClass(node: DocumentationNode, parentDir: File) {
+ val fileForClass = parentDir.resolve(node.classNodeNameWithOuterClass() + ".html")
+ fileForClass.bufferedWriter().use {
+ createOutputBuilderForNode(node, it).appendClassLike(node)
+ }
+ for (memberClass in node.members.filter { it.kind in NodeKind.classLike }) {
+ buildClass(memberClass, parentDir)
+ }
+ }
+
+ fun buildPackage(node: DocumentationNode, parentDir: File) {
+ assert(node.kind == NodeKind.Package)
+ val members = node.members
+ val directoryForPackage = parentDir.resolve(node.name.replace('.', File.separatorChar))
+ directoryForPackage.mkdirsOrFail()
+
+ directoryForPackage.resolve("package-summary.html").bufferedWriter().use {
+ createOutputBuilderForNode(node, it).appendPackage(node)
+ }
+
+ members.filter { it.kind in NodeKind.classLike }.forEach {
+ buildClass(it, directoryForPackage)
+ }
+ }
+
+ fun buildClassIndex(node: DocumentationNode, parentDir: File) {
+ val file = parentDir.resolve("classes.html")
+ file.bufferedWriter().use {
+ createOutputBuilderForNode(node, it).generateClassesIndex(node)
+ }
+ }
+
+ fun buildPackageIndex(nodes: List<DocumentationNode>, parentDir: File) {
+ val file = parentDir.resolve("packages.html")
+ file.bufferedWriter().use {
+ outputBuilderFactoryService.createOutputBuilder(it, containerUri(nodes.first().owner!!).resolve("packages.html"))
+ .generatePackageIndex(nodes)
+ }
+ }
+
+ override fun buildPages(nodes: Iterable<DocumentationNode>) {
+ val module = nodes.single()
+
+ val moduleRoot = root.resolve(module.name)
+ val packages = module.members.filter { it.kind == NodeKind.Package }
+ packages.forEach { buildPackage(it, moduleRoot) }
+
+ buildClassIndex(module.members.single { it.kind == NodeKind.AllTypes }, moduleRoot)
+ buildPackageIndex(packages, moduleRoot)
+ }
+
+ override fun buildOutlines(nodes: Iterable<DocumentationNode>) {
+ val uriToWriter = mutableMapOf<URI, BufferedWriter>()
+
+ fun provideOutput(uri: URI): BufferedWriter {
+ val normalized = uri.normalize()
+ uriToWriter[normalized]?.let { return it }
+ val file = root.resolve(normalized.path.removePrefix("/"))
+ val writer = file.bufferedWriter()
+ uriToWriter[normalized] = writer
+ return writer
+ }
+
+ outlineFactoryService?.generateOutlines(::provideOutput, nodes)
+
+ uriToWriter.values.forEach { it.close() }
+ }
+
+ override fun buildSupportFiles() {}
+
+ override fun buildPackageList(nodes: Iterable<DocumentationNode>) {
+ nodes.filter { it.kind == NodeKind.Module }.forEach { module ->
+ val moduleRoot = root.resolve(module.name)
+ val packageListFile = moduleRoot.resolve("package-list")
+ packageListFile.writeText(packageListService.formatPackageList(module as DocumentationModule))
+ }
+ }
+}
+
+interface JavaLayoutHtmlFormatOutputBuilderFactory {
+ fun createOutputBuilder(output: Appendable, uri: URI): JavaLayoutHtmlFormatOutputBuilder
+ fun createOutputBuilder(output: Appendable, node: DocumentationNode): JavaLayoutHtmlFormatOutputBuilder
+}
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt
new file mode 100644
index 0000000..1a0763f
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt
@@ -0,0 +1,28 @@
+package org.jetbrains.dokka.Formats
+
+import org.jetbrains.dokka.DocumentationModule
+import org.jetbrains.dokka.ExternalDocumentationLinkResolver.Companion.DOKKA_PARAM_PREFIX
+import org.jetbrains.dokka.NodeKind
+import org.jetbrains.dokka.PackageListService
+
+class JavaLayoutHtmlPackageListService : PackageListService {
+
+ private fun StringBuilder.appendParam(name: String, value: String) {
+ append(DOKKA_PARAM_PREFIX)
+ append(name)
+ append(":")
+ appendln(value)
+ }
+
+ override fun formatPackageList(module: DocumentationModule): String {
+ val packages = module.members(NodeKind.Package).map { it.name }
+
+ return buildString {
+ appendParam("format", "java-layout-html")
+ for (p in packages) {
+ appendln(p)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt b/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt
deleted file mode 100644
index 7d08238..0000000
--- a/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt
+++ /dev/null
@@ -1,662 +0,0 @@
-package org.jetbrains.dokka.Formats
-
-import com.google.inject.Binder
-import com.google.inject.Inject
-import com.google.inject.name.Named
-import kotlinx.html.*
-import kotlinx.html.Entities.nbsp
-import kotlinx.html.stream.appendHTML
-import org.jetbrains.dokka.*
-import org.jetbrains.dokka.LanguageService.RenderMode.FULL
-import org.jetbrains.dokka.LanguageService.RenderMode.SUMMARY
-import org.jetbrains.dokka.NodeKind.Companion.classLike
-import org.jetbrains.dokka.NodeKind.Companion.memberLike
-import org.jetbrains.dokka.Utilities.bind
-import org.jetbrains.dokka.Utilities.lazyBind
-import org.jetbrains.dokka.Utilities.toOptional
-import org.jetbrains.dokka.Utilities.toType
-import org.jetbrains.kotlin.preprocessor.mkdirsOrFail
-import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
-import java.io.BufferedWriter
-import java.io.File
-import java.net.URI
-import java.net.URLEncoder
-import kotlin.reflect.KClass
-
-
-abstract class JavaLayoutHtmlFormatDescriptorBase : FormatDescriptor, DefaultAnalysisComponent {
-
- override fun configureOutput(binder: Binder): Unit = with(binder) {
- bind<Generator>() toType generatorServiceClass
- bind<LanguageService>() toType languageServiceClass
- bind<JavaLayoutHtmlTemplateService>() toType templateServiceClass
- bind<JavaLayoutHtmlUriProvider>() toType generatorServiceClass
- lazyBind<JavaLayoutHtmlFormatOutlineFactoryService>() toOptional outlineFactoryClass
- }
-
- val generatorServiceClass = JavaLayoutHtmlFormatGenerator::class
- abstract val languageServiceClass: KClass<out LanguageService>
- abstract val templateServiceClass: KClass<out JavaLayoutHtmlTemplateService>
- abstract val outlineFactoryClass: KClass<out JavaLayoutHtmlFormatOutlineFactoryService>?
-}
-
-class JavaLayoutHtmlFormatDescriptor : JavaLayoutHtmlFormatDescriptorBase(), DefaultAnalysisComponentServices by KotlinAsKotlin {
- override val languageServiceClass = KotlinLanguageService::class
- override val templateServiceClass = JavaLayoutHtmlTemplateService.Default::class
- override val outlineFactoryClass = null
-}
-
-
-interface JavaLayoutHtmlTemplateService {
- fun composePage(
- nodes: List<DocumentationNode>,
- tagConsumer: TagConsumer<Appendable>,
- headContent: HEAD.() -> Unit,
- bodyContent: BODY.() -> Unit
- )
-
- class Default : JavaLayoutHtmlTemplateService {
- override fun composePage(
- nodes: List<DocumentationNode>,
- tagConsumer: TagConsumer<Appendable>,
- headContent: HEAD.() -> Unit,
- bodyContent: BODY.() -> Unit
- ) {
- tagConsumer.html {
- head {
- meta(charset = "UTF-8")
- headContent()
- }
- body(block = bodyContent)
- }
- }
- }
-}
-
-interface JavaLayoutHtmlFormatOutlineFactoryService {
- fun generateOutlines(outputProvider: (URI) -> Appendable, nodes: Iterable<DocumentationNode>)
-}
-
-class JavaLayoutHtmlFormatOutputBuilder(
- val output: Appendable,
- val languageService: LanguageService,
- val uriProvider: JavaLayoutHtmlUriProvider,
- val templateService: JavaLayoutHtmlTemplateService,
- val logger: DokkaLogger,
- val uri: URI
-) {
-
- val htmlConsumer = output.appendHTML()
-
- val contentToHtmlBuilder = ContentToHtmlBuilder(uriProvider, uri)
-
- private fun <T> FlowContent.summaryNodeGroup(nodes: Iterable<T>, header: String, headerAsRow: Boolean = false, row: TBODY.(T) -> Unit) {
- if (nodes.none()) return
- if (!headerAsRow) {
- h2 { +header }
- }
- table {
- if (headerAsRow) thead { tr { td { h3 { +header } } } }
- tbody {
- nodes.forEach { node ->
- row(node)
- }
- }
- }
- }
-
- fun FlowContent.metaMarkup(content: ContentNode) = with(contentToHtmlBuilder) {
- appendContent(content)
- }
-
- fun FlowContent.metaMarkup(content: List<ContentNode>) = with(contentToHtmlBuilder) {
- appendContent(content)
- }
-
- private fun TBODY.formatClassLikeRow(node: DocumentationNode) = tr {
- td { a(href = uriProvider.linkTo(node, uri)) { +node.simpleName() } }
- td { metaMarkup(node.summary) }
- }
-
- private fun FlowContent.modifiers(node: DocumentationNode) {
- for (modifier in node.details(NodeKind.Modifier)) {
- renderedSignature(modifier, SUMMARY)
- }
- }
-
- private fun FlowContent.shortFunctionParametersList(func: DocumentationNode) {
- val params = func.details(NodeKind.Parameter)
- .map { languageService.render(it, FULL) }
- .run {
- drop(1).fold(listOfNotNull(firstOrNull())) { acc, node ->
- acc + ContentText(", ") + node
- }
- }
- metaMarkup(listOf(ContentText("(")) + params + listOf(ContentText(")")))
- }
-
-
- private fun TBODY.functionLikeSummaryRow(node: DocumentationNode) = tr {
- if (node.kind != NodeKind.Constructor) {
- td {
- modifiers(node)
- renderedSignature(node.detail(NodeKind.Type), SUMMARY)
- }
- }
- td {
- div {
- code {
- a(href = uriProvider.linkTo(node, uri)) { +node.name }
- shortFunctionParametersList(node)
- }
- }
-
- metaMarkup(node.summary)
- }
- }
-
- private fun TBODY.propertyLikeSummaryRow(node: DocumentationNode) = tr {
- td {
- modifiers(node)
- renderedSignature(node.detail(NodeKind.Type), SUMMARY)
- }
- td {
- div {
- code {
- a(href = uriProvider.linkTo(node, uri)) { +node.name }
- }
- }
-
- metaMarkup(node.summary)
- }
- }
-
- private fun TBODY.nestedClassSummaryRow(node: DocumentationNode) = tr {
- td {
- modifiers(node)
- }
- td {
- div {
- code {
- a(href = uriProvider.linkTo(node, uri)) { +node.name }
- }
- }
-
- metaMarkup(node.summary)
- }
- }
-
- private fun TBODY.inheritRow(entry: Map.Entry<DocumentationNode, List<DocumentationNode>>, summaryRow: TBODY.(DocumentationNode) -> Unit) = tr {
- td {
- val (from, nodes) = entry
- +"From class "
- a(href = uriProvider.linkTo(from.owner!!, uri)) { +from.qualifiedName() }
- table {
- tbody {
- for (node in nodes) {
- summaryRow(node)
- }
- }
- }
- }
- }
-
- private fun FlowContent.renderedSignature(node: DocumentationNode, mode: LanguageService.RenderMode = SUMMARY) {
- metaMarkup(languageService.render(node, mode))
- }
-
- private fun FlowContent.fullFunctionDocs(node: DocumentationNode) {
- div {
- id = node.signatureForAnchor(logger)
- h3 { +node.name }
- pre { renderedSignature(node, FULL) }
- metaMarkup(node.content)
- for ((name, sections) in node.content.sections.groupBy { it.tag }) {
- table {
- thead { tr { td { h3 { +name } } } }
- tbody {
- sections.forEach {
- tr {
- td { it.subjectName?.let { +it } }
- td {
- metaMarkup(it.children)
- }
- }
- }
- }
- }
- }
- }
- }
-
- private fun FlowContent.fullPropertyDocs(node: DocumentationNode) {
- fullFunctionDocs(node)
- }
-
- fun appendPackage(node: DocumentationNode) = templateService.composePage(
- listOf(node),
- htmlConsumer,
- headContent = {
- title(node.nameWithOuterClass())
- },
- bodyContent = {
- h1 { +node.name }
- metaMarkup(node.content)
- summaryNodeGroup(node.members(NodeKind.Class), "Classes") { formatClassLikeRow(it) }
- summaryNodeGroup(node.members(NodeKind.Exception), "Exceptions") { formatClassLikeRow(it) }
- summaryNodeGroup(node.members(NodeKind.TypeAlias), "Type-aliases") { formatClassLikeRow(it) }
- summaryNodeGroup(node.members(NodeKind.AnnotationClass), "Annotations") { formatClassLikeRow(it) }
- summaryNodeGroup(node.members(NodeKind.Enum), "Enums") { formatClassLikeRow(it) }
-
- summaryNodeGroup(node.members(NodeKind.Function), "Top-level functions summary") { functionLikeSummaryRow(it) }
- summaryNodeGroup(node.members(NodeKind.Property), "Top-level properties summary") { propertyLikeSummaryRow(it) }
-
-
- fullDocs(node.members(NodeKind.Function), { h2 { +"Top-level functions" } }) { fullFunctionDocs(it) }
- fullDocs(node.members(NodeKind.Property), { h2 { +"Top-level properties" } }) { fullPropertyDocs(it) }
- }
- )
-
- fun FlowContent.classHierarchy(node: DocumentationNode) {
-
- val superclasses = generateSequence(node) { it.superclass }.toList().asReversed()
- table {
- superclasses.forEach {
- tr {
- if (it != superclasses.first()) {
- td {
- +" ↳"
- }
- }
- td {
- a(href = uriProvider.linkTo(it, uri)) { +it.qualifiedName() }
- }
- }
- }
- }
- }
-
- fun appendClassLike(node: DocumentationNode) = templateService.composePage(
- listOf(node),
- htmlConsumer,
- headContent = {
- title(node.nameWithOuterClass())
- },
- bodyContent = {
- h1 { +node.name }
- pre { renderedSignature(node, FULL) }
- classHierarchy(node)
-
- metaMarkup(node.content)
-
- h2 { +"Summary" }
-
- fun DocumentationNode.isFunction() = kind == NodeKind.Function || kind == NodeKind.CompanionObjectFunction
- fun DocumentationNode.isProperty() = kind == NodeKind.Property || kind == NodeKind.CompanionObjectProperty
-
- val functionsToDisplay = node.members.filter(DocumentationNode::isFunction)
- val properties = node.members.filter(DocumentationNode::isProperty)
- val inheritedFunctionsByReceiver = node.inheritedMembers.filter(DocumentationNode::isFunction).groupBy { it.owner!! }
- val inheritedPropertiesByReceiver = node.inheritedMembers.filter(DocumentationNode::isProperty).groupBy { it.owner!! }
- val extensionProperties = node.extensions.filter(DocumentationNode::isProperty)
- val extensionFunctions = node.extensions.filter(DocumentationNode::isFunction)
-
- summaryNodeGroup(node.members.filter { it.kind in NodeKind.classLike }, "Nested classes", headerAsRow = true) { nestedClassSummaryRow(it) }
-
- summaryNodeGroup(node.members(NodeKind.Constructor), "Constructors", headerAsRow = true) { functionLikeSummaryRow(it) }
-
- summaryNodeGroup(functionsToDisplay, "Functions", headerAsRow = true) { functionLikeSummaryRow(it) }
- summaryNodeGroup(inheritedFunctionsByReceiver.entries, "Inherited functions", headerAsRow = true) { inheritRow(it) { functionLikeSummaryRow(it) } }
- summaryNodeGroup(extensionFunctions, "Extension functions", headerAsRow = true) { functionLikeSummaryRow(it) }
-
-
- summaryNodeGroup(properties, "Properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
- summaryNodeGroup(inheritedPropertiesByReceiver.entries, "Inherited properties", headerAsRow = true) { inheritRow(it) { propertyLikeSummaryRow(it) } }
- summaryNodeGroup(extensionProperties, "Extension properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
-
- fullDocs(node.members(NodeKind.Constructor), { h2 { +"Constructors" } }) { fullFunctionDocs(it) }
- fullDocs(functionsToDisplay, { h2 { +"Functions" } }) { fullFunctionDocs(it) }
- fullDocs(extensionFunctions, { h2 { +"Extension functions" } }) { fullFunctionDocs(it) }
- fullDocs(properties, { h2 { +"Properties" } }) { fullPropertyDocs(it) }
- fullDocs(extensionProperties, { h2 { +"Extension properties" } }) { fullPropertyDocs(it) }
- }
- )
-
- fun generateClassesIndex(allTypesNode: DocumentationNode) = templateService.composePage(
- listOf(allTypesNode),
- htmlConsumer,
- headContent = {
-
- },
- bodyContent = {
- h1 { +"Class Index" }
-
- fun DocumentationNode.classWithNestedClasses(): List<DocumentationNode> =
- members.filter { it.kind in classLike }.flatMap(DocumentationNode::classWithNestedClasses) + this
-
- val classesByFirstLetter = allTypesNode.members
- .filterNot { it.kind == NodeKind.ExternalClass }
- .flatMap(DocumentationNode::classWithNestedClasses)
- .groupBy {
- it.classNodeNameWithOuterClass().first().toString()
- }
- .entries
- .sortedBy { (letter) -> letter }
-
- ul {
- classesByFirstLetter.forEach { (letter) ->
- li { a(href = "#letter_$letter") { +letter } }
- }
- }
-
- classesByFirstLetter.forEach { (letter, nodes) ->
- h2 {
- id = "letter_$letter"
- +letter
- }
- table {
- tbody {
- for (node in nodes.sortedBy { it.classNodeNameWithOuterClass() }) {
- tr {
- td {
- a(href = uriProvider.linkTo(node, uri)) { +node.classNodeNameWithOuterClass() }
- }
- td {
- metaMarkup(node.content)
- }
- }
- }
- }
- }
- }
- }
- )
-
- fun generatePackageIndex(nodes: List<DocumentationNode>) = templateService.composePage(nodes,
- htmlConsumer,
- headContent = {
-
- },
- bodyContent = {
- h1 { +"Package Index" }
- table {
- tbody {
- for (node in nodes.sortedBy { it.name }) {
- tr {
- td {
- a(href = uriProvider.linkTo(node, uri)) { +node.name }
- }
- td {
- metaMarkup(node.content)
- }
- }
- }
- }
- }
- }
- )
-
- private fun FlowContent.fullDocs(
- nodes: List<DocumentationNode>,
- header: FlowContent.() -> Unit,
- renderNode: FlowContent.(DocumentationNode) -> Unit
- ) {
- if (nodes.none()) return
- header()
- for (node in nodes) {
- renderNode(node)
- }
- }
-}
-
-class ContentToHtmlBuilder(val uriProvider: JavaLayoutHtmlUriProvider, val uri: URI) {
- fun FlowContent.appendContent(content: List<ContentNode>): Unit = content.forEach { appendContent(it) }
-
- private fun FlowContent.hN(level: Int, classes: String? = null, block: CommonAttributeGroupFacadeFlowHeadingPhrasingContent.() -> Unit) {
- when (level) {
- 1 -> h1(classes, block)
- 2 -> h2(classes, block)
- 3 -> h3(classes, block)
- 4 -> h4(classes, block)
- 5 -> h5(classes, block)
- 6 -> h6(classes, block)
- }
- }
-
- fun FlowContent.appendContent(content: ContentNode) {
- when (content) {
- is ContentText -> +content.text
- is ContentSymbol -> span("symbol") { +content.text }
- is ContentKeyword -> span("keyword") { +content.text }
- is ContentIdentifier -> span("identifier") {
- content.signature?.let { id = it }
- +content.text
- }
-
- is ContentHeading -> hN(level = content.level) { appendContent(content.children) }
-
- is ContentEntity -> +content.text
-
- is ContentStrong -> strong { appendContent(content.children) }
- is ContentStrikethrough -> del { appendContent(content.children) }
- is ContentEmphasis -> em { appendContent(content.children) }
-
- is ContentOrderedList -> ol { appendContent(content.children) }
- is ContentUnorderedList -> ul { appendContent(content.children) }
- is ContentListItem -> consumer.li {
- (content.children.singleOrNull() as? ContentParagraph)
- ?.let { paragraph -> appendContent(paragraph.children) }
- ?: appendContent(content.children)
- }
-
-
- is ContentCode -> pre { code { appendContent(content.children) } }
- is ContentBlockSampleCode -> pre { code {} }
- is ContentBlockCode -> pre { code {} }
-
-
- is ContentNonBreakingSpace -> +nbsp
- is ContentSoftLineBreak, is ContentIndentedSoftLineBreak -> {
- }
-
- is ContentParagraph -> p { appendContent(content.children) }
-
- is ContentNodeLink -> {
- a(href = content.node?.let { uriProvider.linkTo(it, uri) } ?: "#unresolved") { appendContent(content.children) }
- }
- is ContentExternalLink -> {
- a(href = content.href) { appendContent(content.children) }
- }
-
- is ContentBlock -> appendContent(content.children)
- }
- }
-}
-
-
-interface JavaLayoutHtmlUriProvider {
- fun tryGetContainerUri(node: DocumentationNode): URI?
- fun tryGetMainUri(node: DocumentationNode): URI?
- fun containerUri(node: DocumentationNode): URI = tryGetContainerUri(node) ?: error("Unsupported ${node.kind}")
- fun mainUri(node: DocumentationNode): URI = tryGetMainUri(node) ?: error("Unsupported ${node.kind}")
-
- fun linkTo(to: DocumentationNode, from: URI): String {
- return mainUri(to).relativeTo(from).toString()
- }
-
- fun mainUriOrWarn(node: DocumentationNode): URI? = tryGetMainUri(node) ?: (null).also {
- AssertionError("Not implemented mainUri for ${node.kind}").printStackTrace()
- }
-}
-
-class JavaLayoutHtmlFormatGenerator @Inject constructor(
- @Named("outputDir") val root: File,
- val languageService: LanguageService,
- val templateService: JavaLayoutHtmlTemplateService,
- val logger: DokkaLogger
-) : Generator, JavaLayoutHtmlUriProvider {
-
- @set:Inject(optional = true)
- var outlineFactoryService: JavaLayoutHtmlFormatOutlineFactoryService? = null
-
- fun createOutputBuilderForNode(node: DocumentationNode, output: Appendable)
- = JavaLayoutHtmlFormatOutputBuilder(output, languageService, this, templateService, logger, mainUri(node))
-
- override fun tryGetContainerUri(node: DocumentationNode): URI? {
- return when (node.kind) {
- NodeKind.Module -> URI("/").resolve(node.name + "/")
- NodeKind.Package -> tryGetContainerUri(node.owner!!)?.resolve(node.name.replace('.', '/') + '/')
- in classLike -> tryGetContainerUri(node.owner!!)?.resolve("${node.classNodeNameWithOuterClass()}.html")
- else -> null
- }
- }
-
- override fun tryGetMainUri(node: DocumentationNode): URI? {
- return when (node.kind) {
- NodeKind.Package -> tryGetContainerUri(node)?.resolve("package-summary.html")
- in classLike -> tryGetContainerUri(node)?.resolve("#")
- in memberLike -> {
- val owner = if (node.owner?.kind != NodeKind.ExternalClass) node.owner else node.owner?.owner
- tryGetMainUri(owner!!)?.resolveInPage(node)
- }
- NodeKind.TypeParameter, NodeKind.Parameter -> node.path.asReversed().drop(1).firstNotNullResult(this::tryGetMainUri)?.resolveInPage(node)
- NodeKind.AllTypes -> tryGetContainerUri(node.owner!!)?.resolve("classes.html")
- else -> null
- }
- }
-
- fun URI.resolveInPage(node: DocumentationNode): URI = resolve("#${node.signatureUrlEncoded(logger)}")
-
- fun buildClass(node: DocumentationNode, parentDir: File) {
- val fileForClass = parentDir.resolve(node.classNodeNameWithOuterClass() + ".html")
- fileForClass.bufferedWriter().use {
- createOutputBuilderForNode(node, it).appendClassLike(node)
- }
- for (memberClass in node.members.filter { it.kind in classLike }) {
- buildClass(memberClass, parentDir)
- }
- }
-
- fun buildPackage(node: DocumentationNode, parentDir: File) {
- assert(node.kind == NodeKind.Package)
- val members = node.members
- val directoryForPackage = parentDir.resolve(node.name.replace('.', File.separatorChar))
- directoryForPackage.mkdirsOrFail()
-
- directoryForPackage.resolve("package-summary.html").bufferedWriter().use {
- createOutputBuilderForNode(node, it).appendPackage(node)
- }
-
- members.filter { it.kind in classLike }.forEach {
- buildClass(it, directoryForPackage)
- }
- }
-
- fun buildClassIndex(node: DocumentationNode, parentDir: File) {
- val file = parentDir.resolve("classes.html")
- file.bufferedWriter().use {
- createOutputBuilderForNode(node, it).generateClassesIndex(node)
- }
- }
-
- fun buildPackageIndex(nodes: List<DocumentationNode>, parentDir: File) {
- val file = parentDir.resolve("packages.html")
- file.bufferedWriter().use {
- JavaLayoutHtmlFormatOutputBuilder(it, languageService, this, templateService, logger, containerUri(nodes.first().owner!!).resolve("packages.html"))
- .generatePackageIndex(nodes)
- }
- }
-
- override fun buildPages(nodes: Iterable<DocumentationNode>) {
- val module = nodes.single()
-
- val moduleRoot = root.resolve(module.name)
- val packages = module.members.filter { it.kind == NodeKind.Package }
- packages.forEach { buildPackage(it, moduleRoot) }
-
- buildClassIndex(module.members.single { it.kind == NodeKind.AllTypes }, moduleRoot)
- buildPackageIndex(packages, moduleRoot)
- }
-
- override fun buildOutlines(nodes: Iterable<DocumentationNode>) {
- val uriToWriter = mutableMapOf<URI, BufferedWriter>()
-
- fun provideOutput(uri: URI): BufferedWriter {
- val normalized = uri.normalize()
- uriToWriter[normalized]?.let { return it }
- val file = root.resolve(normalized.path.removePrefix("/"))
- val writer = file.bufferedWriter()
- uriToWriter[normalized] = writer
- return writer
- }
-
- outlineFactoryService?.generateOutlines(::provideOutput, nodes)
-
- uriToWriter.values.forEach { it.close() }
- }
-
- override fun buildSupportFiles() {}
-
- override fun buildPackageList(nodes: Iterable<DocumentationNode>) {
-
- }
-}
-
-fun DocumentationNode.signatureForAnchor(logger: DokkaLogger): String = when (kind) {
- NodeKind.Function, NodeKind.Constructor -> buildString {
- detailOrNull(NodeKind.Receiver)?.let {
- append("(")
- append(it.detail(NodeKind.Type).qualifiedNameFromType())
- append(").")
- }
- append(name)
- details(NodeKind.Parameter).joinTo(this, prefix = "(", postfix = ")") { it.detail(NodeKind.Type).qualifiedNameFromType() }
- }
- NodeKind.Property ->
- "$name:${detail(NodeKind.Type).qualifiedNameFromType()}"
- NodeKind.TypeParameter, NodeKind.Parameter -> owner!!.signatureForAnchor(logger) + "/" + name
- else -> "Not implemented signatureForAnchor $this".also { logger.warn(it) }
-}
-
-fun DocumentationNode.signatureUrlEncoded(logger: DokkaLogger) = URLEncoder.encode(signatureForAnchor(logger), "UTF-8")
-
-fun DocumentationNode.classNodeNameWithOuterClass(): String {
- assert(kind in NodeKind.classLike)
- return path.dropWhile { it.kind == NodeKind.Package || it.kind == NodeKind.Module }.joinToString(separator = ".") { it.name }
-}
-
-fun URI.relativeTo(uri: URI): URI {
- // Normalize paths to remove . and .. segments
- val base = uri.normalize()
- val child = this.normalize()
-
- fun StringBuilder.appendRelativePath() {
- // Split paths into segments
- var bParts = base.path.split('/').dropLastWhile { it.isEmpty() }
- val cParts = child.path.split('/').dropLastWhile { it.isEmpty() }
-
- // Discard trailing segment of base path
- if (bParts.isNotEmpty() && !base.path.endsWith("/")) {
- bParts = bParts.dropLast(1)
- }
-
- // Compute common prefix
- val commonPartsSize = bParts.zip(cParts).count { (basePart, childPart) -> basePart == childPart }
- bParts.drop(commonPartsSize).joinTo(this, separator = "") { "../" }
- cParts.drop(commonPartsSize).joinTo(this, separator = "/")
- }
-
- return URI.create(buildString {
- if (base.path != child.path) {
- appendRelativePath()
- }
- child.rawQuery?.let {
- append("?")
- append(it)
- }
- child.rawFragment?.let {
- append("#")
- append(it)
- }
- })
-}
diff --git a/core/src/main/kotlin/Model/DocumentationNode.kt b/core/src/main/kotlin/Model/DocumentationNode.kt
index 4b10879..a145ae8 100644
--- a/core/src/main/kotlin/Model/DocumentationNode.kt
+++ b/core/src/main/kotlin/Model/DocumentationNode.kt
@@ -106,10 +106,10 @@
get() = references(RefKind.Platform).map { it.to.name }
val supertypes: List<DocumentationNode>
- get() = references(RefKind.Superclass).map { it.to }
+ get() = details(NodeKind.Supertype)
val superclass: DocumentationNode?
- get() = supertypes.find { it.kind == NodeKind.Class }
+ get() = supertypes.firstOrNull { it.links.any { it.kind in NodeKind.classLike } }
// TODO: Should we allow node mutation? Model merge will copy by ref, so references are transparent, which could nice
fun addReferenceTo(to: DocumentationNode, kind: RefKind) {
@@ -162,7 +162,7 @@
}
fun DocumentationNode.findOrCreatePackageNode(packageName: String, packageContent: Map<String, Content>, refGraph: NodeReferenceGraph): DocumentationNode {
- val existingNode = members(NodeKind.Package).firstOrNull { it.name == packageName }
+ val existingNode = members(NodeKind.Package).firstOrNull { it.name == packageName }
if (existingNode != null) {
return existingNode
}
@@ -180,7 +180,8 @@
RefKind.Detail -> child.addReferenceTo(this, RefKind.Owner)
RefKind.Member -> child.addReferenceTo(this, RefKind.Owner)
RefKind.Owner -> child.addReferenceTo(this, RefKind.Member)
- else -> { /* Do not add any links back for other types */ }
+ else -> { /* Do not add any links back for other types */
+ }
}
}
diff --git a/core/src/main/kotlin/Utilities/Uri.kt b/core/src/main/kotlin/Utilities/Uri.kt
new file mode 100644
index 0000000..5b52018
--- /dev/null
+++ b/core/src/main/kotlin/Utilities/Uri.kt
@@ -0,0 +1,40 @@
+package org.jetbrains.dokka
+
+import java.net.URI
+
+
+fun URI.relativeTo(uri: URI): URI {
+ // Normalize paths to remove . and .. segments
+ val base = uri.normalize()
+ val child = this.normalize()
+
+ fun StringBuilder.appendRelativePath() {
+ // Split paths into segments
+ var bParts = base.path.split('/').dropLastWhile { it.isEmpty() }
+ val cParts = child.path.split('/').dropLastWhile { it.isEmpty() }
+
+ // Discard trailing segment of base path
+ if (bParts.isNotEmpty() && !base.path.endsWith("/")) {
+ bParts = bParts.dropLast(1)
+ }
+
+ // Compute common prefix
+ val commonPartsSize = bParts.zip(cParts).count { (basePart, childPart) -> basePart == childPart }
+ bParts.drop(commonPartsSize).joinTo(this, separator = "") { "../" }
+ cParts.drop(commonPartsSize).joinTo(this, separator = "/")
+ }
+
+ return URI.create(buildString {
+ if (base.path != child.path) {
+ appendRelativePath()
+ }
+ child.rawQuery?.let {
+ append("?")
+ append(it)
+ }
+ child.rawFragment?.let {
+ append("#")
+ append(it)
+ }
+ })
+}
\ No newline at end of file