blob: 458fda35040d9a70a6d8adb3de8fe7808963b9d7 [file] [log] [blame]
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +04001package org.jetbrains.dokka
2
3import java.util.LinkedHashMap
Dmitry Jemerov0c584d02015-01-12 17:23:07 +01004import org.jetbrains.dokka.LanguageService.RenderMode
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +04005
6public data class FormatLink(val text: String, val location: Location)
7
8public abstract class StructuredFormatService(val locationService: LocationService,
Ilya Ryzhenkov499d0822014-07-15 16:18:53 +04009 val languageService: LanguageService) : FormatService {
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040010
11 abstract public fun appendBlockCode(to: StringBuilder, line: String)
12 abstract public fun appendBlockCode(to: StringBuilder, lines: Iterable<String>)
13 abstract public fun appendHeader(to: StringBuilder, text: String, level: Int = 1)
Dmitry Jemerov8ef68182014-12-30 12:36:14 +010014 abstract public fun appendParagraph(to: StringBuilder, text: String)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040015 abstract public fun appendLine(to: StringBuilder, text: String)
16 public abstract fun appendLine(to: StringBuilder)
Ilya Ryzhenkov499d0822014-07-15 16:18:53 +040017
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +040018 public abstract fun appendTable(to: StringBuilder, body: () -> Unit)
19 public abstract fun appendTableHeader(to: StringBuilder, body: () -> Unit)
20 public abstract fun appendTableBody(to: StringBuilder, body: () -> Unit)
21 public abstract fun appendTableRow(to: StringBuilder, body: () -> Unit)
22 public abstract fun appendTableCell(to: StringBuilder, body: () -> Unit)
Ilya Ryzhenkov499d0822014-07-15 16:18:53 +040023
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +040024 public abstract fun formatText(text: String): String
Ilya Ryzhenkov7c6da4b2014-10-03 19:09:31 +040025 public abstract fun formatSymbol(text: String): String
26 public abstract fun formatKeyword(text: String): String
27 public abstract fun formatIdentifier(text: String): String
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +040028 public abstract fun formatLink(text: String, location: Location): String
Ilya Ryzhenkov71cd87e2014-10-03 22:51:44 +040029 public abstract fun formatLink(text: String, href: String): String
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +040030 public open fun formatLink(link: FormatLink): String = formatLink(formatText(link.text), link.location)
Ilya Ryzhenkov9f0ff552014-10-13 13:38:40 +040031 public abstract fun formatStrong(text: String): String
Dmitry Jemerove17eaa52015-01-09 20:59:58 +010032 public abstract fun formatStrikethrough(text: String): String
Ilya Ryzhenkov9f0ff552014-10-13 13:38:40 +040033 public abstract fun formatEmphasis(text: String): String
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040034 public abstract fun formatCode(code: String): String
Ilya Ryzhenkov18399492014-12-22 09:50:17 +020035 public abstract fun formatList(text: String): String
36 public abstract fun formatListItem(text: String): String
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040037 public abstract fun formatBreadcrumbs(items: Iterable<FormatLink>): String
38
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040039 open fun formatText(location: Location, nodes: Iterable<ContentNode>): String {
40 return nodes.map { formatText(location, it) }.join("")
Ilya Ryzhenkov778e2b32014-09-29 20:54:59 +040041 }
42
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040043 open fun formatText(location: Location, content: ContentNode): String {
Ilya Ryzhenkov455d74a2014-09-19 22:25:27 +030044 return StringBuilder {
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040045 when (content) {
Dmitry Jemerov8ef68182014-12-30 12:36:14 +010046 is ContentText -> append(formatText(content.text))
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040047 is ContentSymbol -> append(formatSymbol(content.text))
48 is ContentKeyword -> append(formatKeyword(content.text))
49 is ContentIdentifier -> append(formatIdentifier(content.text))
Ilya Ryzhenkov9f0ff552014-10-13 13:38:40 +040050 is ContentStrong -> append(formatStrong(formatText(location, content.children)))
Dmitry Jemerove17eaa52015-01-09 20:59:58 +010051 is ContentStrikethrough -> append(formatStrikethrough(formatText(location, content.children)))
Ilya Ryzhenkov9f0ff552014-10-13 13:38:40 +040052 is ContentCode -> append(formatCode(formatText(location, content.children)))
53 is ContentEmphasis -> append(formatEmphasis(formatText(location, content.children)))
Ilya Ryzhenkov18399492014-12-22 09:50:17 +020054 is ContentList -> append(formatList(formatText(location, content.children)))
55 is ContentListItem -> append(formatListItem(formatText(location, content.children)))
56
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040057 is ContentNodeLink -> {
58 val linkTo = locationService.relativeLocation(location, content.node, extension)
59 val linkText = formatText(location, content.children)
60 append(formatLink(linkText, linkTo))
61 }
Ilya Ryzhenkov71cd87e2014-10-03 22:51:44 +040062 is ContentExternalLink -> {
63 val linkText = formatText(location, content.children)
64 append(formatLink(linkText, content.href))
65 }
Ilya Ryzhenkovad14ea92014-10-13 18:22:02 +040066 is ContentParagraph -> {
Dmitry Jemerov8ef68182014-12-30 12:36:14 +010067 appendParagraph(this, formatText(location, content.children))
Ilya Ryzhenkovad14ea92014-10-13 18:22:02 +040068 }
Ilya Ryzhenkov1cb3af92014-10-13 20:14:45 +040069 is ContentBlockCode -> {
70 appendBlockCode(this, formatText(location, content.children))
71 }
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040072 else -> append(formatText(location, content.children))
Ilya Ryzhenkov455d74a2014-09-19 22:25:27 +030073 }
74 }.toString()
75 }
76
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040077 open public fun link(from: DocumentationNode, to: DocumentationNode): FormatLink = link(from, to, extension)
78
79 open public fun link(from: DocumentationNode, to: DocumentationNode, extension: String): FormatLink {
80 return FormatLink(to.name, locationService.relativeLocation(from, to, extension))
81 }
82
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040083 fun appendDescription(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
Dmitry Jemerovc43a4372014-12-29 20:22:43 +010084 val described = nodes.filter { it.hasDescription() }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040085 if (described.any()) {
Dmitry Jemerovc43a4372014-12-29 20:22:43 +010086 val single = described.size() == 1
Ilya Ryzhenkovfb41c692014-07-15 18:23:15 +040087 appendHeader(to, "Description", 3)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040088 for (node in described) {
Ilya Ryzhenkovfb41c692014-07-15 18:23:15 +040089 if (!single) {
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040090 appendBlockCode(to, formatText(location, languageService.render(node)))
Ilya Ryzhenkovfb41c692014-07-15 18:23:15 +040091 }
Ilya Ryzhenkov49077362014-10-14 19:53:13 +040092 appendLine(to, formatText(location, node.content.description))
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040093 appendLine(to)
Ilya Ryzhenkov280dc292014-10-14 16:08:10 +040094 for ((label, section) in node.content.sections) {
Dmitry Jemerovc43a4372014-12-29 20:22:43 +010095 if (!isDescriptionSection(label, node)) continue
Ilya Ryzhenkov9f0ff552014-10-13 13:38:40 +040096 appendLine(to, formatStrong(formatText(label)))
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040097 appendLine(to, formatText(location, section))
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040098 }
99 }
100 }
101 }
102
Dmitry Jemerovc43a4372014-12-29 20:22:43 +0100103 private fun DocumentationNode.hasDescription() =
104 content.description != ContentEmpty || content.sections.any { isDescriptionSection(it.key, this) }
105
106 private fun isDescriptionSection(label: String, node: DocumentationNode): Boolean {
107 if (label.startsWith("$"))
108 return false
109 if (node.members.any { it.name == label })
110 return false
111 return true
112 }
113
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400114 fun appendSummary(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400115 val breakdownBySummary = nodes.groupByTo(LinkedHashMap()) { node ->
Ilya Ryzhenkov280dc292014-10-14 16:08:10 +0400116 formatText(location, node.summary)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400117 }
118
119 for ((summary, items) in breakdownBySummary) {
Ilya Ryzhenkov7c6da4b2014-10-03 19:09:31 +0400120 items.forEach {
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400121 appendBlockCode(to, formatText(location, languageService.render(it)))
Dmitry Jemerov0dd5ea32015-01-14 13:30:43 +0100122 it.appendOverrides(to)
123 it.appendDeprecation(to)
Dmitry Jemerov6146fa82015-01-14 18:46:36 +0100124 it.appendSourceLink(to)
Ilya Ryzhenkov7c6da4b2014-10-03 19:09:31 +0400125 }
Ilya Ryzhenkov280dc292014-10-14 16:08:10 +0400126 appendLine(to, summary)
Ilya Ryzhenkov455d74a2014-09-19 22:25:27 +0300127 appendLine(to)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400128 }
129 }
130
Dmitry Jemerov0dd5ea32015-01-14 13:30:43 +0100131 private fun DocumentationNode.appendOverrides(to: StringBuilder) {
132 overrides.forEach {
133 to.append("Overrides ")
134 val location = locationService.relativeLocation(this, it, extension)
135 appendLine(to, formatLink(FormatLink(it.owner!!.name + "." + it.name, location)))
136 }
137 }
138
139 private fun DocumentationNode.appendDeprecation(to: StringBuilder) {
140 if (deprecation != null) {
141 val deprecationParameter = deprecation!!.details(DocumentationNode.Kind.Parameter).firstOrNull()
142 val deprecationValue = deprecationParameter?.details(DocumentationNode.Kind.Value)?.firstOrNull()
143 if (deprecationValue != null) {
144 to.append(formatStrong("Deprecated: "))
145 appendLine(to, formatText(deprecationValue.name.trim("\"")))
146 } else {
147 appendLine(to, formatStrong("Deprecated"))
148 }
149 }
150 }
151
Dmitry Jemerov6146fa82015-01-14 18:46:36 +0100152 private fun DocumentationNode.appendSourceLink(to: StringBuilder) {
153 val sourceUrl = details(DocumentationNode.Kind.SourceUrl).firstOrNull()
154 if (sourceUrl != null) {
155 appendLine(to, formatLink("Source", sourceUrl.name))
156 }
157 }
158
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400159 fun appendLocation(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
Ilya Ryzhenkovbd494a82014-08-21 19:53:36 +0400160 val breakdownByName = nodes.groupBy { node -> node.name }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400161 for ((name, items) in breakdownByName) {
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +0400162 appendHeader(to, formatText(name))
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400163 appendSummary(location, to, items)
164 appendDescription(location, to, items)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400165 }
166 }
167
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400168 private fun StructuredFormatService.appendSection(location: Location, caption: String, nodes: List<DocumentationNode>, node: DocumentationNode, to: StringBuilder) {
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400169 if (nodes.any()) {
170 appendHeader(to, caption, 3)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400171
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400172 val children = nodes.sortBy { it.name }
173 val membersMap = children.groupBy { link(node, it) }
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +0400174
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400175 appendTable(to) {
176 appendTableBody(to) {
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400177 for ((memberLocation, members) in membersMap) {
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400178 appendTableRow(to) {
179 appendTableCell(to) {
Dmitry Jemerov8ef68182014-12-30 12:36:14 +0100180 to.append(formatLink(memberLocation))
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400181 }
182 appendTableCell(to) {
Ilya Ryzhenkov280dc292014-10-14 16:08:10 +0400183 val breakdownBySummary = members.groupBy { formatText(location, it.summary) }
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400184 for ((summary, items) in breakdownBySummary) {
Dmitry Jemerov3fc3e332014-12-30 15:35:00 +0100185 val signatureTexts = items map { signature ->
Dmitry Jemerov0c584d02015-01-12 17:23:07 +0100186 val signature = languageService.render(signature, RenderMode.SUMMARY)
Dmitry Jemerov8ef68182014-12-30 12:36:14 +0100187 val signatureAsCode = ContentCode()
188 signatureAsCode.append(signature)
Dmitry Jemerov3fc3e332014-12-30 15:35:00 +0100189 formatText(location, signatureAsCode)
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400190 }
Dmitry Jemerov3fc3e332014-12-30 15:35:00 +0100191 signatureTexts.subList(0, signatureTexts.size()-1).forEach {
192 appendLine(to, it)
193 }
194 to.append(signatureTexts.last())
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400195 if (!summary.isEmpty()) {
Dmitry Jemerov8ef68182014-12-30 12:36:14 +0100196 to.append(summary)
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +0400197 }
198 }
199 }
Ilya Ryzhenkove8447fd2014-07-15 16:37:50 +0400200 }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400201 }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400202 }
203 }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400204 }
205 }
206
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400207 override fun appendNodes(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
208 val breakdownByLocation = nodes.groupBy { node ->
209 formatBreadcrumbs(node.path.map { link(node, it) })
210 }
211
212 for ((breadcrumbs, items) in breakdownByLocation) {
213 appendLine(to, breadcrumbs)
214 appendLine(to)
Dmitry Jemerov4b0dcee2015-01-09 19:48:44 +0100215 appendLocation(location, to, items.filter { it.kind != DocumentationNode.Kind.ExternalClass })
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400216 }
217
218 for (node in nodes) {
Dmitry Jemerov4b0dcee2015-01-09 19:48:44 +0100219 if (node.kind == DocumentationNode.Kind.ExternalClass) {
220 appendSection(location, "Extensions for ${node.name}", node.members, node, to)
221 continue
222 }
223
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400224 appendSection(location, "Packages", node.members(DocumentationNode.Kind.Package), node, to)
225 appendSection(location, "Types", node.members.filter {
226 it.kind in setOf(
227 DocumentationNode.Kind.Class,
228 DocumentationNode.Kind.Interface,
229 DocumentationNode.Kind.Enum,
Dmitry Jemerov716483c2014-12-30 17:41:14 +0100230 DocumentationNode.Kind.Object,
231 DocumentationNode.Kind.AnnotationClass)
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400232 }, node, to)
Dmitry Jemerov4b0dcee2015-01-09 19:48:44 +0100233 appendSection(location, "Extensions for External Classes", node.members(DocumentationNode.Kind.ExternalClass), node, to)
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400234 appendSection(location, "Constructors", node.members(DocumentationNode.Kind.Constructor), node, to)
235 appendSection(location, "Properties", node.members(DocumentationNode.Kind.Property), node, to)
236 appendSection(location, "Functions", node.members(DocumentationNode.Kind.Function), node, to)
Dmitry Jemerovcedaeb42014-12-29 20:50:26 +0100237 appendSection(location, "Class Object Properties", node.members(DocumentationNode.Kind.ClassObjectProperty), node, to)
238 appendSection(location, "Class Object Functions", node.members(DocumentationNode.Kind.ClassObjectFunction), node, to)
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400239 appendSection(location, "Accessors", node.members(DocumentationNode.Kind.PropertyAccessor), node, to)
Dmitry Jemerovc4f40a02015-01-12 16:32:30 +0100240 appendSection(location, "Enum Values", node.members(DocumentationNode.Kind.EnumItem), node, to)
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400241 appendSection(location, "Other members", node.members.filter {
242 it.kind !in setOf(
243 DocumentationNode.Kind.Class,
244 DocumentationNode.Kind.Interface,
Dmitry Jemerov716483c2014-12-30 17:41:14 +0100245 DocumentationNode.Kind.Enum,
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400246 DocumentationNode.Kind.Object,
Dmitry Jemerov716483c2014-12-30 17:41:14 +0100247 DocumentationNode.Kind.AnnotationClass,
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400248 DocumentationNode.Kind.Constructor,
249 DocumentationNode.Kind.Property,
250 DocumentationNode.Kind.Package,
251 DocumentationNode.Kind.Function,
Dmitry Jemerovcedaeb42014-12-29 20:50:26 +0100252 DocumentationNode.Kind.PropertyAccessor,
253 DocumentationNode.Kind.ClassObjectProperty,
Dmitry Jemerov4b0dcee2015-01-09 19:48:44 +0100254 DocumentationNode.Kind.ClassObjectFunction,
Dmitry Jemerovc4f40a02015-01-12 16:32:30 +0100255 DocumentationNode.Kind.ExternalClass,
256 DocumentationNode.Kind.EnumItem
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400257 )
258 }, node, to)
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400259 appendSection(location, "Extensions", node.extensions, node, to)
Dmitry Jemerovc4f40a02015-01-12 16:32:30 +0100260 appendSection(location, "Inheritors",
261 node.inheritors.filter { it.kind != DocumentationNode.Kind.EnumItem }, node, to)
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400262 appendSection(location, "Links", node.links, node, to)
263
264 }
265 }
266
Ilya Ryzhenkov499d0822014-07-15 16:18:53 +0400267 abstract public fun appendOutlineHeader(to: StringBuilder, node: DocumentationNode)
268 abstract public fun appendOutlineChildren(to: StringBuilder, nodes: Iterable<DocumentationNode>)
269
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400270 public override fun appendOutline(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
Ilya Ryzhenkov499d0822014-07-15 16:18:53 +0400271 for (node in nodes) {
272 appendOutlineHeader(to, node)
273 if (node.members.any()) {
274 appendOutlineChildren(to, node.members)
275 }
276 }
277 }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400278}