blob: 7535449db3d064d8dc00da5c090019390b2c416a [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
Dmitry Jemerovd9bfa022015-02-19 18:59:00 +01006public data class FormatLink(val text: String, val href: String)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +04007
Dmitry Jemerovea1f4cc2015-02-19 19:51:01 +01008public abstract class StructuredFormatService(locationService: LocationService,
9 val languageService: LanguageService,
10 override val extension: String) : FormatService {
11 val locationService: LocationService = locationService.withExtension(extension)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040012
13 abstract public fun appendBlockCode(to: StringBuilder, line: String)
14 abstract public fun appendBlockCode(to: StringBuilder, lines: Iterable<String>)
15 abstract public fun appendHeader(to: StringBuilder, text: String, level: Int = 1)
Dmitry Jemerov8ef68182014-12-30 12:36:14 +010016 abstract public fun appendParagraph(to: StringBuilder, text: String)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040017 abstract public fun appendLine(to: StringBuilder, text: String)
18 public abstract fun appendLine(to: StringBuilder)
Dmitry Jemerov85a3ae72015-02-20 14:08:30 +010019 public abstract fun appendAnchor(to: StringBuilder, anchor: String)
Ilya Ryzhenkov499d0822014-07-15 16:18:53 +040020
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +040021 public abstract fun appendTable(to: StringBuilder, body: () -> Unit)
22 public abstract fun appendTableHeader(to: StringBuilder, body: () -> Unit)
23 public abstract fun appendTableBody(to: StringBuilder, body: () -> Unit)
24 public abstract fun appendTableRow(to: StringBuilder, body: () -> Unit)
25 public abstract fun appendTableCell(to: StringBuilder, body: () -> Unit)
Ilya Ryzhenkov499d0822014-07-15 16:18:53 +040026
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +040027 public abstract fun formatText(text: String): String
Ilya Ryzhenkov7c6da4b2014-10-03 19:09:31 +040028 public abstract fun formatSymbol(text: String): String
29 public abstract fun formatKeyword(text: String): String
30 public abstract fun formatIdentifier(text: String): String
Ilya Ryzhenkov71cd87e2014-10-03 22:51:44 +040031 public abstract fun formatLink(text: String, href: String): String
Dmitry Jemerovd9bfa022015-02-19 18:59:00 +010032 public open fun formatLink(link: FormatLink): String = formatLink(formatText(link.text), link.href)
Ilya Ryzhenkov9f0ff552014-10-13 13:38:40 +040033 public abstract fun formatStrong(text: String): String
Dmitry Jemerove17eaa52015-01-09 20:59:58 +010034 public abstract fun formatStrikethrough(text: String): String
Ilya Ryzhenkov9f0ff552014-10-13 13:38:40 +040035 public abstract fun formatEmphasis(text: String): String
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040036 public abstract fun formatCode(code: String): String
Ilya Ryzhenkov18399492014-12-22 09:50:17 +020037 public abstract fun formatList(text: String): String
38 public abstract fun formatListItem(text: String): String
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040039 public abstract fun formatBreadcrumbs(items: Iterable<FormatLink>): String
Dmitry Jemerov722c9af2015-02-26 16:28:05 +010040 public abstract fun formatNonBreakingSpace(): String
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040041
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040042 open fun formatText(location: Location, nodes: Iterable<ContentNode>): String {
43 return nodes.map { formatText(location, it) }.join("")
Ilya Ryzhenkov778e2b32014-09-29 20:54:59 +040044 }
45
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040046 open fun formatText(location: Location, content: ContentNode): String {
Ilya Ryzhenkov455d74a2014-09-19 22:25:27 +030047 return StringBuilder {
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040048 when (content) {
Dmitry Jemerov8ef68182014-12-30 12:36:14 +010049 is ContentText -> append(formatText(content.text))
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040050 is ContentSymbol -> append(formatSymbol(content.text))
51 is ContentKeyword -> append(formatKeyword(content.text))
52 is ContentIdentifier -> append(formatIdentifier(content.text))
Dmitry Jemerov722c9af2015-02-26 16:28:05 +010053 is ContentNonBreakingSpace -> append(formatNonBreakingSpace())
Ilya Ryzhenkov9f0ff552014-10-13 13:38:40 +040054 is ContentStrong -> append(formatStrong(formatText(location, content.children)))
Dmitry Jemerove17eaa52015-01-09 20:59:58 +010055 is ContentStrikethrough -> append(formatStrikethrough(formatText(location, content.children)))
Ilya Ryzhenkov9f0ff552014-10-13 13:38:40 +040056 is ContentCode -> append(formatCode(formatText(location, content.children)))
57 is ContentEmphasis -> append(formatEmphasis(formatText(location, content.children)))
Ilya Ryzhenkov18399492014-12-22 09:50:17 +020058 is ContentList -> append(formatList(formatText(location, content.children)))
59 is ContentListItem -> append(formatListItem(formatText(location, content.children)))
60
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040061 is ContentNodeLink -> {
Dmitry Jemerov184a24c2015-02-25 19:03:51 +010062 val node = content.node
63 val linkTo = if (node != null) locationHref(location, node) else "#"
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +040064 val linkText = formatText(location, content.children)
65 append(formatLink(linkText, linkTo))
66 }
Ilya Ryzhenkov71cd87e2014-10-03 22:51:44 +040067 is ContentExternalLink -> {
68 val linkText = formatText(location, content.children)
69 append(formatLink(linkText, content.href))
70 }
Ilya Ryzhenkovad14ea92014-10-13 18:22:02 +040071 is ContentParagraph -> {
Dmitry Jemerov8ef68182014-12-30 12:36:14 +010072 appendParagraph(this, formatText(location, content.children))
Ilya Ryzhenkovad14ea92014-10-13 18:22:02 +040073 }
Ilya Ryzhenkov1cb3af92014-10-13 20:14:45 +040074 is ContentBlockCode -> {
75 appendBlockCode(this, formatText(location, content.children))
76 }
Dmitry Jemerov0d0fc1f2015-02-10 18:32:12 +010077 is ContentBlock -> append(formatText(location, content.children))
Ilya Ryzhenkov455d74a2014-09-19 22:25:27 +030078 }
79 }.toString()
80 }
81
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040082 open public fun link(from: DocumentationNode, to: DocumentationNode): FormatLink = link(from, to, extension)
83
84 open public fun link(from: DocumentationNode, to: DocumentationNode, extension: String): FormatLink {
Dmitry Jemerovea1f4cc2015-02-19 19:51:01 +010085 return FormatLink(to.name, locationService.relativePathToLocation(from, to))
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +040086 }
87
Dmitry Jemerov85a3ae72015-02-20 14:08:30 +010088 fun locationHref(from: Location, to: DocumentationNode): String {
89 val topLevelPage = to.references(DocumentationReference.Kind.TopLevelPage).singleOrNull()?.to
90 if (topLevelPage != null) {
91 return from.relativePathTo(locationService.location(topLevelPage), to.name)
92 }
93 return from.relativePathTo(locationService.location(to))
94 }
95
Dmitry Jemerove1a38842015-02-10 18:55:12 +010096 fun appendDocumentation(location: Location, to: StringBuilder, overloads: Iterable<DocumentationNode>) {
97 val breakdownBySummary = overloads.groupByTo(LinkedHashMap()) { node -> node.content }
Dmitry Jemerovbfd9ffd2015-01-30 17:59:15 +010098
Dmitry Jemerove1a38842015-02-10 18:55:12 +010099 for ((summary, items) in breakdownBySummary) {
100 items.forEach {
Dmitry Jemerovf90ee502015-02-26 13:55:37 +0100101 appendAsSignature(to) {
102 to.append(formatCode(formatText(location, languageService.render(it))))
103 it.appendSourceLink(to)
104 }
Dmitry Jemerove1a38842015-02-10 18:55:12 +0100105 it.appendOverrides(to)
Dmitry Jemerova60d8ba2015-02-24 16:24:00 +0100106 it.appendDeprecation(location, to)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400107 }
Dmitry Jemerove1a38842015-02-10 18:55:12 +0100108 // All items have exactly the same documentation, so we can use any item to render it
109 val item = items.first()
Dmitry Jemerovebbf2652015-02-10 19:35:27 +0100110 to.append(formatText(location, item.content.summary))
Dmitry Jemerove1a38842015-02-10 18:55:12 +0100111 appendDescription(location, to, item)
Dmitry Jemerovebbf2652015-02-10 19:35:27 +0100112 appendLine(to)
113 appendLine(to)
Dmitry Jemerove1a38842015-02-10 18:55:12 +0100114 }
115 }
116
Dmitry Jemerovf90ee502015-02-26 13:55:37 +0100117 protected open fun appendAsSignature(to: StringBuilder, block: () -> Unit) {
118 block()
119 }
120
Dmitry Jemerove1a38842015-02-10 18:55:12 +0100121 fun appendDescription(location: Location, to: StringBuilder, node: DocumentationNode) {
122 if (node.content.description != ContentEmpty) {
123 appendHeader(to, "Description", 3)
124 appendLine(to, formatText(location, node.content.description))
125 appendLine(to)
126 }
127 node.content.getSectionsWithSubjects().forEach {
128 appendSectionWithSubject(it.getKey(), location, it.getValue(), to)
129 }
130
131 for (section in node.content.sections.filter { it.subjectName == null }) {
132 appendLine(to, formatStrong(formatText(section.tag)))
133 appendLine(to, formatText(location, section))
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400134 }
135 }
136
Dmitry Jemerov0fac1d92015-01-30 19:01:40 +0100137 fun Content.getSectionsWithSubjects(): Map<String, List<ContentSection>> =
138 sections.filter { it.subjectName != null }.groupBy { it.tag }
139
140 fun appendSectionWithSubject(title: String, location: Location, subjectSections: List<ContentSection>, to: StringBuilder) {
Dmitry Jemerovbfd9ffd2015-01-30 17:59:15 +0100141 appendHeader(to, title, 3)
Dmitry Jemerov0fac1d92015-01-30 19:01:40 +0100142 subjectSections.forEach {
143 val subjectName = it.subjectName
144 if (subjectName != null) {
Dmitry Jemerov85a3ae72015-02-20 14:08:30 +0100145 appendAnchor(to, subjectName)
Dmitry Jemerov0fac1d92015-01-30 19:01:40 +0100146 to.append(formatCode(subjectName)).append(" - ")
147 to.append(formatText(location, it))
Dmitry Jemerovbfd9ffd2015-01-30 17:59:15 +0100148 appendLine(to)
149 }
150 }
Dmitry Jemerovc43a4372014-12-29 20:22:43 +0100151 }
152
Dmitry Jemerov0dd5ea32015-01-14 13:30:43 +0100153 private fun DocumentationNode.appendOverrides(to: StringBuilder) {
154 overrides.forEach {
155 to.append("Overrides ")
Dmitry Jemerovea1f4cc2015-02-19 19:51:01 +0100156 val location = locationService.relativePathToLocation(this, it)
Dmitry Jemerov0dd5ea32015-01-14 13:30:43 +0100157 appendLine(to, formatLink(FormatLink(it.owner!!.name + "." + it.name, location)))
158 }
159 }
160
Dmitry Jemerova60d8ba2015-02-24 16:24:00 +0100161 private fun DocumentationNode.appendDeprecation(location: Location, to: StringBuilder) {
Dmitry Jemerov0dd5ea32015-01-14 13:30:43 +0100162 if (deprecation != null) {
163 val deprecationParameter = deprecation!!.details(DocumentationNode.Kind.Parameter).firstOrNull()
164 val deprecationValue = deprecationParameter?.details(DocumentationNode.Kind.Value)?.firstOrNull()
165 if (deprecationValue != null) {
Dmitry Jemerov7003ad82015-02-26 13:20:41 +0100166 to.append(formatStrong("Deprecated:")).append(" ")
Dmitry Jemerov0dd5ea32015-01-14 13:30:43 +0100167 appendLine(to, formatText(deprecationValue.name.trim("\"")))
Dmitry Jemerova60d8ba2015-02-24 16:24:00 +0100168 } else if (deprecation?.content != Content.Empty) {
Dmitry Jemerov7003ad82015-02-26 13:20:41 +0100169 to.append(formatStrong("Deprecated:")).append(" ")
Dmitry Jemerova60d8ba2015-02-24 16:24:00 +0100170 to.append(formatText(location, deprecation!!.content))
Dmitry Jemerov0dd5ea32015-01-14 13:30:43 +0100171 } else {
172 appendLine(to, formatStrong("Deprecated"))
173 }
174 }
175 }
176
Dmitry Jemerov6146fa82015-01-14 18:46:36 +0100177 private fun DocumentationNode.appendSourceLink(to: StringBuilder) {
178 val sourceUrl = details(DocumentationNode.Kind.SourceUrl).firstOrNull()
179 if (sourceUrl != null) {
Dmitry Jemerovebbf2652015-02-10 19:35:27 +0100180 to.append(" ")
181 appendLine(to, formatLink("(source)", sourceUrl.name))
182 } else {
183 appendLine(to)
Dmitry Jemerov6146fa82015-01-14 18:46:36 +0100184 }
185 }
186
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400187 fun appendLocation(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
Ilya Ryzhenkovbd494a82014-08-21 19:53:36 +0400188 val breakdownByName = nodes.groupBy { node -> node.name }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400189 for ((name, items) in breakdownByName) {
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +0400190 appendHeader(to, formatText(name))
Dmitry Jemerove1a38842015-02-10 18:55:12 +0100191 appendDocumentation(location, to, items)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400192 }
193 }
194
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400195 private fun StructuredFormatService.appendSection(location: Location, caption: String, nodes: List<DocumentationNode>, node: DocumentationNode, to: StringBuilder) {
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400196 if (nodes.any()) {
197 appendHeader(to, caption, 3)
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400198
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400199 val children = nodes.sortBy { it.name }
200 val membersMap = children.groupBy { link(node, it) }
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +0400201
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400202 appendTable(to) {
203 appendTableBody(to) {
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400204 for ((memberLocation, members) in membersMap) {
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400205 appendTableRow(to) {
206 appendTableCell(to) {
Dmitry Jemerov8ef68182014-12-30 12:36:14 +0100207 to.append(formatLink(memberLocation))
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400208 }
209 appendTableCell(to) {
Ilya Ryzhenkov280dc292014-10-14 16:08:10 +0400210 val breakdownBySummary = members.groupBy { formatText(location, it.summary) }
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400211 for ((summary, items) in breakdownBySummary) {
Dmitry Jemerov3fc3e332014-12-30 15:35:00 +0100212 val signatureTexts = items map { signature ->
Dmitry Jemerov0c584d02015-01-12 17:23:07 +0100213 val signature = languageService.render(signature, RenderMode.SUMMARY)
Dmitry Jemerov8ef68182014-12-30 12:36:14 +0100214 val signatureAsCode = ContentCode()
215 signatureAsCode.append(signature)
Dmitry Jemerov3fc3e332014-12-30 15:35:00 +0100216 formatText(location, signatureAsCode)
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400217 }
Dmitry Jemerov3fc3e332014-12-30 15:35:00 +0100218 signatureTexts.subList(0, signatureTexts.size()-1).forEach {
Dmitry Jemerov86a6db82015-02-26 14:40:02 +0100219 appendAsSignature(to) {
220 appendLine(to, it)
221 }
Dmitry Jemerov3fc3e332014-12-30 15:35:00 +0100222 }
Dmitry Jemerovf90ee502015-02-26 13:55:37 +0100223 appendAsSignature(to) {
224 to.append(signatureTexts.last())
225 }
Ilya Ryzhenkova52e1d52014-10-03 15:57:16 +0400226 if (!summary.isEmpty()) {
Dmitry Jemerov8ef68182014-12-30 12:36:14 +0100227 to.append(summary)
Ilya Ryzhenkovaa59acb2014-07-15 20:05:55 +0400228 }
229 }
230 }
Ilya Ryzhenkove8447fd2014-07-15 16:37:50 +0400231 }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400232 }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400233 }
234 }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400235 }
236 }
237
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400238 override fun appendNodes(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
239 val breakdownByLocation = nodes.groupBy { node ->
Dmitry Jemerovd9bfa022015-02-19 18:59:00 +0100240 formatBreadcrumbs(node.path.filterNot { it.name.isEmpty() }.map { link(node, it) })
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400241 }
242
243 for ((breadcrumbs, items) in breakdownByLocation) {
244 appendLine(to, breadcrumbs)
245 appendLine(to)
Dmitry Jemerov4b0dcee2015-01-09 19:48:44 +0100246 appendLocation(location, to, items.filter { it.kind != DocumentationNode.Kind.ExternalClass })
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400247 }
248
249 for (node in nodes) {
Dmitry Jemerov4b0dcee2015-01-09 19:48:44 +0100250 if (node.kind == DocumentationNode.Kind.ExternalClass) {
251 appendSection(location, "Extensions for ${node.name}", node.members, node, to)
252 continue
253 }
254
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400255 appendSection(location, "Packages", node.members(DocumentationNode.Kind.Package), node, to)
256 appendSection(location, "Types", node.members.filter {
257 it.kind in setOf(
258 DocumentationNode.Kind.Class,
259 DocumentationNode.Kind.Interface,
260 DocumentationNode.Kind.Enum,
Dmitry Jemerov716483c2014-12-30 17:41:14 +0100261 DocumentationNode.Kind.Object,
262 DocumentationNode.Kind.AnnotationClass)
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400263 }, node, to)
Dmitry Jemerov4b0dcee2015-01-09 19:48:44 +0100264 appendSection(location, "Extensions for External Classes", node.members(DocumentationNode.Kind.ExternalClass), node, to)
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400265 appendSection(location, "Constructors", node.members(DocumentationNode.Kind.Constructor), node, to)
266 appendSection(location, "Properties", node.members(DocumentationNode.Kind.Property), node, to)
267 appendSection(location, "Functions", node.members(DocumentationNode.Kind.Function), node, to)
Dmitry Jemerov2822a3e2015-02-17 12:32:17 +0100268 appendSection(location, "Default Object Properties", node.members(DocumentationNode.Kind.DefaultObjectProperty), node, to)
269 appendSection(location, "Default Object Functions", node.members(DocumentationNode.Kind.DefaultObjectFunction), node, to)
Dmitry Jemerovc4f40a02015-01-12 16:32:30 +0100270 appendSection(location, "Enum Values", node.members(DocumentationNode.Kind.EnumItem), node, to)
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400271 appendSection(location, "Other members", node.members.filter {
272 it.kind !in setOf(
273 DocumentationNode.Kind.Class,
274 DocumentationNode.Kind.Interface,
Dmitry Jemerov716483c2014-12-30 17:41:14 +0100275 DocumentationNode.Kind.Enum,
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400276 DocumentationNode.Kind.Object,
Dmitry Jemerov716483c2014-12-30 17:41:14 +0100277 DocumentationNode.Kind.AnnotationClass,
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400278 DocumentationNode.Kind.Constructor,
279 DocumentationNode.Kind.Property,
280 DocumentationNode.Kind.Package,
281 DocumentationNode.Kind.Function,
Dmitry Jemerov2822a3e2015-02-17 12:32:17 +0100282 DocumentationNode.Kind.DefaultObjectProperty,
283 DocumentationNode.Kind.DefaultObjectFunction,
Dmitry Jemerovc4f40a02015-01-12 16:32:30 +0100284 DocumentationNode.Kind.ExternalClass,
285 DocumentationNode.Kind.EnumItem
Ilya Ryzhenkov49077362014-10-14 19:53:13 +0400286 )
287 }, node, to)
Dmitry Jemerov4f590342015-02-26 19:19:55 +0100288 appendSection(location, "Extension Properties", node.extensions.filter { it.kind == DocumentationNode.Kind.Property }, node, to)
289 appendSection(location, "Extension Functions", node.extensions.filter { it.kind == DocumentationNode.Kind.Function }, node, to)
Dmitry Jemerovc4f40a02015-01-12 16:32:30 +0100290 appendSection(location, "Inheritors",
291 node.inheritors.filter { it.kind != DocumentationNode.Kind.EnumItem }, node, to)
Ilya Ryzhenkovd6fd0452014-10-03 20:20:02 +0400292 appendSection(location, "Links", node.links, node, to)
293
294 }
295 }
Ilya Ryzhenkov62cb5092014-07-15 15:54:05 +0400296}