<lambda>null1 package org.jetbrains.dokka
2
3 import org.jetbrains.dokka.LanguageService.RenderMode
4 import java.util.*
5
6 data class FormatLink(val text: String, val href: String)
7
8 abstract class StructuredOutputBuilder(val to: StringBuilder,
9 val location: Location,
10 val generator: NodeLocationAwareGenerator,
11 val languageService: LanguageService,
12 val extension: String,
13 val impliedPlatforms: List<String>) : FormattedOutputBuilder {
14
15 protected fun DocumentationNode.location() = generator.location(this)
16
17 protected fun wrap(prefix: String, suffix: String, body: () -> Unit) {
18 to.append(prefix)
19 body()
20 to.append(suffix)
21 }
22
23 protected fun wrapIfNotEmpty(prefix: String, suffix: String, body: () -> Unit, checkEndsWith: Boolean = false) {
24 val startLength = to.length
25 to.append(prefix)
26 body()
27 if (checkEndsWith && to.endsWith(suffix)) {
28 to.setLength(to.length - suffix.length)
29 } else if (to.length > startLength + prefix.length) {
30 to.append(suffix)
31 } else {
32 to.setLength(startLength)
33 }
34 }
35
36 protected fun wrapInTag(tag: String,
37 body: () -> Unit,
38 newlineBeforeOpen: Boolean = false,
39 newlineAfterOpen: Boolean = false,
40 newlineAfterClose: Boolean = false) {
41 if (newlineBeforeOpen && !to.endsWith('\n')) to.appendln()
42 to.append("<$tag>")
43 if (newlineAfterOpen) to.appendln()
44 body()
45 to.append("</$tag>")
46 if (newlineAfterClose) to.appendln()
47 }
48
49 protected abstract fun ensureParagraph()
50
51 open fun appendSampleBlockCode(language: String, imports: () -> Unit, body: () -> Unit) = appendBlockCode(language, body)
52 abstract fun appendBlockCode(language: String, body: () -> Unit)
53 abstract fun appendHeader(level: Int = 1, body: () -> Unit)
54 abstract fun appendParagraph(body: () -> Unit)
55
56 open fun appendSoftParagraph(body: () -> Unit) {
57 ensureParagraph()
58 body()
59 }
60
61 abstract fun appendLine()
62 abstract fun appendAnchor(anchor: String)
63
64 abstract fun appendTable(vararg columns: String, body: () -> Unit)
65 abstract fun appendTableBody(body: () -> Unit)
66 abstract fun appendTableRow(body: () -> Unit)
67 abstract fun appendTableCell(body: () -> Unit)
68
69 abstract fun appendText(text: String)
70
71 open fun appendSinceKotlin(version: String) {
72 appendParagraph {
73 appendText("Available since Kotlin: ")
74 appendCode { appendText(version) }
75 }
76 }
77
78 open fun appendSectionWithTag(section: ContentSection) {
79 appendParagraph {
80 appendStrong { appendText(section.tag) }
81 appendLine()
82 appendContent(section)
83 }
84 }
85
86 open fun appendSymbol(text: String) {
87 appendText(text)
88 }
89
90 open fun appendKeyword(text: String) {
91 appendText(text)
92 }
93
94 open fun appendIdentifier(text: String, kind: IdentifierKind, signature: String?) {
95 appendText(text)
96 }
97
98 fun appendEntity(text: String) {
99 to.append(text)
100 }
101
102 abstract fun appendLink(href: String, body: () -> Unit)
103
104 open fun appendLink(link: FormatLink) {
105 appendLink(link.href) { appendText(link.text) }
106 }
107
108 abstract fun appendStrong(body: () -> Unit)
109 abstract fun appendStrikethrough(body: () -> Unit)
110 abstract fun appendEmphasis(body: () -> Unit)
111 abstract fun appendCode(body: () -> Unit)
112 abstract fun appendUnorderedList(body: () -> Unit)
113 abstract fun appendOrderedList(body: () -> Unit)
114 abstract fun appendListItem(body: () -> Unit)
115
116 abstract fun appendBreadcrumbSeparator()
117 abstract fun appendNonBreakingSpace()
118 open fun appendSoftLineBreak() {
119 }
120
121 open fun appendIndentedSoftLineBreak() {
122 }
123
124 fun appendContent(content: List<ContentNode>) {
125 for (contentNode in content) {
126 appendContent(contentNode)
127 }
128 }
129
130 open fun appendContent(content: ContentNode) {
131 when (content) {
132 is ContentText -> appendText(content.text)
133 is ContentSymbol -> appendSymbol(content.text)
134 is ContentKeyword -> appendKeyword(content.text)
135 is ContentIdentifier -> appendIdentifier(content.text, content.kind, content.signature)
136 is ContentNonBreakingSpace -> appendNonBreakingSpace()
137 is ContentSoftLineBreak -> appendSoftLineBreak()
138 is ContentIndentedSoftLineBreak -> appendIndentedSoftLineBreak()
139 is ContentEntity -> appendEntity(content.text)
140 is ContentStrong -> appendStrong { appendContent(content.children) }
141 is ContentStrikethrough -> appendStrikethrough { appendContent(content.children) }
142 is ContentCode -> appendCode { appendContent(content.children) }
143 is ContentEmphasis -> appendEmphasis { appendContent(content.children) }
144 is ContentUnorderedList -> appendUnorderedList { appendContent(content.children) }
145 is ContentOrderedList -> appendOrderedList { appendContent(content.children) }
146 is ContentListItem -> appendListItem {
147 val child = content.children.singleOrNull()
148 if (child is ContentParagraph) {
149 appendContent(child.children)
150 } else {
151 appendContent(content.children)
152 }
153 }
154
155 is ContentNodeLink -> {
156 val node = content.node
157 val linkTo = if (node != null) locationHref(location, node) else "#"
158 appendLinkIfNotThisPage(linkTo, content)
159 }
160 is ContentExternalLink -> appendLinkIfNotThisPage(content.href, content)
161
162 is ContentParagraph -> {
163 if (!content.isEmpty()) {
164 appendParagraph { appendContent(content.children) }
165 }
166 }
167
168 is ContentSpecialReference -> wrapInTag(tag = "aside class=\"note\"", body = {
169 if (!content.isEmpty()) {
170 appendContent(content.children)
171 }
172 })
173
174 is ContentBlockSampleCode, is ContentBlockCode -> {
175 content as ContentBlockCode
176 fun ContentBlockCode.appendBlockCodeContent() {
177 children
178 .dropWhile { it is ContentText && it.text.isBlank() }
179 .forEach { appendContent(it) }
180 }
181 when (content) {
182 is ContentBlockSampleCode ->
183 appendSampleBlockCode(content.language, content.importsBlock::appendBlockCodeContent, { content.appendBlockCodeContent() })
184 is ContentBlockCode ->
185 appendBlockCode(content.language, { content.appendBlockCodeContent() })
186 }
187 }
188 is ContentHeading -> appendHeader(content.level) { appendContent(content.children) }
189 is ContentBlock -> appendContent(content.children)
190 }
191 }
192
193 private fun appendLinkIfNotThisPage(href: String, content: ContentBlock) {
194 if (href == ".") {
195 appendContent(content.children)
196 } else {
197 appendLink(href) { appendContent(content.children) }
198 }
199 }
200
201 open fun link(from: DocumentationNode,
202 to: DocumentationNode,
203 name: (DocumentationNode) -> String = DocumentationNode::name): FormatLink = link(from, to, extension, name)
204
205 open fun link(from: DocumentationNode,
206 to: DocumentationNode,
207 extension: String,
208 name: (DocumentationNode) -> String = DocumentationNode::name): FormatLink {
209 if (to.owner?.kind == NodeKind.GroupNode)
210 return link(from, to.owner!!, extension, name)
211
212 if (from.owner?.kind == NodeKind.GroupNode)
213 return link(from.owner!!, to, extension, name)
214
215 return FormatLink(name(to), from.location().relativePathTo(to.location()))
216 }
217
218 fun locationHref(from: Location, to: DocumentationNode): String {
219 val topLevelPage = to.references(RefKind.TopLevelPage).singleOrNull()?.to
220 if (topLevelPage != null) {
221 val signature = to.detailOrNull(NodeKind.Signature)
222 return from.relativePathTo(topLevelPage.location(), signature?.name ?: to.name)
223 }
224 return from.relativePathTo(to.location())
225 }
226
227 private fun DocumentationNode.isModuleOrPackage(): Boolean =
228 kind == NodeKind.Module || kind == NodeKind.Package
229
230 protected open fun appendAsSignature(node: ContentNode, block: () -> Unit) {
231 block()
232 }
233
234 protected open fun appendAsOverloadGroup(to: StringBuilder, platforms: Set<String>, block: () -> Unit) {
235 block()
236 }
237
238 protected open fun appendIndexRow(platforms: Set<String>, block: () -> Unit) {
239 appendTableRow(block)
240 }
241
242 protected open fun appendPlatforms(platforms: Set<String>) {
243 if (platforms.isNotEmpty()) {
244 appendLine()
245 appendText(platforms.joinToString(prefix = "(", postfix = ")"))
246 }
247 }
248
249 protected open fun appendBreadcrumbs(path: Iterable<FormatLink>) {
250 for ((index, item) in path.withIndex()) {
251 if (index > 0) {
252 appendBreadcrumbSeparator()
253 }
254 appendLink(item)
255 }
256 }
257
258 fun Content.getSectionsWithSubjects(): Map<String, List<ContentSection>> =
259 sections.filter { it.subjectName != null }.groupBy { it.tag }
260
261 private fun ContentNode.appendSignature() {
262 if (this is ContentBlock && this.isEmpty()) {
263 return
264 }
265
266 val signatureAsCode = ContentCode()
267 signatureAsCode.append(this)
268 appendContent(signatureAsCode)
269 }
270
271 open inner class PageBuilder(val nodes: Iterable<DocumentationNode>, val noHeader: Boolean = false) {
272 open fun build() {
273 val breakdownByLocation = nodes.groupBy { node ->
274 node.path.filterNot { it.name.isEmpty() }.map { link(node, it) }.distinct()
275 }
276
277 for ((path, nodes) in breakdownByLocation) {
278 if (!noHeader && path.isNotEmpty()) {
279 appendBreadcrumbs(path)
280 appendLine()
281 appendLine()
282 }
283 appendLocation(nodes.filter { it.kind != NodeKind.ExternalClass })
284 }
285 }
286
287 private fun appendLocation(nodes: Iterable<DocumentationNode>) {
288 val singleNode = nodes.singleOrNull()
289 if (singleNode != null && singleNode.isModuleOrPackage()) {
290 if (singleNode.kind == NodeKind.Package) {
291 val packageName = if (singleNode.name.isEmpty()) "<root>" else singleNode.name
292 appendHeader(2) { appendText("Package $packageName") }
293 }
294 singleNode.appendPlatforms()
295 appendContent(singleNode.content)
296 } else {
297 val breakdownByName = nodes.groupBy { node -> node.name }
298 for ((name, items) in breakdownByName) {
299 if (!noHeader)
300 appendHeader { appendText(name) }
301 appendDocumentation(items, singleNode != null)
302 }
303 }
304 }
305
306 private fun appendDocumentation(overloads: Iterable<DocumentationNode>, isSingleNode: Boolean) {
307 val breakdownBySummary = overloads.groupByTo(LinkedHashMap()) { node -> node.content }
308
309 if (breakdownBySummary.size == 1) {
310 formatOverloadGroup(breakdownBySummary.values.single(), isSingleNode)
311 } else {
312 for ((_, items) in breakdownBySummary) {
313
314 appendAsOverloadGroup(to, platformsOfItems(items)) {
315 formatOverloadGroup(items)
316 }
317
318 }
319 }
320 }
321
322 private fun formatOverloadGroup(items: List<DocumentationNode>, isSingleNode: Boolean = false) {
323 for ((index, item) in items.withIndex()) {
324 if (index > 0) appendLine()
325 val rendered = languageService.render(item)
326 item.detailOrNull(NodeKind.Signature)?.let {
327 if (item.kind !in NodeKind.classLike || !isSingleNode)
328 appendAnchor(it.name)
329 }
330 appendAsSignature(rendered) {
331 appendCode { appendContent(rendered) }
332 item.appendSourceLink()
333 }
334 item.appendOverrides()
335 item.appendDeprecation()
336 item.appendPlatforms()
337 }
338 // All items have exactly the same documentation, so we can use any item to render it
339 val item = items.first()
340 item.details(NodeKind.OverloadGroupNote).forEach {
341 appendContent(it.content)
342 }
343
344 appendContent(item.content.summary)
345 item.appendDescription()
346 }
347
348 private fun DocumentationNode.appendSourceLink() {
349 val sourceUrl = details(NodeKind.SourceUrl).firstOrNull()
350 if (sourceUrl != null) {
351 to.append(" ")
352 appendLink(sourceUrl.name) { to.append("(source)") }
353 }
354 }
355
356 private fun DocumentationNode.appendOverrides() {
357 overrides.forEach {
358 appendParagraph {
359 to.append("Overrides ")
360 val location = location().relativePathTo(it.location())
361
362 appendLink(FormatLink(it.owner!!.name + "." + it.name, location))
363 }
364 }
365 }
366
367 private fun DocumentationNode.appendDeprecation() {
368 if (deprecation != null) {
369 val deprecationParameter = deprecation!!.details(NodeKind.Parameter).firstOrNull()
370 val deprecationValue = deprecationParameter?.details(NodeKind.Value)?.firstOrNull()
371 appendLine()
372 if (deprecationValue != null) {
373 appendStrong { to.append("Deprecated:") }
374 appendText(" " + deprecationValue.name.removeSurrounding("\""))
375 appendLine()
376 appendLine()
377 } else if (deprecation?.content != Content.Empty) {
378 appendStrong { to.append("Deprecated:") }
379 to.append(" ")
380 appendContent(deprecation!!.content)
381 } else {
382 appendStrong { to.append("Deprecated") }
383 appendLine()
384 appendLine()
385 }
386 }
387 }
388
389 private fun DocumentationNode.appendPlatforms() {
390 val platforms = if (isModuleOrPackage())
391 platformsToShow.toSet() + platformsOfItems(members)
392 else
393 platformsToShow
394
395 if (platforms.isEmpty()) return
396
397 appendParagraph {
398 appendStrong { to.append("Platform and version requirements:") }
399 to.append(" " + platforms.joinToString())
400 }
401 }
402
403 protected fun platformsOfItems(items: List<DocumentationNode>): Set<String> {
404 val platforms = items.asSequence().map {
405 when (it.kind) {
406 NodeKind.ExternalClass, NodeKind.Package, NodeKind.Module, NodeKind.GroupNode -> platformsOfItems(it.members)
407 else -> it.platformsToShow.toSet()
408 }
409 }
410
411 fun String.isKotlinVersion() = this.startsWith("Kotlin")
412
413 // Calculating common platforms for items
414 return platforms.reduce { result, platformsOfItem ->
415 val otherKotlinVersion = result.find { it.isKotlinVersion() }
416 val (kotlinVersions, otherPlatforms) = platformsOfItem.partition { it.isKotlinVersion() }
417
418 // When no Kotlin version specified, it means that version is 1.0
419 if (otherKotlinVersion != null && kotlinVersions.isNotEmpty()) {
420 val allKotlinVersions = (kotlinVersions + otherKotlinVersion).distinct()
421
422 val minVersion = allKotlinVersions.min()!!
423 val resultVersion = when {
424 allKotlinVersions.size == 1 -> allKotlinVersions.single()
425 minVersion.endsWith("+") -> minVersion
426 else -> minVersion + "+"
427 }
428
429 result.intersect(otherPlatforms) + resultVersion
430 } else {
431 result.intersect(platformsOfItem)
432 }
433 }
434 }
435
436 val DocumentationNode.platformsToShow: List<String>
437 get() = platforms.let { if (it.containsAll(impliedPlatforms)) it - impliedPlatforms else it }
438
439 private fun DocumentationNode.appendDescription() {
440 if (content.description != ContentEmpty) {
441 appendContent(content.description)
442 }
443 content.getSectionsWithSubjects().forEach {
444 appendSectionWithSubject(it.key, it.value)
445 }
446
447 for (section in content.sections.filter { it.subjectName == null }) {
448 appendSectionWithTag(section)
449 }
450 }
451
452 fun appendSectionWithSubject(title: String, subjectSections: List<ContentSection>) {
453 appendHeader(3) { appendText(title) }
454 subjectSections.forEach {
455 val subjectName = it.subjectName
456 if (subjectName != null) {
457 appendSoftParagraph {
458 appendAnchor(subjectName)
459 appendCode { to.append(subjectName) }
460 to.append(" - ")
461 appendContent(it)
462 }
463 }
464 }
465 }
466 }
467
468 inner class GroupNodePageBuilder(val node: DocumentationNode) : PageBuilder(listOf(node)) {
469
470 override fun build() {
471 val breakdownByLocation = node.path.filterNot { it.name.isEmpty() }.map { link(node, it) }
472
473 appendBreadcrumbs(breakdownByLocation)
474 appendLine()
475 appendLine()
476 appendHeader { appendText(node.name) }
477
478 fun DocumentationNode.priority(): Int = when (kind) {
479 NodeKind.TypeAlias -> 1
480 NodeKind.Class -> 2
481 else -> 3
482 }
483
484 for (member in node.members.sortedBy(DocumentationNode::priority)) {
485
486 appendAsOverloadGroup(to, platformsOfItems(listOf(member))) {
487 formatSubNodeOfGroup(member)
488 }
489
490 }
491 }
492
493 fun formatSubNodeOfGroup(member: DocumentationNode) {
494 SingleNodePageBuilder(member, true).build()
495 }
496 }
497
498 inner class SingleNodePageBuilder(val node: DocumentationNode, noHeader: Boolean = false)
499 : PageBuilder(listOf(node), noHeader) {
500
501 override fun build() {
502 super.build()
503
504 if (node.kind == NodeKind.ExternalClass) {
505 appendSection("Extensions for ${node.name}", node.members)
506 return
507 }
508
509 fun DocumentationNode.membersOrGroupMembers(predicate: (DocumentationNode) -> Boolean): List<DocumentationNode> {
510 return members.filter(predicate) + members(NodeKind.GroupNode).flatMap { it.members.filter(predicate) }
511 }
512
513 fun DocumentationNode.membersOrGroupMembers(kind: NodeKind): List<DocumentationNode> {
514 return membersOrGroupMembers { it.kind == kind }
515 }
516
517 appendSection("Packages", node.members(NodeKind.Package), platformsBasedOnMembers = true)
518 appendSection("Types", node.membersOrGroupMembers { it.kind in NodeKind.classLike && it.kind != NodeKind.TypeAlias && it.kind != NodeKind.AnnotationClass && it.kind != NodeKind.Exception })
519 appendSection("Annotations", node.membersOrGroupMembers(NodeKind.AnnotationClass))
520 appendSection("Exceptions", node.membersOrGroupMembers(NodeKind.Exception))
521 appendSection("Type Aliases", node.membersOrGroupMembers(NodeKind.TypeAlias))
522 appendSection("Extensions for External Classes", node.members(NodeKind.ExternalClass))
523 appendSection("Enum Values", node.members(NodeKind.EnumItem), sortMembers = false, omitSamePlatforms = true)
524 appendSection("Constructors", node.members(NodeKind.Constructor), omitSamePlatforms = true)
525 appendSection("Properties", node.members(NodeKind.Property), omitSamePlatforms = true)
526 appendSection("Inherited Properties", node.inheritedMembers(NodeKind.Property))
527 appendSection("Functions", node.members(NodeKind.Function), omitSamePlatforms = true)
528 appendSection("Inherited Functions", node.inheritedMembers(NodeKind.Function))
529 appendSection("Companion Object Properties", node.members(NodeKind.CompanionObjectProperty), omitSamePlatforms = true)
530 appendSection("Inherited Companion Object Properties", node.inheritedCompanionObjectMembers(NodeKind.Property))
531 appendSection("Companion Object Functions", node.members(NodeKind.CompanionObjectFunction), omitSamePlatforms = true)
532 appendSection("Inherited Companion Object Functions", node.inheritedCompanionObjectMembers(NodeKind.Function))
533 appendSection("Other members", node.members.filter {
534 it.kind !in setOf(
535 NodeKind.Class,
536 NodeKind.Interface,
537 NodeKind.Enum,
538 NodeKind.Object,
539 NodeKind.AnnotationClass,
540 NodeKind.Exception,
541 NodeKind.TypeAlias,
542 NodeKind.Constructor,
543 NodeKind.Property,
544 NodeKind.Package,
545 NodeKind.Function,
546 NodeKind.CompanionObjectProperty,
547 NodeKind.CompanionObjectFunction,
548 NodeKind.ExternalClass,
549 NodeKind.EnumItem,
550 NodeKind.AllTypes,
551 NodeKind.GroupNode
552 )
553 })
554
555 val allExtensions = node.extensions
556 appendSection("Extension Properties", allExtensions.filter { it.kind == NodeKind.Property })
557 appendSection("Extension Functions", allExtensions.filter { it.kind == NodeKind.Function })
558 appendSection("Companion Object Extension Properties", allExtensions.filter { it.kind == NodeKind.CompanionObjectProperty })
559 appendSection("Companion Object Extension Functions", allExtensions.filter { it.kind == NodeKind.CompanionObjectFunction })
560 appendSection("Inheritors",
561 node.inheritors.filter { it.kind != NodeKind.EnumItem })
562
563 if (node.kind == NodeKind.Module) {
564 appendHeader(3) { to.append("Index") }
565 node.members(NodeKind.AllTypes).singleOrNull()?.let { allTypes ->
566 appendLink(link(node, allTypes, { "All Types" }))
567 }
568 }
569 }
570
571 private fun appendSection(caption: String, members: List<DocumentationNode>,
572 sortMembers: Boolean = true,
573 omitSamePlatforms: Boolean = false,
574 platformsBasedOnMembers: Boolean = false) {
575 if (members.isEmpty()) return
576
577 appendHeader(3) { appendText(caption) }
578
579 val children = if (sortMembers) members.sortedBy { it.name.toLowerCase() } else members
580 val membersMap = children.groupBy { link(node, it) }
581
582
583
584 appendTable("Name", "Summary") {
585 appendTableBody {
586 for ((memberLocation, members) in membersMap) {
587 val elementPlatforms = platformsOfItems(members, omitSamePlatforms)
588 val platforms = if (platformsBasedOnMembers)
589 members.flatMapTo(mutableSetOf()) { platformsOfItems(it.members) } + elementPlatforms
590 else
591 elementPlatforms
592 appendIndexRow(platforms) {
593 appendTableCell {
594 appendParagraph {
595 appendLink(memberLocation)
596 if (members.singleOrNull()?.kind != NodeKind.ExternalClass) {
597 appendPlatforms(platforms)
598 }
599 }
600 }
601 appendTableCell {
602 val breakdownBySummary = members.groupBy { it.summary }
603 for ((summary, items) in breakdownBySummary) {
604 appendSummarySignatures(items)
605 appendContent(summary)
606 }
607 }
608 }
609 }
610 }
611 }
612 }
613
614 private fun platformsOfItems(items: List<DocumentationNode>, omitSamePlatforms: Boolean = true): Set<String> {
615 val platforms = platformsOfItems(items)
616 if (platforms.isNotEmpty() && (platforms != node.platformsToShow.toSet() || !omitSamePlatforms)) {
617 return platforms
618 }
619 return emptySet()
620 }
621
622 private fun appendSummarySignatures(items: List<DocumentationNode>) {
623 val summarySignature = languageService.summarizeSignatures(items)
624 if (summarySignature != null) {
625 appendAsSignature(summarySignature) {
626 summarySignature.appendSignature()
627 }
628 return
629 }
630 val renderedSignatures = items.map { languageService.render(it, RenderMode.SUMMARY) }
631 renderedSignatures.subList(0, renderedSignatures.size - 1).forEach {
632 appendAsSignature(it) {
633 it.appendSignature()
634 }
635 appendLine()
636 }
637 appendAsSignature(renderedSignatures.last()) {
638 renderedSignatures.last().appendSignature()
639 }
640 }
641 }
642
643 inner class AllTypesNodeBuilder(val node: DocumentationNode)
644 : PageBuilder(listOf(node)) {
645
646 override fun build() {
647 appendContent(node.owner!!.summary)
648 appendHeader(3) { to.append("All Types") }
649
650 appendTable("Name", "Summary") {
651 appendTableBody {
652 for (type in node.members) {
653 appendTableRow {
654 appendTableCell {
655 appendLink(link(node, type) {
656 if (it.kind == NodeKind.ExternalClass) it.name else it.qualifiedName()
657 })
658 if (type.kind == NodeKind.ExternalClass) {
659 val packageName = type.owner?.name
660 if (packageName != null) {
661 appendText(" (extensions in package $packageName)")
662 }
663 }
664 }
665 appendTableCell {
666 appendContent(type.summary)
667 }
668 }
669 }
670 }
671 }
672 }
673 }
674
675 override fun appendNodes(nodes: Iterable<DocumentationNode>) {
676 val singleNode = nodes.singleOrNull()
677 when (singleNode?.kind) {
678 NodeKind.AllTypes -> AllTypesNodeBuilder(singleNode).build()
679 NodeKind.GroupNode -> GroupNodePageBuilder(singleNode).build()
680 null -> PageBuilder(nodes).build()
681 else -> SingleNodePageBuilder(singleNode).build()
682 }
683 }
684 }
685
686 abstract class StructuredFormatService(val generator: NodeLocationAwareGenerator,
687 val languageService: LanguageService,
688 override val extension: String,
689 override final val linkExtension: String = extension) : FormatService {
690
691 }
692