diff options
author | Dmitry Jemerov <yole@jetbrains.com> | 2015-12-03 16:22:11 +0100 |
---|---|---|
committer | Dmitry Jemerov <yole@jetbrains.com> | 2015-12-03 16:22:49 +0100 |
commit | 39631054c58df5841ea268b7002b820ec55f6e0a (patch) | |
tree | cefedd8411c859243bd181568e16fcdd372a38c8 /core/src/main/kotlin/Formats | |
parent | 797cb4732c53bf1e3b2091add8cf731fc436607f (diff) | |
download | dokka-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.kt | 12 | ||||
-rw-r--r-- | core/src/main/kotlin/Formats/FormatService.kt | 20 | ||||
-rw-r--r-- | core/src/main/kotlin/Formats/HtmlFormatService.kt | 169 | ||||
-rw-r--r-- | core/src/main/kotlin/Formats/HtmlTemplateService.kt | 34 | ||||
-rw-r--r-- | core/src/main/kotlin/Formats/JekyllFormatService.kt | 22 | ||||
-rw-r--r-- | core/src/main/kotlin/Formats/KotlinWebsiteFormatService.kt | 121 | ||||
-rw-r--r-- | core/src/main/kotlin/Formats/MarkdownFormatService.kt | 117 | ||||
-rw-r--r-- | core/src/main/kotlin/Formats/OutlineService.kt | 29 | ||||
-rw-r--r-- | core/src/main/kotlin/Formats/StandardFormats.kt | 38 | ||||
-rw-r--r-- | core/src/main/kotlin/Formats/StructuredFormatService.kt | 367 | ||||
-rw-r--r-- | core/src/main/kotlin/Formats/YamlOutlineService.kt | 24 |
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(" / ") + } + + + 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 = " " +} + +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/> " + 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 = " " +} 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-- + } +} |