| package org.jetbrains.dokka.Formats |
| |
| import com.google.inject.Inject |
| import com.google.inject.name.Named |
| import kotlinx.html.* |
| import org.jetbrains.dokka.* |
| import org.jetbrains.dokka.Samples.DevsiteSampleProcessingService |
| import org.jetbrains.dokka.Kotlin.ParameterInfoNode |
| import org.jetbrains.dokka.Utilities.firstSentence |
| import java.lang.Math.max |
| import java.net.URI |
| import kotlin.reflect.KClass |
| |
| /** |
| * On Devsite, certain headers and footers are needed for generating Devsite metadata. |
| */ |
| class DevsiteHtmlTemplateService @Inject constructor( |
| @Named("outlineRoot") val outlineRoot: String, |
| @Named("dacRoot") val dacRoot: String |
| ) : JavaLayoutHtmlTemplateService { |
| override fun composePage(page: JavaLayoutHtmlFormatOutputBuilder.Page, tagConsumer: TagConsumer<Appendable>, headContent: HEAD.() -> Unit, bodyContent: BODY.() -> Unit) { |
| tagConsumer.html { |
| attributes["devsite"] = "true" |
| head { |
| headContent() |
| title { |
| +when (page) { |
| is JavaLayoutHtmlFormatOutputBuilder.Page.ClassIndex -> "Class Index" |
| is JavaLayoutHtmlFormatOutputBuilder.Page.ClassPage -> page.node.nameWithOuterClass() |
| is JavaLayoutHtmlFormatOutputBuilder.Page.PackageIndex -> "Package Index" |
| is JavaLayoutHtmlFormatOutputBuilder.Page.PackagePage -> page.node.nameWithOuterClass() |
| } |
| } |
| unsafe { +"{% setvar book_path %}${dacRoot}/${outlineRoot}_book.yaml{% endsetvar %}\n{% include \"_shared/_reference-head-tags.html\" %}\n" } |
| } |
| body { |
| bodyContent() |
| } |
| } |
| } |
| } |
| |
| class DevsiteLayoutHtmlFormatOutputBuilderFactoryImpl @javax.inject.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 DevsiteLayoutHtmlFormatOutputBuilder(output, languageService, uriProvider, templateService, logger, uri) |
| } |
| } |
| |
| class DevsiteLayoutHtmlFormatOutputBuilder( |
| output: Appendable, |
| languageService: LanguageService, |
| uriProvider: JavaLayoutHtmlUriProvider, |
| templateService: JavaLayoutHtmlTemplateService, |
| logger: DokkaLogger, |
| uri: URI |
| ) : JavaLayoutHtmlFormatOutputBuilder(output, languageService, uriProvider, templateService, logger, uri) { |
| override fun FlowContent.fullMemberDocs(node: DocumentationNode) { |
| fullMemberDocs(node, node) |
| } |
| |
| override fun FlowContent.fullMemberDocs(node: DocumentationNode, uriNode: DocumentationNode) { |
| a { |
| attributes["name"] = uriNode.signatureForAnchor(logger).anchorEncoded() |
| } |
| div(classes = "api apilevel-${node.apiLevel.name}") { |
| attributes["data-version-added"] = node.apiLevel.name |
| h3(classes = "api-name") { |
| //id = node.signatureForAnchor(logger).urlEncoded() |
| +node.prettyName |
| } |
| apiAndDeprecatedVersions(node) |
| pre(classes = "api-signature no-pretty-print") { renderedSignature(node, LanguageService.RenderMode.FULL) } |
| deprecationWarningToMarkup(node, prefix = true) |
| nodeContent(node, uriNode) |
| node.constantValue()?.let { value -> |
| pre { |
| +"Value: " |
| code { +value } |
| } |
| } |
| for ((name, sections) in node.content.sections.groupBy { it.tag }) { |
| when (name) { |
| ContentTags.Return -> { |
| table(classes = "responsive") { |
| tbody { |
| tr { |
| th { |
| colSpan = "2" |
| +name |
| } |
| } |
| sections.forEach { |
| tr { |
| if (it.children.size > 0) { |
| td { |
| val firstChild = it.children.first() |
| if (firstChild is ContentBlock && |
| firstChild.children.size == 3 && |
| firstChild.children[0] is NodeRenderContent && |
| firstChild.children[1] is ContentSymbol && |
| firstChild.children[2] is ContentText) { |
| // it.children is expected to have two items |
| // First should have 3 children of its own: |
| // - NodeRenderContent is the return type |
| // - ContentSymbol - ":" |
| // - ContentText - " " |
| // We want to only use NodeRenderContent in a separate <td> and |
| // <code> to get proper formatting in DAC. |
| code { |
| metaMarkup(listOf(firstChild.children[0])) |
| } |
| } else { |
| metaMarkup(listOf(firstChild)) |
| } |
| } |
| td { |
| if (it.children.size > 1) { |
| metaMarkup(it.children.subList(1, it.children.size)) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| ContentTags.Parameters -> { |
| table(classes = "responsive") { |
| tbody { |
| tr { |
| th { |
| colSpan = "2" |
| +name |
| } |
| } |
| sections.forEach { section -> |
| tr { |
| td { |
| val parameterInfoNode = section.children.find { it is ParameterInfoNode } as? ParameterInfoNode |
| // If there is no info found, just display the parameter |
| // name. |
| if (parameterInfoNode?.parameterContent == null) { |
| code { |
| section.subjectName?.let { +it } |
| } |
| } else { |
| // Add already marked up type information here |
| metaMarkup( |
| listOf(parameterInfoNode.parameterContent!!) |
| ) |
| } |
| } |
| td { |
| metaMarkup(section.children) |
| } |
| } |
| } |
| } |
| } |
| } |
| ContentTags.SeeAlso -> { |
| div { |
| p { |
| b { |
| +name |
| } |
| } |
| ul(classes = "nolist") { |
| sections.filter {it.tag == "See Also"}.forEach { |
| it.children.forEach { child -> |
| if (child is ContentNodeLazyLink || child is ContentExternalLink) { |
| li { |
| code { |
| contentNodeToMarkup(child) // Wrap bare links in listItems. |
| } // bare links come from the java-to-kotlin parser. |
| } |
| } |
| else if (child is ContentUnorderedList) { |
| metaMarkup(child.children) // Already wrapped in listItems. |
| } // this is how we want things to look. No parser currently does this (yet). |
| else if (child is ContentParagraph) { |
| li{ |
| code { |
| metaMarkup (child.children) // Replace paragraphs with listItems. |
| } // paragraph-wrapped links come from the kotlin parser |
| } |
| } // NOTE: currently the java-to-java parser does not add See Also links! |
| } |
| } |
| } |
| } |
| } |
| ContentTags.Exceptions -> { |
| table(classes = "responsive") { |
| tbody { |
| tr { |
| th { |
| colSpan = "2" |
| +name |
| } |
| } |
| sections.forEach { |
| tr { |
| td { |
| code { |
| it.subjectName?.let { +it } |
| } |
| } |
| td { |
| metaMarkup(it.children) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| override fun summary(node: DocumentationNode) = node.firstSentenceOfSummary() |
| |
| fun TBODY.xmlAttributeRow(attr: DocumentationNode) = tr { |
| td { |
| a(href = attr) { |
| code { |
| +attr.attributeRef!!.name |
| } |
| } |
| } |
| td { |
| +attr.attributeRef!!.firstSentence() |
| } |
| } |
| |
| protected fun FlowContent.fullAttributeDocs( |
| attributes: List<DocumentationNode>, |
| header: String |
| ) { |
| if (attributes.none()) return |
| h2 { |
| +header |
| } |
| attributes.forEach { |
| fullMemberDocs(it.attributeRef!!, it) |
| } |
| } |
| |
| override fun FlowContent.classLikeFullMemberDocs(page: Page.ClassPage) = with(page) { |
| fullAttributeDocs(attributes, "XML attributes") |
| fullMemberDocs(enumValues, "Enum values") |
| fullMemberDocs(constants, "Constants") |
| |
| constructors.forEach { (visibility, group) -> |
| fullMemberDocs(group, "${visibility.capitalize()} constructors") |
| } |
| |
| functions.forEach { (visibility, group) -> |
| fullMemberDocs(group, "${visibility.capitalize()} methods") |
| } |
| |
| fullMemberDocs(properties, "Properties") |
| |
| fields.forEach { (visibility, group) -> |
| fullMemberDocs(group, "${visibility.capitalize()} fields") |
| } |
| if (!hasMeaningfulCompanion) { |
| fullMemberDocs(companionFunctions, "Companion functions") |
| fullMemberDocs(companionProperties, "Companion properties") |
| } |
| } |
| |
| override fun FlowContent.classLikeSummaries(page: Page.ClassPage) = with(page) { |
| this@classLikeSummaries.summaryNodeGroup( |
| nestedClasses, |
| header = "Nested classes", |
| summaryId = "nestedclasses", |
| tableClass = "responsive", |
| headerAsRow = true |
| ) { |
| nestedClassSummaryRow(it) |
| } |
| |
| this@classLikeSummaries.summaryNodeGroup( |
| attributes, |
| header="XML attributes", |
| summaryId="lattrs", |
| tableClass = "responsive", |
| headerAsRow = true |
| ) { |
| xmlAttributeRow(it) |
| } |
| |
| this@classLikeSummaries.expandableSummaryNodeGroupForInheritedMembers( |
| superClasses = inheritedAttributes.entries, |
| header="Inherited XML attributes", |
| tableId="inhattrs", |
| tableClass = "responsive", |
| row = { inheritedXmlAttributeRow(it)} |
| ) |
| |
| this@classLikeSummaries.summaryNodeGroup( |
| constants, |
| header = "Constants", |
| summaryId = "constants", |
| tableClass = "responsive", |
| headerAsRow = true |
| ) { propertyLikeSummaryRow(it) } |
| |
| this@classLikeSummaries.expandableSummaryNodeGroupForInheritedMembers( |
| superClasses = inheritedConstants.entries, |
| header = "Inherited constants", |
| tableId = "inhconstants", |
| tableClass = "responsive constants inhtable", |
| row = { inheritedMemberRow(it) } |
| ) |
| |
| constructors.forEach { (visibility, group) -> |
| this@classLikeSummaries.summaryNodeGroup( |
| group, |
| header = "${visibility.capitalize()} constructors", |
| summaryId = "${visibility.take(3)}ctors", |
| tableClass = "responsive", |
| headerAsRow = true |
| ) { |
| functionLikeSummaryRow(it) |
| } |
| } |
| |
| this@classLikeSummaries.summaryNodeGroup( |
| enumValues, |
| header = "Enum values", |
| summaryId = "enumvalues", |
| tableClass = "responsive", |
| headerAsRow = true |
| ) { |
| propertyLikeSummaryRow(it, showSignature = false) |
| } |
| |
| functions.forEach { (visibility, group) -> |
| this@classLikeSummaries.summaryNodeGroup( |
| group, |
| header = "${visibility.capitalize()} methods", |
| summaryId = "${visibility.take(3)}methods", |
| tableClass = "responsive", |
| headerAsRow = true |
| ) { |
| functionLikeSummaryRow(it) |
| } |
| } |
| |
| this@classLikeSummaries.summaryNodeGroup( |
| companionFunctions, |
| header = "Companion functions", |
| summaryId = "compmethods", |
| tableClass = "responsive", |
| headerAsRow = true |
| ) { |
| functionLikeSummaryRow(it) |
| } |
| |
| this@classLikeSummaries.expandableSummaryNodeGroupForInheritedMembers( |
| superClasses = inheritedFunctionsByReceiver.entries, |
| header = "Inherited functions", |
| tableId = "inhmethods", |
| tableClass = "responsive", |
| row = { inheritedMemberRow(it) } |
| ) |
| |
| this@classLikeSummaries.summaryNodeGroup( |
| extensionFunctions.entries, |
| header = "Extension functions", |
| summaryId = "extmethods", |
| tableClass = "responsive", |
| headerAsRow = true |
| ) { |
| extensionRow(it) { |
| functionLikeSummaryRow(it) |
| } |
| } |
| this@classLikeSummaries.summaryNodeGroup( |
| inheritedExtensionFunctions.entries, |
| header = "Inherited extension functions", |
| summaryId = "inhextmethods", |
| tableClass = "responsive", |
| headerAsRow = true |
| ) { |
| extensionRow(it) { |
| functionLikeSummaryRow(it) |
| } |
| } |
| |
| fields.forEach { (visibility, group) -> |
| this@classLikeSummaries.summaryNodeGroup( |
| group, |
| header = "${visibility.capitalize()} fields", |
| summaryId = "${visibility.take(3)}fields", |
| tableClass = "responsive", |
| headerAsRow = true |
| ) { propertyLikeSummaryRow(it) } |
| } |
| |
| this@classLikeSummaries.expandableSummaryNodeGroupForInheritedMembers( |
| superClasses = inheritedFieldsByReceiver.entries, |
| header = "Inherited fields", |
| tableId = "inhfields", |
| tableClass = "responsive properties inhtable", |
| row = { inheritedMemberRow(it) } |
| ) |
| |
| this@classLikeSummaries.summaryNodeGroup( |
| properties, |
| header = "Properties", |
| summaryId = "properties", |
| tableClass = "responsive", |
| headerAsRow = true |
| ) { propertyLikeSummaryRow(it) } |
| |
| |
| this@classLikeSummaries.summaryNodeGroup( |
| companionProperties, |
| "Companion properties", |
| headerAsRow = true |
| ) { |
| propertyLikeSummaryRow(it) |
| } |
| |
| this@classLikeSummaries.expandableSummaryNodeGroupForInheritedMembers( |
| superClasses = inheritedPropertiesByReceiver.entries, |
| header = "Inherited properties", |
| tableId = "inhfields", |
| tableClass = "responsive properties inhtable", |
| row = { inheritedMemberRow(it) } |
| ) |
| |
| this@classLikeSummaries.summaryNodeGroup( |
| extensionProperties.entries, |
| "Extension properties", |
| headerAsRow = true |
| ) { |
| extensionRow(it) { |
| propertyLikeSummaryRow(it) |
| } |
| } |
| |
| this@classLikeSummaries.summaryNodeGroup( |
| inheritedExtensionProperties.entries, |
| "Inherited extension properties", |
| headerAsRow = true |
| ) { |
| extensionRow(it) { |
| propertyLikeSummaryRow(it) |
| } |
| } |
| } |
| |
| fun <T> FlowContent.summaryNodeGroup( |
| nodes: Iterable<T>, |
| header: String, |
| headerAsRow: Boolean, |
| summaryId: String, |
| tableClass: String = "responsive", |
| row: TBODY.(T) -> Unit |
| ) { |
| if (nodes.none()) return |
| if (!headerAsRow) { |
| h2 { +header } |
| } |
| table(classes = tableClass) { |
| id = summaryId |
| tbody { |
| if (headerAsRow) { |
| developerHeading(header) |
| } |
| nodes.forEach { node -> |
| row(node) |
| } |
| } |
| } |
| } |
| |
| override fun FlowContent.contentBlockCode(content: ContentBlockCode) { |
| pre { |
| attributes["class"] = "prettyprint" |
| contentNodesToMarkup(content.children) |
| } |
| } |
| |
| override fun FlowContent.contentBlockSampleCode(content: ContentBlockSampleCode) { |
| pre { |
| attributes["class"] = "prettyprint" |
| contentNodesToMarkup(content.importsBlock.children) |
| +"\n\n" |
| contentNodesToMarkup(content.children) |
| } |
| } |
| |
| override fun generatePackage(page: Page.PackagePage) = templateService.composePage( |
| page, |
| htmlConsumer, |
| headContent = { |
| |
| }, |
| bodyContent = { |
| h1 { +page.node.name } |
| nodeContent(page.node) |
| this@composePage.summaryNodeGroup(page.interfaces.sortedBy { it.nameWithOuterClass().toLowerCase() }, "Interfaces", headerAsRow = false) { classLikeRow(it) } |
| this@composePage.summaryNodeGroup(page.classes.sortedBy { it.nameWithOuterClass().toLowerCase() }, "Classes", headerAsRow = false) { classLikeRow(it) } |
| this@composePage.summaryNodeGroup(page.exceptions.sortedBy { it.nameWithOuterClass().toLowerCase() }, "Exceptions", headerAsRow = false) { classLikeRow(it) } |
| this@composePage.summaryNodeGroup(page.typeAliases.sortedBy { it.nameWithOuterClass().toLowerCase() }, "Type-aliases", headerAsRow = false) { classLikeRow(it) } |
| this@composePage.summaryNodeGroup(page.annotations.sortedBy { it.nameWithOuterClass().toLowerCase() }, "Annotations", headerAsRow = false) { classLikeRow(it) } |
| this@composePage.summaryNodeGroup(page.enums.sortedBy { it.nameWithOuterClass().toLowerCase() }, "Enums", headerAsRow = false) { classLikeRow(it) } |
| |
| this@composePage.summaryNodeGroup( |
| page.constants.sortedBy { it.name }, |
| "Top-level constants summary", |
| headerAsRow = false |
| ) { |
| propertyLikeSummaryRow(it) |
| } |
| |
| this@composePage.summaryNodeGroup( |
| page.functions.sortedBy { it.name }, |
| "Top-level functions summary", |
| headerAsRow = false |
| ) { |
| functionLikeSummaryRow(it) |
| } |
| |
| this@composePage.summaryNodeGroup( |
| page.properties.sortedBy { it.name }, |
| "Top-level properties summary", |
| headerAsRow = false |
| ) { |
| propertyLikeSummaryRow(it) |
| } |
| |
| summaryNodeGroupForExtensions("Extension functions summary", page.extensionFunctions.entries) |
| summaryNodeGroupForExtensions("Extension properties summary", page.extensionProperties.entries) |
| |
| fullMemberDocs(page.constants.sortedBy { it.name }, "Top-level constants") |
| fullMemberDocs(page.functions.sortedBy { it.name }, "Top-level functions") |
| fullMemberDocs(page.properties.sortedBy { it.name }, "Top-level properties") |
| fullMemberDocs(page.extensionFunctions.values.flatten().sortedBy { it.name }, "Extension functions") |
| fullMemberDocs(page.extensionProperties.values.flatten().sortedBy { it.name }, "Extension properties") |
| } |
| ) |
| |
| private fun TBODY.inheritedXmlAttributeRow(inheritedMember: DocumentationNode) { |
| tr(classes = "api apilevel-${inheritedMember.attributeRef!!.apiLevel.name}") { |
| attributes["data-version-added"] = "${inheritedMember.apiLevel}" |
| td { |
| code { |
| a(href = inheritedMember) { +inheritedMember.attributeRef!!.name } |
| } |
| } |
| td { |
| attributes["width"] = "100%" |
| p { |
| nodeContent(inheritedMember.attributeRef!!, inheritedMember) |
| } |
| } |
| } |
| } |
| |
| private fun TBODY.inheritedMemberRow(inheritedMember: DocumentationNode) { |
| tr(classes = "api apilevel-${inheritedMember.apiLevel.name}") { |
| attributes["data-version-added"] = "${inheritedMember.apiLevel}" |
| val type = inheritedMember.detailOrNull(NodeKind.Type) |
| td { |
| code { |
| type?.let { |
| renderedSignature(it, LanguageService.RenderMode.SUMMARY) |
| } |
| } |
| } |
| td { |
| attributes["width"] = "100%" |
| code { |
| a(href = inheritedMember) { +inheritedMember.name } |
| if (inheritedMember.kind == NodeKind.Function) { |
| shortFunctionParametersList(inheritedMember) |
| } |
| } |
| p { |
| nodeContent(inheritedMember) |
| } |
| } |
| } |
| } |
| |
| private fun FlowContent.expandableSummaryNodeGroupForInheritedMembers( |
| tableId: String, |
| header: String, |
| tableClass: String, |
| superClasses: Set<Map.Entry<DocumentationNode, List<DocumentationNode>>>, |
| row: TBODY.(inheritedMember: DocumentationNode) -> Unit |
| ) { |
| if (superClasses.none()) return |
| table(classes = tableClass) { |
| attributes["id"] = tableId |
| tbody { |
| developerHeading(header) |
| superClasses.forEach { (superClass, members) -> |
| tr(classes = "api apilevel-${superClass.apiLevel.name}") { |
| td { |
| attributes["colSpan"] = "2" |
| div(classes = "expandable jd-inherited-apis") { |
| span(classes = "expand-control exw-expanded") { |
| +"From class " |
| code { |
| a(href = superClass) { +superClass.name } |
| } |
| } |
| table(classes = "responsive exw-expanded-content") { |
| tbody { |
| members.forEach { inheritedMember -> |
| row(inheritedMember) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private fun FlowContent.summaryNodeGroupForExtensions( |
| header: String, |
| receivers: Set<Map.Entry<DocumentationNode, List<DocumentationNode>>> |
| ) { |
| if (receivers.none()) return |
| h2 { +header } |
| div { |
| receivers.forEach { |
| table { |
| tr { |
| td { |
| attributes["colSpan"] = "2" |
| +"For " |
| a(href = it.key) { +it.key.name } |
| } |
| } |
| it.value.forEach { node -> |
| tr { |
| if (node.kind != NodeKind.Constructor) { |
| td { |
| modifiers(node) |
| renderedSignature(node.detail(NodeKind.Type), LanguageService.RenderMode.SUMMARY) |
| } |
| } |
| td { |
| div { |
| code { |
| val receiver = node.detailOrNull(NodeKind.Receiver) |
| if (receiver != null) { |
| renderedSignature(receiver.detail(NodeKind.Type), LanguageService.RenderMode.SUMMARY) |
| +"." |
| } |
| a(href = node) { +node.name } |
| shortFunctionParametersList(node) |
| } |
| } |
| |
| nodeSummary(node) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| override fun generatePackageIndex(page: Page.PackageIndex) = templateService.composePage( |
| page, |
| htmlConsumer, |
| headContent = { |
| |
| }, |
| bodyContent = { |
| h1 { +"Package Index" } |
| table { |
| tbody { |
| for (node in page.packages) { |
| tr { |
| td { |
| a(href = uriProvider.linkTo(node, uri)) { +node.name } |
| } |
| } |
| } |
| } |
| } |
| } |
| ) |
| |
| override fun generateClassIndex(page: Page.ClassIndex) = templateService.composePage( |
| page, |
| htmlConsumer, |
| headContent = { |
| |
| }, |
| bodyContent = { |
| h1 { +"Class Index" } |
| |
| p { |
| +"These are all the API classes. See all " |
| a(href="packages.html") { |
| +"API packages." |
| } |
| } |
| |
| div(classes = "jd-letterlist") { |
| page.classesByFirstLetter.forEach { (letter) -> |
| +"\n " |
| a(href = "#letter_$letter") { +letter } |
| unsafe { |
| raw(" ") |
| } |
| } |
| +"\n " |
| } |
| |
| page.classesByFirstLetter.forEach { (letter, classes) -> |
| h2 { |
| id = "letter_$letter" |
| +letter |
| } |
| table { |
| tbody { |
| for (node in classes) { |
| tr { |
| td { |
| a(href = uriProvider.linkTo(node, uri)) { +node.classNodeNameWithOuterClass() } |
| } |
| td { |
| if (!deprecatedIndexSummary(node)) { |
| nodeSummary(node) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| ) |
| |
| override fun FlowContent.classHierarchy(superclasses: List<DocumentationNode>) { |
| table(classes = "jd-inheritance-table") { |
| var level = superclasses.size |
| superclasses.forEach { |
| tr { |
| var spaceColumns = max(superclasses.size - 1 - level, 0) |
| while (spaceColumns > 0) { |
| td(classes = "jd-inheritance-space") { |
| +" " |
| } |
| spaceColumns-- |
| } |
| if (it != superclasses.first()) { |
| td(classes = "jd-inheritance-space") { |
| +"   ↳" |
| } |
| } |
| td(classes = "jd-inheritance-class-cell") { |
| attributes["colSpan"] = "$level" |
| qualifiedTypeReference(it) |
| } |
| } |
| level-- |
| } |
| } |
| } |
| |
| override fun FlowContent.subclasses(inheritors: List<DocumentationNode>, direct: Boolean) { |
| if (inheritors.isEmpty()) return |
| |
| // The number of subclasses in collapsed view before truncating and adding a "and xx others". |
| // See https://developer.android.com/reference/android/view/View for an example. |
| val numToShow = 12 |
| |
| table(classes = "jd-sumtable jd-sumtable-subclasses") { |
| tbody { |
| tr { |
| td { |
| div(classes = "expandable") { |
| span(classes = "expand-control") { |
| if (direct) |
| +"Known Direct Subclasses" |
| else |
| +"Known Indirect Subclasses" |
| } |
| div(classes = "showalways") { |
| attributes["id"] = if (direct) "subclasses-direct" else "subclasses-indirect" |
| |
| inheritors.take(numToShow).forEach { inheritor -> |
| a(href = inheritor) { +inheritor.classNodeNameWithOuterClass() } |
| if (inheritor != inheritors.last()) +", " |
| } |
| |
| if (inheritors.size > numToShow) { |
| +"and ${inheritors.size - numToShow} others." |
| } |
| } |
| div(classes = "exw-expanded-content") { |
| attributes["id"] = if (direct) "subclasses-direct-summary" else "subclasses-indirect-summary" |
| table(classes = "jd-sumtable-expando") { |
| inheritors.forEach { inheritor -> |
| tr(classes = "api api-level-${inheritor.apiLevel.name}") { |
| attributes["data-version-added"] = inheritor.apiLevel.name |
| td(classes = "jd-linkcol") { |
| a(href = inheritor) { +inheritor.classNodeNameWithOuterClass() } |
| } |
| td(classes = "jd-descrcol") { |
| attributes["width"] = "100%" |
| nodeSummary(inheritor) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| fun DocumentationNode.firstSentenceOfSummary(): ContentNode { |
| |
| fun Sequence<ContentNode>.flatten(): Sequence<ContentNode> { |
| return flatMap { |
| when (it) { |
| is ContentParagraph -> it.children.asSequence().flatten() |
| else -> sequenceOf(it) |
| } |
| } |
| } |
| |
| fun ContentNode.firstSentence(): ContentText? = when(this) { |
| is ContentText -> ContentText(text.firstSentence()) |
| else -> null |
| } |
| |
| val elements = sequenceOf(summary).flatten() |
| fun containsDot(it: ContentNode) = (it as? ContentText)?.text?.contains(".") == true |
| |
| val paragraph = ContentParagraph() |
| (elements.takeWhile { !containsDot(it) } + elements.firstOrNull { containsDot(it) }?.firstSentence()).forEach { |
| if (it != null) { |
| paragraph.append(it) |
| } |
| } |
| if (paragraph.isEmpty()) { |
| return ContentEmpty |
| } |
| |
| return paragraph |
| } |
| |
| fun DocumentationNode.firstSentence(): String { |
| val sb = StringBuilder() |
| addContentNodeToStringBuilder(content, sb) |
| return sb.toString().firstSentence() |
| } |
| |
| private fun addContentNodesToStringBuilder(content: List<ContentNode>, sb: StringBuilder): Unit = |
| content.forEach { addContentNodeToStringBuilder(it, sb) } |
| |
| private fun addContentNodeToStringBuilder(content: ContentNode, sb: StringBuilder) { |
| when (content) { |
| is ContentText -> sb.appendWith(content.text) |
| is ContentSymbol -> sb.appendWith(content.text) |
| is ContentKeyword -> sb.appendWith(content.text) |
| is ContentIdentifier -> sb.appendWith(content.text) |
| is ContentEntity -> sb.appendWith(content.text) |
| |
| is ContentHeading -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentStrong -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentStrikethrough -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentEmphasis -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentOrderedList -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentUnorderedList -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentListItem -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentCode -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentBlockSampleCode -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentBlockCode -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentParagraph -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentNodeLink -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentBookmark -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentExternalLink -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentLocalLink -> addContentNodesToStringBuilder(content.children, sb) |
| is ContentSection -> { } |
| is ContentBlock -> addContentNodesToStringBuilder(content.children, sb) |
| } |
| } |
| |
| private fun StringBuilder.appendWith(text: String, delimiter: String = " ") { |
| if (this.length == 0) { |
| append(text) |
| } else { |
| append(delimiter) |
| append(text) |
| } |
| } |
| } |
| |
| fun TBODY.developerHeading(header: String) { |
| tr { |
| th { |
| attributes["colSpan"] = "2" |
| +header |
| } |
| } |
| } |
| |
| class DacFormatDescriptor : JavaLayoutHtmlFormatDescriptorBase(), DefaultAnalysisComponentServices by KotlinAsKotlin { |
| override val templateServiceClass: KClass<out JavaLayoutHtmlTemplateService> = DevsiteHtmlTemplateService::class |
| |
| override val outlineFactoryClass = DacOutlineFormatter::class |
| override val languageServiceClass = KotlinLanguageService::class |
| override val packageListServiceClass: KClass<out PackageListService> = JavaLayoutHtmlPackageListService::class |
| override val outputBuilderFactoryClass: KClass<out JavaLayoutHtmlFormatOutputBuilderFactory> = DevsiteLayoutHtmlFormatOutputBuilderFactoryImpl::class |
| override val sampleProcessingService = DevsiteSampleProcessingService::class |
| } |
| |
| |
| class DacAsJavaFormatDescriptor : JavaLayoutHtmlFormatDescriptorBase(), DefaultAnalysisComponentServices by KotlinAsJava { |
| override val templateServiceClass: KClass<out JavaLayoutHtmlTemplateService> = DevsiteHtmlTemplateService::class |
| |
| override val outlineFactoryClass = DacOutlineFormatter::class |
| override val languageServiceClass = NewJavaLanguageService::class |
| override val packageListServiceClass: KClass<out PackageListService> = JavaLayoutHtmlPackageListService::class |
| override val outputBuilderFactoryClass: KClass<out JavaLayoutHtmlFormatOutputBuilderFactory> = DevsiteLayoutHtmlFormatOutputBuilderFactoryImpl::class |
| } |