diff options
author | Simon Ogorodnik <Simon.Ogorodnik@jetbrains.com> | 2017-12-28 20:27:28 +0300 |
---|---|---|
committer | Simon Ogorodnik <Simon.Ogorodnik@jetbrains.com> | 2017-12-28 20:27:28 +0300 |
commit | cca63f7d6269193d28f5c24ea5b3a21357759347 (patch) | |
tree | c03af7f7dc36030b4190df4a91195fa22df01da5 /core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt | |
parent | f3c67082b056b0ca84c80528db4a8e2b62c5bb49 (diff) | |
download | dokka-cca63f7d6269193d28f5c24ea5b3a21357759347.tar.gz |
Split JavaLayoutHtml to separate files
Diffstat (limited to 'core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt')
-rw-r--r-- | core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt | 408 |
1 files changed, 408 insertions, 0 deletions
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 000000000..84778d00d --- /dev/null +++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt @@ -0,0 +1,408 @@ +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 java.net.URLEncoder + + +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 = { + + }, + 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 = { + + }, + 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) + } + } +}
\ No newline at end of file |