aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/Formats
diff options
context:
space:
mode:
authorDmitry Jemerov <yole@jetbrains.com>2015-12-03 16:22:11 +0100
committerDmitry Jemerov <yole@jetbrains.com>2015-12-03 16:22:49 +0100
commit39631054c58df5841ea268b7002b820ec55f6e0a (patch)
treecefedd8411c859243bd181568e16fcdd372a38c8 /core/src/main/kotlin/Formats
parent797cb4732c53bf1e3b2091add8cf731fc436607f (diff)
downloaddokka-39631054c58df5841ea268b7002b820ec55f6e0a.tar.gz
dokka-39631054c58df5841ea268b7002b820ec55f6e0a.tar.bz2
dokka-39631054c58df5841ea268b7002b820ec55f6e0a.zip
restructure Dokka build to use Gradle for everything except for the Maven plugin
Diffstat (limited to 'core/src/main/kotlin/Formats')
-rw-r--r--core/src/main/kotlin/Formats/FormatDescriptor.kt12
-rw-r--r--core/src/main/kotlin/Formats/FormatService.kt20
-rw-r--r--core/src/main/kotlin/Formats/HtmlFormatService.kt169
-rw-r--r--core/src/main/kotlin/Formats/HtmlTemplateService.kt34
-rw-r--r--core/src/main/kotlin/Formats/JekyllFormatService.kt22
-rw-r--r--core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt121
-rw-r--r--core/src/main/kotlin/Formats/MarkdownFormatService.kt117
-rw-r--r--core/src/main/kotlin/Formats/OutlineService.kt29
-rw-r--r--core/src/main/kotlin/Formats/StandardFormats.kt38
-rw-r--r--core/src/main/kotlin/Formats/StructuredFormatService.kt367
-rw-r--r--core/src/main/kotlin/Formats/YamlOutlineService.kt24
11 files changed, 953 insertions, 0 deletions
diff --git a/core/src/main/kotlin/Formats/FormatDescriptor.kt b/core/src/main/kotlin/Formats/FormatDescriptor.kt
new file mode 100644
index 00000000..0c7ca794
--- /dev/null
+++ b/core/src/main/kotlin/Formats/FormatDescriptor.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.dokka.Formats
+
+import org.jetbrains.dokka.*
+import kotlin.reflect.KClass
+
+public interface FormatDescriptor {
+ val formatServiceClass: KClass<out FormatService>?
+ val outlineServiceClass: KClass<out OutlineFormatService>?
+ val generatorServiceClass: KClass<out Generator>
+ val packageDocumentationBuilderClass: KClass<out PackageDocumentationBuilder>
+ val javaDocumentationBuilderClass: KClass<out JavaDocumentationBuilder>
+}
diff --git a/core/src/main/kotlin/Formats/FormatService.kt b/core/src/main/kotlin/Formats/FormatService.kt
new file mode 100644
index 00000000..7e66a6b7
--- /dev/null
+++ b/core/src/main/kotlin/Formats/FormatService.kt
@@ -0,0 +1,20 @@
+package org.jetbrains.dokka
+
+/**
+ * Abstract representation of a formatting service used to output documentation in desired format
+ *
+ * Bundled Formatters:
+ * * [HtmlFormatService] – outputs documentation to HTML format
+ * * [MarkdownFormatService] – outputs documentation in Markdown format
+ * * [TextFormatService] – outputs documentation in Text format
+ */
+public interface FormatService {
+ /** Returns extension for output files */
+ val extension: String
+
+ /** Appends formatted content to [StringBuilder](to) using specified [location] */
+ fun appendNodes(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>)
+}
+
+/** Format content to [String] using specified [location] */
+fun FormatService.format(location: Location, nodes: Iterable<DocumentationNode>): String = StringBuilder().apply { appendNodes(location, this, nodes) }.toString()
diff --git a/core/src/main/kotlin/Formats/HtmlFormatService.kt b/core/src/main/kotlin/Formats/HtmlFormatService.kt
new file mode 100644
index 00000000..4d45e6cb
--- /dev/null
+++ b/core/src/main/kotlin/Formats/HtmlFormatService.kt
@@ -0,0 +1,169 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import com.google.inject.name.Named
+import java.io.File
+import java.nio.file.Path
+import java.nio.file.Paths
+
+public open class HtmlFormatService @Inject constructor(@Named("folders") locationService: LocationService,
+ signatureGenerator: LanguageService,
+ val templateService: HtmlTemplateService)
+: StructuredFormatService(locationService, signatureGenerator, "html"), OutlineFormatService {
+ override public fun formatText(text: String): String {
+ return text.htmlEscape()
+ }
+
+ override fun formatSymbol(text: String): String {
+ return "<span class=\"symbol\">${formatText(text)}</span>"
+ }
+
+ override fun formatKeyword(text: String): String {
+ return "<span class=\"keyword\">${formatText(text)}</span>"
+ }
+
+ override fun formatIdentifier(text: String, kind: IdentifierKind): String {
+ return "<span class=\"identifier\">${formatText(text)}</span>"
+ }
+
+ override fun appendBlockCode(to: StringBuilder, line: String, language: String) {
+ to.append("<pre><code>")
+ to.append(line)
+ to.append("</code></pre>")
+ }
+
+ override fun appendHeader(to: StringBuilder, text: String, level: Int) {
+ to.appendln("<h$level>${text}</h$level>")
+ }
+
+ override fun appendParagraph(to: StringBuilder, text: String) {
+ to.appendln("<p>${text}</p>")
+ }
+
+ override fun appendLine(to: StringBuilder, text: String) {
+ to.appendln("${text}<br/>")
+ }
+
+ override fun appendLine(to: StringBuilder) {
+ to.appendln("<br/>")
+ }
+
+ override fun appendAnchor(to: StringBuilder, anchor: String) {
+ to.appendln("<a name=\"${anchor.htmlEscape()}\"></a>")
+ }
+
+ override fun appendTable(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<table>")
+ body()
+ to.appendln("</table>")
+ }
+
+ override fun appendTableHeader(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<thead>")
+ body()
+ to.appendln("</thead>")
+ }
+
+ override fun appendTableBody(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<tbody>")
+ body()
+ to.appendln("</tbody>")
+ }
+
+ override fun appendTableRow(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<tr>")
+ body()
+ to.appendln("</tr>")
+ }
+
+ override fun appendTableCell(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<td>")
+ body()
+ to.appendln("</td>")
+ }
+
+ override fun formatLink(text: String, href: String): String {
+ return "<a href=\"${href}\">${text}</a>"
+ }
+
+ override fun formatStrong(text: String): String {
+ return "<strong>${text}</strong>"
+ }
+
+ override fun formatEmphasis(text: String): String {
+ return "<emph>${text}</emph>"
+ }
+
+ override fun formatStrikethrough(text: String): String {
+ return "<s>${text}</s>"
+ }
+
+ override fun formatCode(code: String): String {
+ return "<code>${code}</code>"
+ }
+
+ override fun formatUnorderedList(text: String): String = "<ul>${text}</ul>"
+ override fun formatOrderedList(text: String): String = "<ol>${text}</ol>"
+
+ override fun formatListItem(text: String, kind: ListKind): String {
+ return "<li>${text}</li>"
+ }
+
+ override fun formatBreadcrumbs(items: Iterable<FormatLink>): String {
+ return items.map { formatLink(it) }.joinToString("&nbsp;/&nbsp;")
+ }
+
+
+ override fun appendNodes(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
+ templateService.appendHeader(to, getPageTitle(nodes), calcPathToRoot(location))
+ super.appendNodes(location, to, nodes)
+ templateService.appendFooter(to)
+ }
+
+ override fun appendOutline(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
+ templateService.appendHeader(to, "Module Contents", calcPathToRoot(location))
+ super.appendOutline(location, to, nodes)
+ templateService.appendFooter(to)
+ }
+
+ private fun calcPathToRoot(location: Location): Path {
+ val path = Paths.get(location.path)
+ return path.parent?.relativize(Paths.get(locationService.root.path + '/')) ?: path
+ }
+
+ override fun getOutlineFileName(location: Location): File {
+ return File("${location.path}-outline.html")
+ }
+
+ override fun appendOutlineHeader(location: Location, node: DocumentationNode, to: StringBuilder) {
+ val link = ContentNodeDirectLink(node)
+ link.append(languageService.render(node, LanguageService.RenderMode.FULL))
+ val signature = formatText(location, link)
+ to.appendln("<a href=\"${location.path}\">${signature}</a><br/>")
+ }
+
+ override fun appendOutlineLevel(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<ul>")
+ body()
+ to.appendln("</ul>")
+ }
+
+ override fun formatNonBreakingSpace(): String = "&nbsp;"
+}
+
+fun getPageTitle(nodes: Iterable<DocumentationNode>): String? {
+ val breakdownByLocation = nodes.groupBy { node -> formatPageTitle(node) }
+ return breakdownByLocation.keys.singleOrNull()
+}
+
+fun formatPageTitle(node: DocumentationNode): String {
+ val path = node.path
+ if (path.size == 1) {
+ return path.first().name
+ }
+ val qualifiedName = node.qualifiedName()
+ if (qualifiedName.length == 0 && path.size == 2) {
+ return path.first().name + " / root package"
+ }
+ return path.first().name + " / " + qualifiedName
+}
diff --git a/core/src/main/kotlin/Formats/HtmlTemplateService.kt b/core/src/main/kotlin/Formats/HtmlTemplateService.kt
new file mode 100644
index 00000000..ae42a31b
--- /dev/null
+++ b/core/src/main/kotlin/Formats/HtmlTemplateService.kt
@@ -0,0 +1,34 @@
+package org.jetbrains.dokka
+
+import java.nio.file.Path
+
+public interface HtmlTemplateService {
+ fun appendHeader(to: StringBuilder, title: String?, basePath: Path)
+ fun appendFooter(to: StringBuilder)
+
+ companion object {
+ public fun default(css: String? = null): HtmlTemplateService {
+ return object : HtmlTemplateService {
+ override fun appendFooter(to: StringBuilder) {
+ to.appendln("</BODY>")
+ to.appendln("</HTML>")
+ }
+ override fun appendHeader(to: StringBuilder, title: String?, basePath: Path) {
+ to.appendln("<HTML>")
+ to.appendln("<HEAD>")
+ if (title != null) {
+ to.appendln("<title>$title</title>")
+ }
+ if (css != null) {
+ val cssPath = basePath.resolve(css)
+ to.appendln("<link rel=\"stylesheet\" href=\"$cssPath\">")
+ }
+ to.appendln("</HEAD>")
+ to.appendln("<BODY>")
+ }
+ }
+ }
+ }
+}
+
+
diff --git a/core/src/main/kotlin/Formats/JekyllFormatService.kt b/core/src/main/kotlin/Formats/JekyllFormatService.kt
new file mode 100644
index 00000000..f81257d6
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JekyllFormatService.kt
@@ -0,0 +1,22 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+
+open class JekyllFormatService
+ @Inject constructor(locationService: LocationService,
+ signatureGenerator: LanguageService,
+ linkExtension: String = "md")
+: MarkdownFormatService(locationService, signatureGenerator, linkExtension) {
+
+ override fun appendNodes(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
+ to.appendln("---")
+ appendFrontMatter(nodes, to)
+ to.appendln("---")
+ to.appendln("")
+ super.appendNodes(location, to, nodes)
+ }
+
+ protected open fun appendFrontMatter(nodes: Iterable<DocumentationNode>, to: StringBuilder) {
+ to.appendln("title: ${getPageTitle(nodes)}")
+ }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt b/core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt
new file mode 100644
index 00000000..4eda7910
--- /dev/null
+++ b/core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt
@@ -0,0 +1,121 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+
+public class KotlinWebsiteFormatService @Inject constructor(locationService: LocationService,
+ signatureGenerator: LanguageService)
+: JekyllFormatService(locationService, signatureGenerator, "html") {
+ private var needHardLineBreaks = false
+
+ override fun appendFrontMatter(nodes: Iterable<DocumentationNode>, to: StringBuilder) {
+ super.appendFrontMatter(nodes, to)
+ to.appendln("layout: api")
+ }
+
+ override public fun formatBreadcrumbs(items: Iterable<FormatLink>): String {
+ items.drop(1)
+
+ if (items.count() > 1) {
+ return "<div class='api-docs-breadcrumbs'>" +
+ items.map { formatLink(it) }.joinToString(" / ") +
+ "</div>"
+ }
+
+ return ""
+ }
+
+ override public fun formatCode(code: String): String = if (code.length > 0) "<code>$code</code>" else ""
+
+ override fun formatStrikethrough(text: String): String = "<s>$text</s>"
+
+ override fun appendAsSignature(to: StringBuilder, node: ContentNode, block: () -> Unit) {
+ val contentLength = node.textLength
+ if (contentLength == 0) return
+ to.append("<div class=\"signature\">")
+ needHardLineBreaks = contentLength >= 62
+ try {
+ block()
+ } finally {
+ needHardLineBreaks = false
+ }
+ to.append("</div>")
+ }
+
+ override fun appendAsOverloadGroup(to: StringBuilder, block: () -> Unit) {
+ to.append("<div class=\"overload-group\">\n")
+ block()
+ to.append("</div>\n")
+ }
+
+ override fun formatLink(text: String, href: String): String {
+ return "<a href=\"${href}\">${text}</a>"
+ }
+
+ override fun appendTable(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<table class=\"api-docs-table\">")
+ body()
+ to.appendln("</table>")
+ }
+
+ override fun appendTableHeader(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<thead>")
+ body()
+ to.appendln("</thead>")
+ }
+
+ override fun appendTableBody(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<tbody>")
+ body()
+ to.appendln("</tbody>")
+ }
+
+ override fun appendTableRow(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<tr>")
+ body()
+ to.appendln("</tr>")
+ }
+
+ override fun appendTableCell(to: StringBuilder, body: () -> Unit) {
+ to.appendln("<td markdown=\"1\">")
+ body()
+ to.appendln("\n</td>")
+ }
+
+ override public fun appendBlockCode(to: StringBuilder, line: String, language: String) {
+ if (language.isNotEmpty()) {
+ super.appendBlockCode(to, line, language)
+ } else {
+ to.append("<pre markdown=\"1\">")
+ to.append(line.trimStart())
+ to.append("</pre>")
+ }
+ }
+
+ override fun formatSymbol(text: String): String {
+ return "<span class=\"symbol\">${formatText(text)}</span>"
+ }
+
+ override fun formatKeyword(text: String): String {
+ return "<span class=\"keyword\">${formatText(text)}</span>"
+ }
+
+ override fun formatIdentifier(text: String, kind: IdentifierKind): String {
+ return "<span class=\"${identifierClassName(kind)}\">${formatText(text)}</span>"
+ }
+
+ override fun formatSoftLineBreak(): String = if (needHardLineBreaks)
+ "<br/>"
+ else
+ ""
+
+ override fun formatIndentedSoftLineBreak(): String = if (needHardLineBreaks)
+ "<br/>&nbsp;&nbsp;&nbsp;&nbsp;"
+ else
+ ""
+
+ private fun identifierClassName(kind: IdentifierKind) = when(kind) {
+ IdentifierKind.ParameterName -> "parameterName"
+ IdentifierKind.SummarizedTypeName -> "summarizedTypeName"
+ else -> "identifier"
+ }
+}
diff --git a/core/src/main/kotlin/Formats/MarkdownFormatService.kt b/core/src/main/kotlin/Formats/MarkdownFormatService.kt
new file mode 100644
index 00000000..f694ae3e
--- /dev/null
+++ b/core/src/main/kotlin/Formats/MarkdownFormatService.kt
@@ -0,0 +1,117 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+
+
+public open class MarkdownFormatService
+ @Inject constructor(locationService: LocationService,
+ signatureGenerator: LanguageService,
+ linkExtension: String = "md")
+: StructuredFormatService(locationService, signatureGenerator, "md", linkExtension) {
+ override public fun formatBreadcrumbs(items: Iterable<FormatLink>): String {
+ return items.map { formatLink(it) }.joinToString(" / ")
+ }
+
+ override public fun formatText(text: String): String {
+ return text.htmlEscape()
+ }
+
+ override fun formatSymbol(text: String): String {
+ return text.htmlEscape()
+ }
+
+ override fun formatKeyword(text: String): String {
+ return text.htmlEscape()
+ }
+ override fun formatIdentifier(text: String, kind: IdentifierKind): String {
+ return text.htmlEscape()
+ }
+
+ override public fun formatCode(code: String): String {
+ return "`$code`"
+ }
+
+ override public fun formatUnorderedList(text: String): String = text + "\n"
+ override public fun formatOrderedList(text: String): String = text + "\n"
+
+ override fun formatListItem(text: String, kind: ListKind): String {
+ val itemText = if (text.endsWith("\n")) text else text + "\n"
+ return if (kind == ListKind.Unordered) "* $itemText" else "1. $itemText"
+ }
+
+ override public fun formatStrong(text: String): String {
+ return "**$text**"
+ }
+
+ override fun formatEmphasis(text: String): String {
+ return "*$text*"
+ }
+
+ override fun formatStrikethrough(text: String): String {
+ return "~~$text~~"
+ }
+
+ override fun formatLink(text: String, href: String): String {
+ return "[$text]($href)"
+ }
+
+ override public fun appendLine(to: StringBuilder) {
+ to.appendln()
+ }
+
+ override public fun appendLine(to: StringBuilder, text: String) {
+ to.appendln(text)
+ }
+
+ override fun appendAnchor(to: StringBuilder, anchor: String) {
+ // no anchors in Markdown
+ }
+
+ override public fun appendParagraph(to: StringBuilder, text: String) {
+ to.appendln()
+ to.appendln(text)
+ to.appendln()
+ }
+
+ override public fun appendHeader(to: StringBuilder, text: String, level: Int) {
+ appendLine(to)
+ appendLine(to, "${"#".repeat(level)} $text")
+ appendLine(to)
+ }
+
+ override public fun appendBlockCode(to: StringBuilder, line: String, language: String) {
+ appendLine(to)
+ to.appendln("``` ${language}")
+ to.appendln(line)
+ to.appendln("```")
+ appendLine(to)
+ }
+
+ override fun appendTable(to: StringBuilder, body: () -> Unit) {
+ to.appendln()
+ body()
+ to.appendln()
+ }
+
+ override fun appendTableHeader(to: StringBuilder, body: () -> Unit) {
+ body()
+ }
+
+ override fun appendTableBody(to: StringBuilder, body: () -> Unit) {
+ body()
+ }
+
+ override fun appendTableRow(to: StringBuilder, body: () -> Unit) {
+ to.append("|")
+ body()
+ to.appendln()
+ }
+
+ override fun appendTableCell(to: StringBuilder, body: () -> Unit) {
+ to.append(" ")
+ body()
+ to.append(" |")
+ }
+
+ override fun formatNonBreakingSpace(): String = "&nbsp;"
+}
diff --git a/core/src/main/kotlin/Formats/OutlineService.kt b/core/src/main/kotlin/Formats/OutlineService.kt
new file mode 100644
index 00000000..6626cf51
--- /dev/null
+++ b/core/src/main/kotlin/Formats/OutlineService.kt
@@ -0,0 +1,29 @@
+package org.jetbrains.dokka
+
+import java.io.File
+
+/**
+ * Service for building the outline of the package contents.
+ */
+public interface OutlineFormatService {
+ fun getOutlineFileName(location: Location): File
+
+ public fun appendOutlineHeader(location: Location, node: DocumentationNode, to: StringBuilder)
+ public fun appendOutlineLevel(to: StringBuilder, body: () -> Unit)
+
+ /** Appends formatted outline to [StringBuilder](to) using specified [location] */
+ public fun appendOutline(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
+ for (node in nodes) {
+ appendOutlineHeader(location, node, to)
+ if (node.members.any()) {
+ val sortedMembers = node.members.sortedBy { it.name }
+ appendOutlineLevel(to) {
+ appendOutline(location, to, sortedMembers)
+ }
+ }
+ }
+ }
+
+ fun formatOutline(location: Location, nodes: Iterable<DocumentationNode>): String =
+ StringBuilder().apply { appendOutline(location, this, nodes) }.toString()
+}
diff --git a/core/src/main/kotlin/Formats/StandardFormats.kt b/core/src/main/kotlin/Formats/StandardFormats.kt
new file mode 100644
index 00000000..94e1b115
--- /dev/null
+++ b/core/src/main/kotlin/Formats/StandardFormats.kt
@@ -0,0 +1,38 @@
+package org.jetbrains.dokka.Formats
+
+import org.jetbrains.dokka.*
+
+abstract class KotlinFormatDescriptorBase : FormatDescriptor {
+ override val packageDocumentationBuilderClass = KotlinPackageDocumentationBuilder::class
+ override val javaDocumentationBuilderClass = KotlinJavaDocumentationBuilder::class
+
+ override val generatorServiceClass = FileGenerator::class
+}
+
+class HtmlFormatDescriptor : KotlinFormatDescriptorBase() {
+ override val formatServiceClass = HtmlFormatService::class
+ override val outlineServiceClass = HtmlFormatService::class
+}
+
+class HtmlAsJavaFormatDescriptor : FormatDescriptor {
+ override val formatServiceClass = HtmlFormatService::class
+ override val outlineServiceClass = HtmlFormatService::class
+ override val generatorServiceClass = FileGenerator::class
+ override val packageDocumentationBuilderClass = KotlinAsJavaDocumentationBuilder::class
+ override val javaDocumentationBuilderClass = JavaPsiDocumentationBuilder::class
+}
+
+class KotlinWebsiteFormatDescriptor : KotlinFormatDescriptorBase() {
+ override val formatServiceClass = KotlinWebsiteFormatService::class
+ override val outlineServiceClass = YamlOutlineService::class
+}
+
+class JekyllFormatDescriptor : KotlinFormatDescriptorBase() {
+ override val formatServiceClass = JekyllFormatService::class
+ override val outlineServiceClass = null
+}
+
+class MarkdownFormatDescriptor : KotlinFormatDescriptorBase() {
+ override val formatServiceClass = MarkdownFormatService::class
+ override val outlineServiceClass = null
+}
diff --git a/core/src/main/kotlin/Formats/StructuredFormatService.kt b/core/src/main/kotlin/Formats/StructuredFormatService.kt
new file mode 100644
index 00000000..32a2b68a
--- /dev/null
+++ b/core/src/main/kotlin/Formats/StructuredFormatService.kt
@@ -0,0 +1,367 @@
+package org.jetbrains.dokka
+
+import org.jetbrains.dokka.LanguageService.RenderMode
+import java.util.*
+
+data class FormatLink(val text: String, val href: String)
+
+enum class ListKind {
+ Ordered,
+ Unordered
+}
+
+abstract class StructuredFormatService(locationService: LocationService,
+ val languageService: LanguageService,
+ override val extension: String,
+ val linkExtension: String = extension) : FormatService {
+ val locationService: LocationService = locationService.withExtension(linkExtension)
+
+ abstract fun appendBlockCode(to: StringBuilder, line: String, language: String)
+ abstract fun appendHeader(to: StringBuilder, text: String, level: Int = 1)
+ abstract fun appendParagraph(to: StringBuilder, text: String)
+ abstract fun appendLine(to: StringBuilder, text: String)
+ abstract fun appendLine(to: StringBuilder)
+ abstract fun appendAnchor(to: StringBuilder, anchor: String)
+
+ abstract fun appendTable(to: StringBuilder, body: () -> Unit)
+ abstract fun appendTableHeader(to: StringBuilder, body: () -> Unit)
+ abstract fun appendTableBody(to: StringBuilder, body: () -> Unit)
+ abstract fun appendTableRow(to: StringBuilder, body: () -> Unit)
+ abstract fun appendTableCell(to: StringBuilder, body: () -> Unit)
+
+ abstract fun formatText(text: String): String
+ abstract fun formatSymbol(text: String): String
+ abstract fun formatKeyword(text: String): String
+ abstract fun formatIdentifier(text: String, kind: IdentifierKind): String
+ fun formatEntity(text: String): String = text
+ abstract fun formatLink(text: String, href: String): String
+ open fun formatLink(link: FormatLink): String = formatLink(formatText(link.text), link.href)
+ abstract fun formatStrong(text: String): String
+ abstract fun formatStrikethrough(text: String): String
+ abstract fun formatEmphasis(text: String): String
+ abstract fun formatCode(code: String): String
+ abstract fun formatUnorderedList(text: String): String
+ abstract fun formatOrderedList(text: String): String
+ abstract fun formatListItem(text: String, kind: ListKind): String
+ abstract fun formatBreadcrumbs(items: Iterable<FormatLink>): String
+ abstract fun formatNonBreakingSpace(): String
+ open fun formatSoftLineBreak(): String = ""
+ open fun formatIndentedSoftLineBreak(): String = ""
+
+ open fun formatText(location: Location, nodes: Iterable<ContentNode>, listKind: ListKind = ListKind.Unordered): String {
+ return nodes.map { formatText(location, it, listKind) }.joinToString("")
+ }
+
+ open fun formatText(location: Location, content: ContentNode, listKind: ListKind = ListKind.Unordered): String {
+ return StringBuilder().apply {
+ when (content) {
+ is ContentText -> append(formatText(content.text))
+ is ContentSymbol -> append(formatSymbol(content.text))
+ is ContentKeyword -> append(formatKeyword(content.text))
+ is ContentIdentifier -> append(formatIdentifier(content.text, content.kind))
+ is ContentNonBreakingSpace -> append(formatNonBreakingSpace())
+ is ContentSoftLineBreak -> append(formatSoftLineBreak())
+ is ContentIndentedSoftLineBreak -> append(formatIndentedSoftLineBreak())
+ is ContentEntity -> append(formatEntity(content.text))
+ is ContentStrong -> append(formatStrong(formatText(location, content.children)))
+ is ContentStrikethrough -> append(formatStrikethrough(formatText(location, content.children)))
+ is ContentCode -> append(formatCode(formatText(location, content.children)))
+ is ContentEmphasis -> append(formatEmphasis(formatText(location, content.children)))
+ is ContentUnorderedList -> append(formatUnorderedList(formatText(location, content.children, ListKind.Unordered)))
+ is ContentOrderedList -> append(formatOrderedList(formatText(location, content.children, ListKind.Ordered)))
+ is ContentListItem -> append(formatListItem(formatText(location, content.children), listKind))
+
+ is ContentNodeLink -> {
+ val node = content.node
+ val linkTo = if (node != null) locationHref(location, node) else "#"
+ val linkText = formatText(location, content.children)
+ if (linkTo == ".") {
+ append(linkText)
+ } else {
+ append(formatLink(linkText, linkTo))
+ }
+ }
+ is ContentExternalLink -> {
+ val linkText = formatText(location, content.children)
+ if (content.href == ".") {
+ append(linkText)
+ } else {
+ append(formatLink(linkText, content.href))
+ }
+ }
+ is ContentParagraph -> appendParagraph(this, formatText(location, content.children))
+ is ContentBlockCode -> appendBlockCode(this, formatText(location, content.children), content.language)
+ is ContentHeading -> appendHeader(this, formatText(location, content.children), content.level)
+ is ContentBlock -> append(formatText(location, content.children))
+ }
+ }.toString()
+ }
+
+ open fun link(from: DocumentationNode, to: DocumentationNode): FormatLink = link(from, to, extension)
+
+ open fun link(from: DocumentationNode, to: DocumentationNode, extension: String): FormatLink {
+ return FormatLink(to.name, locationService.relativePathToLocation(from, to))
+ }
+
+ fun locationHref(from: Location, to: DocumentationNode): String {
+ val topLevelPage = to.references(DocumentationReference.Kind.TopLevelPage).singleOrNull()?.to
+ if (topLevelPage != null) {
+ return from.relativePathTo(locationService.location(topLevelPage), to.name)
+ }
+ return from.relativePathTo(locationService.location(to))
+ }
+
+ fun appendDocumentation(location: Location, to: StringBuilder, overloads: Iterable<DocumentationNode>) {
+ val breakdownBySummary = overloads.groupByTo(LinkedHashMap()) { node -> node.content }
+
+ for ((summary, items) in breakdownBySummary) {
+ appendAsOverloadGroup(to) {
+ items.forEach {
+ val rendered = languageService.render(it)
+ appendAsSignature(to, rendered) {
+ to.append(formatCode(formatText(location, rendered)))
+ it.appendSourceLink(to)
+ }
+ it.appendOverrides(to)
+ it.appendDeprecation(location, to)
+ }
+ // All items have exactly the same documentation, so we can use any item to render it
+ val item = items.first()
+ item.details(DocumentationNode.Kind.OverloadGroupNote).forEach {
+ to.append(formatText(location, it.content))
+ }
+ to.append(formatText(location, item.content.summary))
+ appendDescription(location, to, item)
+ appendLine(to)
+ appendLine(to)
+ }
+ }
+ }
+
+ private fun DocumentationNode.isModuleOrPackage(): Boolean =
+ kind == DocumentationNode.Kind.Module || kind == DocumentationNode.Kind.Package
+
+ protected open fun appendAsSignature(to: StringBuilder, node: ContentNode, block: () -> Unit) {
+ block()
+ }
+
+ protected open fun appendAsOverloadGroup(to: StringBuilder, block: () -> Unit) {
+ block()
+ }
+
+ fun appendDescription(location: Location, to: StringBuilder, node: DocumentationNode) {
+ if (node.content.description != ContentEmpty) {
+ appendLine(to, formatText(location, node.content.description))
+ appendLine(to)
+ }
+ node.content.getSectionsWithSubjects().forEach {
+ appendSectionWithSubject(it.key, location, it.value, to)
+ }
+
+ for (section in node.content.sections.filter { it.subjectName == null }) {
+ appendLine(to, formatStrong(formatText(section.tag)))
+ appendLine(to, formatText(location, section))
+ }
+ }
+
+ fun Content.getSectionsWithSubjects(): Map<String, List<ContentSection>> =
+ sections.filter { it.subjectName != null }.groupBy { it.tag }
+
+ fun appendSectionWithSubject(title: String, location: Location, subjectSections: List<ContentSection>, to: StringBuilder) {
+ appendHeader(to, title, 3)
+ subjectSections.forEach {
+ val subjectName = it.subjectName
+ if (subjectName != null) {
+ appendAnchor(to, subjectName)
+ to.append(formatCode(subjectName)).append(" - ")
+ to.append(formatText(location, it))
+ appendLine(to)
+ }
+ }
+ }
+
+ private fun DocumentationNode.appendOverrides(to: StringBuilder) {
+ overrides.forEach {
+ to.append("Overrides ")
+ val location = locationService.relativePathToLocation(this, it)
+ appendLine(to, formatLink(FormatLink(it.owner!!.name + "." + it.name, location)))
+ }
+ }
+
+ private fun DocumentationNode.appendDeprecation(location: Location, to: StringBuilder) {
+ if (deprecation != null) {
+ val deprecationParameter = deprecation!!.details(DocumentationNode.Kind.Parameter).firstOrNull()
+ val deprecationValue = deprecationParameter?.details(DocumentationNode.Kind.Value)?.firstOrNull()
+ if (deprecationValue != null) {
+ to.append(formatStrong("Deprecated:")).append(" ")
+ appendLine(to, formatText(deprecationValue.name.removeSurrounding("\"")))
+ appendLine(to)
+ } else if (deprecation?.content != Content.Empty) {
+ to.append(formatStrong("Deprecated:")).append(" ")
+ to.append(formatText(location, deprecation!!.content))
+ } else {
+ appendLine(to, formatStrong("Deprecated"))
+ appendLine(to)
+ }
+ }
+ }
+
+ private fun DocumentationNode.appendSourceLink(to: StringBuilder) {
+ val sourceUrl = details(DocumentationNode.Kind.SourceUrl).firstOrNull()
+ if (sourceUrl != null) {
+ to.append(" ")
+ appendLine(to, formatLink("(source)", sourceUrl.name))
+ } else {
+ appendLine(to)
+ }
+ }
+
+ fun appendLocation(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
+ val singleNode = nodes.singleOrNull()
+ if (singleNode != null && singleNode.isModuleOrPackage()) {
+ if (singleNode.kind == DocumentationNode.Kind.Package) {
+ appendHeader(to, "Package " + formatText(singleNode.name), 2)
+ }
+ to.append(formatText(location, singleNode.content))
+ } else {
+ val breakdownByName = nodes.groupBy { node -> node.name }
+ for ((name, items) in breakdownByName) {
+ appendHeader(to, formatText(name))
+ appendDocumentation(location, to, items)
+ }
+ }
+ }
+
+ private fun appendSection(location: Location, caption: String, nodes: List<DocumentationNode>, node: DocumentationNode, to: StringBuilder) {
+ if (nodes.any()) {
+ appendHeader(to, caption, 3)
+
+ val children = nodes.sortedBy { it.name }
+ val membersMap = children.groupBy { link(node, it) }
+
+ appendTable(to) {
+ appendTableBody(to) {
+ for ((memberLocation, members) in membersMap) {
+ appendTableRow(to) {
+ appendTableCell(to) {
+ to.append(formatLink(memberLocation))
+ }
+ appendTableCell(to) {
+ val breakdownBySummary = members.groupBy { formatText(location, it.summary) }
+ for ((summary, items) in breakdownBySummary) {
+ appendSummarySignatures(items, location, to)
+ if (!summary.isEmpty()) {
+ to.append(summary)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun appendSummarySignatures(items: List<DocumentationNode>, location: Location, to: StringBuilder) {
+ val summarySignature = languageService.summarizeSignatures(items)
+ if (summarySignature != null) {
+ appendAsSignature(to, summarySignature) {
+ appendLine(to, summarySignature.signatureToText(location))
+ }
+ return
+ }
+ val renderedSignatures = items.map { languageService.render(it, RenderMode.SUMMARY) }
+ renderedSignatures.subList(0, renderedSignatures.size - 1).forEach {
+ appendAsSignature(to, it) {
+ appendLine(to, it.signatureToText(location))
+ }
+ }
+ appendAsSignature(to, renderedSignatures.last()) {
+ to.append(renderedSignatures.last().signatureToText(location))
+ }
+ }
+
+ private fun ContentNode.signatureToText(location: Location): String {
+ return if (this is ContentBlock && this.isEmpty()) {
+ ""
+ } else {
+ val signatureAsCode = ContentCode()
+ signatureAsCode.append(this)
+ formatText(location, signatureAsCode)
+ }
+ }
+
+ override fun appendNodes(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
+ val breakdownByLocation = nodes.groupBy { node ->
+ formatBreadcrumbs(node.path.filterNot { it.name.isEmpty() }.map { link(node, it) })
+ }
+
+ for ((breadcrumbs, items) in breakdownByLocation) {
+ appendLine(to, breadcrumbs)
+ appendLine(to)
+ appendLocation(location, to, items.filter { it.kind != DocumentationNode.Kind.ExternalClass })
+ }
+
+ for (node in nodes) {
+ if (node.kind == DocumentationNode.Kind.ExternalClass) {
+ appendSection(location, "Extensions for ${node.name}", node.members, node, to)
+ continue
+ }
+
+ appendSection(location, "Packages", node.members(DocumentationNode.Kind.Package), node, to)
+ appendSection(location, "Types", node.members.filter { it.kind in DocumentationNode.Kind.classLike }, node, to)
+ appendSection(location, "Extensions for External Classes", node.members(DocumentationNode.Kind.ExternalClass), node, to)
+ appendSection(location, "Enum Values", node.members(DocumentationNode.Kind.EnumItem), node, to)
+ appendSection(location, "Constructors", node.members(DocumentationNode.Kind.Constructor), node, to)
+ appendSection(location, "Properties", node.members(DocumentationNode.Kind.Property), node, to)
+ appendSection(location, "Inherited Properties", node.inheritedMembers(DocumentationNode.Kind.Property), node, to)
+ appendSection(location, "Functions", node.members(DocumentationNode.Kind.Function), node, to)
+ appendSection(location, "Inherited Functions", node.inheritedMembers(DocumentationNode.Kind.Function), node, to)
+ appendSection(location, "Companion Object Properties", node.members(DocumentationNode.Kind.CompanionObjectProperty), node, to)
+ appendSection(location, "Companion Object Functions", node.members(DocumentationNode.Kind.CompanionObjectFunction), node, to)
+ appendSection(location, "Other members", node.members.filter {
+ it.kind !in setOf(
+ DocumentationNode.Kind.Class,
+ DocumentationNode.Kind.Interface,
+ DocumentationNode.Kind.Enum,
+ DocumentationNode.Kind.Object,
+ DocumentationNode.Kind.AnnotationClass,
+ DocumentationNode.Kind.Constructor,
+ DocumentationNode.Kind.Property,
+ DocumentationNode.Kind.Package,
+ DocumentationNode.Kind.Function,
+ DocumentationNode.Kind.CompanionObjectProperty,
+ DocumentationNode.Kind.CompanionObjectFunction,
+ DocumentationNode.Kind.ExternalClass,
+ DocumentationNode.Kind.EnumItem
+ )
+ }, node, to)
+
+ val allExtensions = collectAllExtensions(node)
+ appendSection(location, "Extension Properties", allExtensions.filter { it.kind == DocumentationNode.Kind.Property }, node, to)
+ appendSection(location, "Extension Functions", allExtensions.filter { it.kind == DocumentationNode.Kind.Function }, node, to)
+ appendSection(location, "Companion Object Extension Properties", allExtensions.filter { it.kind == DocumentationNode.Kind.CompanionObjectProperty }, node, to)
+ appendSection(location, "Companion Object Extension Functions", allExtensions.filter { it.kind == DocumentationNode.Kind.CompanionObjectFunction }, node, to)
+ appendSection(location, "Inheritors",
+ node.inheritors.filter { it.kind != DocumentationNode.Kind.EnumItem }, node, to)
+ appendSection(location, "Links", node.links, node, to)
+
+ }
+ }
+
+ private fun collectAllExtensions(node: DocumentationNode): Collection<DocumentationNode> {
+ val result = LinkedHashSet<DocumentationNode>()
+ val visited = hashSetOf<DocumentationNode>()
+
+ fun collect(node: DocumentationNode) {
+ if (!visited.add(node)) return
+ result.addAll(node.extensions)
+ node.references(DocumentationReference.Kind.Superclass).forEach { collect(it.to) }
+ }
+
+ collect(node)
+
+ return result
+
+ }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Formats/YamlOutlineService.kt b/core/src/main/kotlin/Formats/YamlOutlineService.kt
new file mode 100644
index 00000000..7968824c
--- /dev/null
+++ b/core/src/main/kotlin/Formats/YamlOutlineService.kt
@@ -0,0 +1,24 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import java.io.File
+
+class YamlOutlineService @Inject constructor(val locationService: LocationService,
+ val languageService: LanguageService) : OutlineFormatService {
+ override fun getOutlineFileName(location: Location): File = File("${location.path}.yml")
+
+ var outlineLevel = 0
+ override fun appendOutlineHeader(location: Location, node: DocumentationNode, to: StringBuilder) {
+ val indent = " ".repeat(outlineLevel)
+ to.appendln("$indent- title: ${languageService.renderName(node)}")
+ to.appendln("$indent url: ${locationService.location(node).path}")
+ }
+
+ override fun appendOutlineLevel(to: StringBuilder, body: () -> Unit) {
+ val indent = " ".repeat(outlineLevel)
+ to.appendln("$indent content:")
+ outlineLevel++
+ body()
+ outlineLevel--
+ }
+}