blob: 2131d65d4d73a7f5d129bb3712f23f83d113fde2 [file] [log] [blame]
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +04001package org.jetbrains.dokka
2
3import java.util.LinkedHashMap
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +04004
5public data class FormatLink(val text: String, val location: Location)
6
7public abstract class StructuredFormatService(val locationService: LocationService,
Ilya Ryzhenkov499d0822014-07-15 16:18:53 +04008 val languageService: LanguageService) : FormatService {
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +04009
10 abstract public fun appendBlockCode(to: StringBuilder, line: String)
11 abstract public fun appendBlockCode(to: StringBuilder, lines: Iterable<String>)
12 abstract public fun appendHeader(to: StringBuilder, text: String, level: Int = 1)
Dmitry Jemerov8ef68182014-12-30 12:36:14 +010013 abstract public fun appendParagraph(to: StringBuilder, text: String)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040014 abstract public fun appendLine(to: StringBuilder, text: String)
15 public abstract fun appendLine(to: StringBuilder)
Ilya Ryzhenkov499d0822014-07-15 16:18:53 +040016
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +040017 public abstract fun appendTable(to: StringBuilder, body: () -> Unit)
18 public abstract fun appendTableHeader(to: StringBuilder, body: () -> Unit)
19 public abstract fun appendTableBody(to: StringBuilder, body: () -> Unit)
20 public abstract fun appendTableRow(to: StringBuilder, body: () -> Unit)
21 public abstract fun appendTableCell(to: StringBuilder, body: () -> Unit)
Ilya Ryzhenkov499d0822014-07-15 16:18:53 +040022
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +040023 public abstract fun formatText(text: String): String
Ilya Ryzhenkov7c6da4b2014-10-03 19:09:31 +040024 public abstract fun formatSymbol(text: String): String
25 public abstract fun formatKeyword(text: String): String
26 public abstract fun formatIdentifier(text: String): String
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +040027 public abstract fun formatLink(text: String, location: Location): String
Ilya Ryzhenkov71cd87e2014-10-03 22:51:44 +040028 public abstract fun formatLink(text: String, href: String): String
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +040029 public open fun formatLink(link: FormatLink): String = formatLink(formatText(link.text), link.location)
Ilya Ryzhenkov9f0ff552014-10-13 13:38:40 +040030 public abstract fun formatStrong(text: String): String
31 public abstract fun formatEmphasis(text: String): String
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040032 public abstract fun formatCode(code: String): String
Ilya Ryzhenkov18399492014-12-22 09:50:17 +020033 public abstract fun formatList(text: String): String
34 public abstract fun formatListItem(text: String): String
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040035 public abstract fun formatBreadcrumbs(items: Iterable<FormatLink>): String
36
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040037 open fun formatText(location: Location, nodes: Iterable<ContentNode>): String {
38 return nodes.map { formatText(location, it) }.join("")
Ilya Ryzhenkov778e2b32014-09-29 20:54:59 +040039 }
40
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040041 open fun formatText(location: Location, content: ContentNode): String {
Ilya Ryzhenkov455d74a2014-09-19 22:25:27 +030042 return StringBuilder {
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040043 when (content) {
Dmitry Jemerov8ef68182014-12-30 12:36:14 +010044 is ContentText -> append(formatText(content.text))
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040045 is ContentSymbol -> append(formatSymbol(content.text))
46 is ContentKeyword -> append(formatKeyword(content.text))
47 is ContentIdentifier -> append(formatIdentifier(content.text))
Ilya Ryzhenkov9f0ff552014-10-13 13:38:40 +040048 is ContentStrong -> append(formatStrong(formatText(location, content.children)))
49 is ContentCode -> append(formatCode(formatText(location, content.children)))
50 is ContentEmphasis -> append(formatEmphasis(formatText(location, content.children)))
Ilya Ryzhenkov18399492014-12-22 09:50:17 +020051 is ContentList -> append(formatList(formatText(location, content.children)))
52 is ContentListItem -> append(formatListItem(formatText(location, content.children)))
53
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040054 is ContentNodeLink -> {
55 val linkTo = locationService.relativeLocation(location, content.node, extension)
56 val linkText = formatText(location, content.children)
57 append(formatLink(linkText, linkTo))
58 }
Ilya Ryzhenkov71cd87e2014-10-03 22:51:44 +040059 is ContentExternalLink -> {
60 val linkText = formatText(location, content.children)
61 append(formatLink(linkText, content.href))
62 }
Ilya Ryzhenkovad14ea92014-10-13 18:22:02 +040063 is ContentParagraph -> {
Dmitry Jemerov8ef68182014-12-30 12:36:14 +010064 appendParagraph(this, formatText(location, content.children))
Ilya Ryzhenkovad14ea92014-10-13 18:22:02 +040065 }
Ilya Ryzhenkov1cb3af92014-10-13 20:14:45 +040066 is ContentBlockCode -> {
67 appendBlockCode(this, formatText(location, content.children))
68 }
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040069 else -> append(formatText(location, content.children))
Ilya Ryzhenkov455d74a2014-09-19 22:25:27 +030070 }
71 }.toString()
72 }
73
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040074 open public fun link(from: DocumentationNode, to: DocumentationNode): FormatLink = link(from, to, extension)
75
76 open public fun link(from: DocumentationNode, to: DocumentationNode, extension: String): FormatLink {
77 return FormatLink(to.name, locationService.relativeLocation(from, to, extension))
78 }
79
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040080 fun appendDescription(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
Dmitry Jemerovc43a4372014-12-29 20:22:43 +010081 val described = nodes.filter { it.hasDescription() }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040082 if (described.any()) {
Dmitry Jemerovc43a4372014-12-29 20:22:43 +010083 val single = described.size() == 1
Ilya Ryzhenkovfb41c692014-07-15 18:23:15 +040084 appendHeader(to, "Description", 3)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040085 for (node in described) {
Ilya Ryzhenkovfb41c692014-07-15 18:23:15 +040086 if (!single) {
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040087 appendBlockCode(to, formatText(location, languageService.render(node)))
Ilya Ryzhenkovfb41c692014-07-15 18:23:15 +040088 }
Ilya Ryzhenkov49077362014-10-14 19:53:13 +040089 appendLine(to, formatText(location, node.content.description))
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040090 appendLine(to)
Ilya Ryzhenkov280dc292014-10-14 16:08:10 +040091 for ((label, section) in node.content.sections) {
Dmitry Jemerovc43a4372014-12-29 20:22:43 +010092 if (!isDescriptionSection(label, node)) continue
Ilya Ryzhenkov9f0ff552014-10-13 13:38:40 +040093 appendLine(to, formatStrong(formatText(label)))
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040094 appendLine(to, formatText(location, section))
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040095 }
96 }
97 }
98 }
99
Dmitry Jemerovc43a4372014-12-29 20:22:43 +0100100 private fun DocumentationNode.hasDescription() =
101 content.description != ContentEmpty || content.sections.any { isDescriptionSection(it.key, this) }
102
103 private fun isDescriptionSection(label: String, node: DocumentationNode): Boolean {
104 if (label.startsWith("$"))
105 return false
106 if (node.members.any { it.name == label })
107 return false
108 return true
109 }
110
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400111 fun appendSummary(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400112 val breakdownBySummary = nodes.groupByTo(LinkedHashMap()) { node ->
Ilya Ryzhenkov280dc292014-10-14 16:08:10 +0400113 formatText(location, node.summary)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400114 }
115
116 for ((summary, items) in breakdownBySummary) {
Ilya Ryzhenkov7c6da4b2014-10-03 19:09:31 +0400117 items.forEach {
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400118 appendBlockCode(to, formatText(location, languageService.render(it)))
Ilya Ryzhenkov7c6da4b2014-10-03 19:09:31 +0400119 }
Ilya Ryzhenkov280dc292014-10-14 16:08:10 +0400120 appendLine(to, summary)
Ilya Ryzhenkov455d74a2014-09-19 22:25:27 +0300121 appendLine(to)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400122 }
123 }
124
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400125 fun appendLocation(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
Ilya Ryzhenkovbd494a82014-08-21 19:53:36 +0400126 val breakdownByName = nodes.groupBy { node -> node.name }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400127 for ((name, items) in breakdownByName) {
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +0400128 appendHeader(to, formatText(name))
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400129 appendSummary(location, to, items)
130 appendDescription(location, to, items)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400131 }
132 }
133
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400134 private fun StructuredFormatService.appendSection(location: Location, caption: String, nodes: List<DocumentationNode>, node: DocumentationNode, to: StringBuilder) {
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400135 if (nodes.any()) {
136 appendHeader(to, caption, 3)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400137
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400138 val children = nodes.sortBy { it.name }
139 val membersMap = children.groupBy { link(node, it) }
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +0400140
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400141 appendTable(to) {
142 appendTableBody(to) {
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400143 for ((memberLocation, members) in membersMap) {
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400144 appendTableRow(to) {
145 appendTableCell(to) {
Dmitry Jemerov8ef68182014-12-30 12:36:14 +0100146 to.append(formatLink(memberLocation))
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400147 }
148 appendTableCell(to) {
Ilya Ryzhenkov280dc292014-10-14 16:08:10 +0400149 val breakdownBySummary = members.groupBy { formatText(location, it.summary) }
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400150 for ((summary, items) in breakdownBySummary) {
Ilya Ryzhenkov7c6da4b2014-10-03 19:09:31 +0400151 for (signature in items) {
Dmitry Jemerov8ef68182014-12-30 12:36:14 +0100152 val signature = languageService.render(signature)
153 val signatureAsCode = ContentCode()
154 signatureAsCode.append(signature)
155 to.append(formatText(location, signatureAsCode))
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400156 }
157
158 if (!summary.isEmpty()) {
Dmitry Jemerov8ef68182014-12-30 12:36:14 +0100159 to.append(summary)
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +0400160 }
161 }
162 }
Ilya Ryzhenkove8447fd2014-07-15 16:37:50 +0400163 }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400164 }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400165 }
166 }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400167 }
168 }
169
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400170 override fun appendNodes(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
171 val breakdownByLocation = nodes.groupBy { node ->
172 formatBreadcrumbs(node.path.map { link(node, it) })
173 }
174
175 for ((breadcrumbs, items) in breakdownByLocation) {
176 appendLine(to, breadcrumbs)
177 appendLine(to)
178 appendLocation(location, to, items)
179 }
180
181 for (node in nodes) {
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400182 appendSection(location, "Packages", node.members(DocumentationNode.Kind.Package), node, to)
183 appendSection(location, "Types", node.members.filter {
184 it.kind in setOf(
185 DocumentationNode.Kind.Class,
186 DocumentationNode.Kind.Interface,
187 DocumentationNode.Kind.Enum,
188 DocumentationNode.Kind.Object)
189 }, node, to)
190 appendSection(location, "Constructors", node.members(DocumentationNode.Kind.Constructor), node, to)
191 appendSection(location, "Properties", node.members(DocumentationNode.Kind.Property), node, to)
192 appendSection(location, "Functions", node.members(DocumentationNode.Kind.Function), node, to)
Dmitry Jemerovcedaeb42014-12-29 20:50:26 +0100193 appendSection(location, "Class Object Properties", node.members(DocumentationNode.Kind.ClassObjectProperty), node, to)
194 appendSection(location, "Class Object Functions", node.members(DocumentationNode.Kind.ClassObjectFunction), node, to)
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400195 appendSection(location, "Accessors", node.members(DocumentationNode.Kind.PropertyAccessor), node, to)
196 appendSection(location, "Other members", node.members.filter {
197 it.kind !in setOf(
198 DocumentationNode.Kind.Class,
199 DocumentationNode.Kind.Interface,
200 DocumentationNode.Kind.Object,
201 DocumentationNode.Kind.Constructor,
202 DocumentationNode.Kind.Property,
203 DocumentationNode.Kind.Package,
204 DocumentationNode.Kind.Function,
Dmitry Jemerovcedaeb42014-12-29 20:50:26 +0100205 DocumentationNode.Kind.PropertyAccessor,
206 DocumentationNode.Kind.ClassObjectProperty,
207 DocumentationNode.Kind.ClassObjectFunction
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400208 )
209 }, node, to)
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400210 appendSection(location, "Extensions", node.extensions, node, to)
211 appendSection(location, "Inheritors", node.inheritors, node, to)
212 appendSection(location, "Links", node.links, node, to)
213
214 }
215 }
216
Ilya Ryzhenkov499d0822014-07-15 16:18:53 +0400217 abstract public fun appendOutlineHeader(to: StringBuilder, node: DocumentationNode)
218 abstract public fun appendOutlineChildren(to: StringBuilder, nodes: Iterable<DocumentationNode>)
219
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400220 public override fun appendOutline(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
Ilya Ryzhenkov499d0822014-07-15 16:18:53 +0400221 for (node in nodes) {
222 appendOutlineHeader(to, node)
223 if (node.members.any()) {
224 appendOutlineChildren(to, node.members)
225 }
226 }
227 }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400228}