aboutsummaryrefslogtreecommitdiff
path: root/plugins/versioning/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/versioning/src/main')
-rw-r--r--plugins/versioning/src/main/kotlin/versioning/ReplaceVersionCommandConsumer.kt45
-rw-r--r--plugins/versioning/src/main/kotlin/versioning/ReplaceVersionsCommand.kt26
-rw-r--r--plugins/versioning/src/main/kotlin/versioning/VersioningConfiguration.kt20
-rw-r--r--plugins/versioning/src/main/kotlin/versioning/VersioningHandler.kt101
-rw-r--r--plugins/versioning/src/main/kotlin/versioning/VersioningPlugin.kt39
-rw-r--r--plugins/versioning/src/main/kotlin/versioning/VersionsNavigationCreator.kt53
-rw-r--r--plugins/versioning/src/main/kotlin/versioning/VersionsOrdering.kt23
-rw-r--r--plugins/versioning/src/main/kotlin/versioning/htmlPreprocessors.kt24
-rw-r--r--plugins/versioning/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin1
-rw-r--r--plugins/versioning/src/main/resources/dokka/styles/multimodule.css37
10 files changed, 369 insertions, 0 deletions
diff --git a/plugins/versioning/src/main/kotlin/versioning/ReplaceVersionCommandConsumer.kt b/plugins/versioning/src/main/kotlin/versioning/ReplaceVersionCommandConsumer.kt
new file mode 100644
index 00000000..2577b2da
--- /dev/null
+++ b/plugins/versioning/src/main/kotlin/versioning/ReplaceVersionCommandConsumer.kt
@@ -0,0 +1,45 @@
+package org.jetbrains.dokka.versioning
+
+import kotlinx.html.unsafe
+import kotlinx.html.visit
+import kotlinx.html.visitAndFinalize
+import org.jetbrains.dokka.base.renderers.html.TemplateBlock
+import org.jetbrains.dokka.base.renderers.html.command.consumers.ImmediateResolutionTagConsumer
+import org.jetbrains.dokka.base.renderers.html.templateCommandFor
+import org.jetbrains.dokka.base.templating.Command
+import org.jetbrains.dokka.base.templating.ImmediateHtmlCommandConsumer
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+
+class ReplaceVersionCommandConsumer(context: DokkaContext) : ImmediateHtmlCommandConsumer {
+
+ private val versionsNavigationCreator =
+ context.plugin<VersioningPlugin>().querySingle { versionsNavigationCreator }
+
+ override fun canProcess(command: Command) = command is ReplaceVersionsCommand
+
+ override fun <R> processCommand(
+ command: Command,
+ block: TemplateBlock,
+ tagConsumer: ImmediateResolutionTagConsumer<R>
+ ) {
+ command as ReplaceVersionsCommand
+ templateCommandFor(command, tagConsumer).visit {
+ unsafe {
+ +versionsNavigationCreator()
+ }
+ }
+ }
+
+ override fun <R> processCommandAndFinalize(
+ command: Command,
+ block: TemplateBlock,
+ tagConsumer: ImmediateResolutionTagConsumer<R>
+ ): R =
+ templateCommandFor(command, tagConsumer).visitAndFinalize(tagConsumer) {
+ unsafe {
+ +versionsNavigationCreator()
+ }
+ }
+} \ No newline at end of file
diff --git a/plugins/versioning/src/main/kotlin/versioning/ReplaceVersionsCommand.kt b/plugins/versioning/src/main/kotlin/versioning/ReplaceVersionsCommand.kt
new file mode 100644
index 00000000..2d2c8e36
--- /dev/null
+++ b/plugins/versioning/src/main/kotlin/versioning/ReplaceVersionsCommand.kt
@@ -0,0 +1,26 @@
+package org.jetbrains.dokka.versioning
+
+
+import org.jetbrains.dokka.base.templating.Command
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.templates.CommandHandler
+import org.jsoup.nodes.Element
+import java.io.File
+
+object ReplaceVersionsCommand : Command
+
+class ReplaceVersionCommandHandler(context: DokkaContext) : CommandHandler {
+
+ val versionsNavigationCreator by lazy {
+ context.plugin<VersioningPlugin>().querySingle { versionsNavigationCreator }
+ }
+
+ override fun canHandle(command: Command): Boolean = command is ReplaceVersionsCommand
+
+ override fun handleCommand(element: Element, command: Command, input: File, output: File) {
+ element.empty()
+ element.append(versionsNavigationCreator(output))
+ }
+} \ No newline at end of file
diff --git a/plugins/versioning/src/main/kotlin/versioning/VersioningConfiguration.kt b/plugins/versioning/src/main/kotlin/versioning/VersioningConfiguration.kt
new file mode 100644
index 00000000..98eef33d
--- /dev/null
+++ b/plugins/versioning/src/main/kotlin/versioning/VersioningConfiguration.kt
@@ -0,0 +1,20 @@
+package org.jetbrains.dokka.versioning
+
+import org.jetbrains.dokka.plugability.ConfigurableBlock
+import org.jetbrains.dokka.plugability.DokkaContext
+import java.io.File
+
+data class VersioningConfiguration(
+ var olderVersionsDir: File? = defaultOlderVersionsDir,
+ var versionsOrdering: List<String>? = defaultVersionsOrdering,
+ var version: String? = defaultVersion,
+) : ConfigurableBlock {
+ fun versionFromConfigurationOrModule(dokkaContext: DokkaContext): String =
+ version ?: dokkaContext.configuration.moduleVersion ?: "1.0"
+
+ companion object {
+ val defaultOlderVersionsDir: File? = null
+ val defaultVersionsOrdering: List<String>? = null
+ val defaultVersion = null
+ }
+} \ No newline at end of file
diff --git a/plugins/versioning/src/main/kotlin/versioning/VersioningHandler.kt b/plugins/versioning/src/main/kotlin/versioning/VersioningHandler.kt
new file mode 100644
index 00000000..699c87e6
--- /dev/null
+++ b/plugins/versioning/src/main/kotlin/versioning/VersioningHandler.kt
@@ -0,0 +1,101 @@
+package org.jetbrains.dokka.versioning
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.readValue
+import kotlinx.coroutines.*
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.configuration
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.query
+import org.jetbrains.dokka.templates.TemplateProcessingStrategy
+import org.jetbrains.dokka.templates.TemplatingPlugin
+import java.io.File
+
+interface VersioningHandler : () -> Unit {
+ fun getVersions(): Map<VersionId, File>
+ fun currentVersion(): File?
+}
+
+typealias VersionId = String
+
+class DefaultVersioningHandler(val context: DokkaContext) : VersioningHandler {
+
+ private val mapper = ObjectMapper()
+
+ private lateinit var versions: Map<VersionId, File>
+
+ private val processingStrategies: List<TemplateProcessingStrategy> =
+ context.plugin<TemplatingPlugin>().query { templateProcessingStrategy }
+
+ private val configuration = configuration<VersioningPlugin, VersioningConfiguration>(context)
+
+ override fun getVersions() = versions
+
+ override fun currentVersion() = configuration?.let { versionsConfiguration ->
+ versions[versionsConfiguration.versionFromConfigurationOrModule(context)]
+ }
+
+ override fun invoke() {
+ configuration?.let { versionsConfiguration ->
+ versions =
+ mapOf(versionsConfiguration.versionFromConfigurationOrModule(context) to context.configuration.outputDir)
+ versionsConfiguration.olderVersionsDir?.let {
+ handlePreviousVersions(it, context.configuration.outputDir)
+ }
+ mapper.writeValue(
+ context.configuration.outputDir.resolve(VERSIONS_FILE),
+ Version(versionsConfiguration.versionFromConfigurationOrModule(context))
+ )
+ }
+ }
+
+ private fun handlePreviousVersions(olderVersionDir: File, output: File): Map<String, File> {
+ assert(olderVersionDir.isDirectory) { "Supplied previous version $olderVersionDir is not a directory!" }
+ return versionsWithOriginDir(olderVersionDir)
+ .also { fetched ->
+ versions = versions + fetched.map { (key, _) ->
+ key to output.resolve(OLDER_VERSIONS_DIR).resolve(key)
+ }.toMap()
+ }
+ .onEach { (version, path) -> copyVersion(version, path, output) }.toMap()
+ }
+
+ private fun versionsWithOriginDir(olderVersionRootDir: File) =
+ olderVersionRootDir.listFiles().orEmpty().mapNotNull { versionDir ->
+ versionDir.listFiles { _, name -> name == VERSIONS_FILE }?.firstOrNull()?.let { file ->
+ val versionsContent = mapper.readValue<Version>(file)
+ Pair(versionsContent.version, versionDir)
+ }.also {
+ if (it == null) context.logger.warn("Failed to find versions file named $VERSIONS_FILE in $versionDir")
+ }
+ }
+
+ private fun copyVersion(version: VersionId, versionRoot: File, output: File) {
+ val targetParent = output.resolve(OLDER_VERSIONS_DIR).resolve(version).apply { mkdirs() }
+ runBlocking(Dispatchers.Default) {
+ coroutineScope {
+ versionRoot.listFiles().orEmpty().forEach { versionRootContent ->
+ launch {
+ if (versionRootContent.isDirectory) versionRootContent.copyRecursively(
+ targetParent.resolve(versionRootContent.name),
+ overwrite = true
+ )
+ else processingStrategies.first {
+ it.process(versionRootContent, targetParent.resolve(versionRootContent.name))
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private data class Version(
+ @JsonProperty("version") val version: String,
+ )
+
+ companion object {
+ private const val OLDER_VERSIONS_DIR = "older"
+ private const val VERSIONS_FILE = "version.json"
+ }
+} \ No newline at end of file
diff --git a/plugins/versioning/src/main/kotlin/versioning/VersioningPlugin.kt b/plugins/versioning/src/main/kotlin/versioning/VersioningPlugin.kt
new file mode 100644
index 00000000..9c20a128
--- /dev/null
+++ b/plugins/versioning/src/main/kotlin/versioning/VersioningPlugin.kt
@@ -0,0 +1,39 @@
+package org.jetbrains.dokka.versioning
+
+import org.jetbrains.dokka.base.DokkaBase
+import org.jetbrains.dokka.plugability.DokkaPlugin
+import org.jetbrains.dokka.plugability.configuration
+import org.jetbrains.dokka.templates.TemplatingPlugin
+
+class VersioningPlugin : DokkaPlugin() {
+
+ val versioningHandler by extensionPoint<VersioningHandler>()
+ val versionsNavigationCreator by extensionPoint<VersionsNavigationCreator>()
+ val versionsOrdering by extensionPoint<VersionsOrdering>()
+
+ private val dokkaBase by lazy { plugin<DokkaBase>() }
+ private val templatingPlugin by lazy { plugin<TemplatingPlugin>() }
+
+ val defaultVersioningHandler by extending {
+ versioningHandler providing ::DefaultVersioningHandler
+ }
+ val defaultVersioningNavigationCreator by extending {
+ versionsNavigationCreator providing ::HtmlVersionsNavigationCreator
+ }
+ val replaceVersionCommandHandler by extending {
+ templatingPlugin.directiveBasedCommandHandlers providing ::ReplaceVersionCommandHandler
+ }
+ val resolveLinkConsumer by extending {
+ dokkaBase.immediateHtmlCommandConsumer providing ::ReplaceVersionCommandConsumer
+ }
+ val cssStyleInstaller by extending {
+ dokkaBase.htmlPreprocessors with MultiModuleStylesInstaller order { after(dokkaBase.assetsInstaller) }
+ }
+ val versionsDefaultOrdering by extending {
+ versionsOrdering providing { ctx ->
+ configuration<VersioningPlugin, VersioningConfiguration>(ctx)?.versionsOrdering?.let {
+ ByConfigurationVersionOrdering(ctx)
+ } ?: SemVerVersionOrdering()
+ }
+ }
+} \ No newline at end of file
diff --git a/plugins/versioning/src/main/kotlin/versioning/VersionsNavigationCreator.kt b/plugins/versioning/src/main/kotlin/versioning/VersionsNavigationCreator.kt
new file mode 100644
index 00000000..76653d47
--- /dev/null
+++ b/plugins/versioning/src/main/kotlin/versioning/VersionsNavigationCreator.kt
@@ -0,0 +1,53 @@
+package org.jetbrains.dokka.versioning
+
+import kotlinx.html.a
+import kotlinx.html.button
+import kotlinx.html.div
+import kotlinx.html.i
+import kotlinx.html.stream.appendHTML
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import java.io.File
+import java.nio.file.Files.isDirectory
+import java.nio.file.Path
+
+interface VersionsNavigationCreator {
+ operator fun invoke(): String
+ operator fun invoke(output: File): String
+}
+
+class HtmlVersionsNavigationCreator(val context: DokkaContext) : VersionsNavigationCreator {
+
+ private val versioningHandler by lazy { context.plugin<VersioningPlugin>().querySingle { versioningHandler } }
+
+ private val versionsOrdering by lazy { context.plugin<VersioningPlugin>().querySingle { versionsOrdering } }
+
+ override fun invoke(): String =
+ versioningHandler.currentVersion()?.let { invoke(it) }.orEmpty()
+
+ override fun invoke(output: File): String {
+ val position = output.takeIf { it.isDirectory } ?: output.parentFile
+ return versioningHandler.getVersions()
+ .let { versions -> versionsOrdering.order(versions.keys.toList()).map { it to versions[it] } }
+ .takeIf { it.isNotEmpty() }
+ ?.let { versions ->
+ StringBuilder().appendHTML().div(classes = "versions-dropdown") {
+ button(classes = "versions-dropdown-button") {
+ versions.first { (_, versionLocation) -> versionLocation?.absolutePath == position.absolutePath }
+ .let { (version, _) ->
+ text(version)
+ }
+ i(classes = "fa fa-caret-down")
+ }
+ div(classes = "versions-dropdown-data") {
+ versions.forEach { (version, path) ->
+ a(href = path?.resolve("index.html")?.toRelativeString(position)) {
+ text(version)
+ }
+ }
+ }
+ }.toString()
+ }.orEmpty()
+ }
+} \ No newline at end of file
diff --git a/plugins/versioning/src/main/kotlin/versioning/VersionsOrdering.kt b/plugins/versioning/src/main/kotlin/versioning/VersionsOrdering.kt
new file mode 100644
index 00000000..f72e2df6
--- /dev/null
+++ b/plugins/versioning/src/main/kotlin/versioning/VersionsOrdering.kt
@@ -0,0 +1,23 @@
+package org.jetbrains.dokka.versioning
+
+import org.apache.maven.artifact.versioning.ComparableVersion
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.configuration
+import org.jetbrains.dokka.versioning.VersionId
+import org.jetbrains.dokka.versioning.VersioningConfiguration
+import org.jetbrains.dokka.versioning.VersioningPlugin
+
+fun interface VersionsOrdering {
+ fun order(records: List<VersionId>): List<VersionId>
+}
+
+class ByConfigurationVersionOrdering(val dokkaContext: DokkaContext) : VersionsOrdering {
+ override fun order(records: List<VersionId>): List<VersionId> =
+ configuration<VersioningPlugin, VersioningConfiguration>(dokkaContext)?.versionsOrdering
+ ?: throw IllegalStateException("Attempted to use a configuration ordering without providing configuration")
+}
+
+class SemVerVersionOrdering : VersionsOrdering {
+ override fun order(records: List<VersionId>): List<VersionId> =
+ records.map { it to ComparableVersion(it) }.sortedByDescending { it.second }.map { it.first }
+} \ No newline at end of file
diff --git a/plugins/versioning/src/main/kotlin/versioning/htmlPreprocessors.kt b/plugins/versioning/src/main/kotlin/versioning/htmlPreprocessors.kt
new file mode 100644
index 00000000..5852ba9e
--- /dev/null
+++ b/plugins/versioning/src/main/kotlin/versioning/htmlPreprocessors.kt
@@ -0,0 +1,24 @@
+package org.jetbrains.dokka.versioning
+
+import org.jetbrains.dokka.pages.RendererSpecificResourcePage
+import org.jetbrains.dokka.pages.RenderingStrategy
+import org.jetbrains.dokka.pages.RootPageNode
+import org.jetbrains.dokka.transformers.pages.PageTransformer
+
+object MultiModuleStylesInstaller : PageTransformer {
+ private val stylesPages = listOf(
+ "styles/multimodule.css",
+ )
+
+ override fun invoke(input: RootPageNode): RootPageNode =
+ input.modified(
+ children = input.children + stylesPages.toRenderSpecificResourcePage()
+ ).transformContentPagesTree {
+ it.modified(
+ embeddedResources = it.embeddedResources + stylesPages
+ )
+ }
+}
+
+private fun List<String>.toRenderSpecificResourcePage(): List<RendererSpecificResourcePage> =
+ map { RendererSpecificResourcePage(it, emptyList(), RenderingStrategy.Copy("/dokka/$it")) }
diff --git a/plugins/versioning/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/plugins/versioning/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin
new file mode 100644
index 00000000..1b0f1e3e
--- /dev/null
+++ b/plugins/versioning/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin
@@ -0,0 +1 @@
+org.jetbrains.dokka.versioning.VersioningPlugin \ No newline at end of file
diff --git a/plugins/versioning/src/main/resources/dokka/styles/multimodule.css b/plugins/versioning/src/main/resources/dokka/styles/multimodule.css
new file mode 100644
index 00000000..4334d759
--- /dev/null
+++ b/plugins/versioning/src/main/resources/dokka/styles/multimodule.css
@@ -0,0 +1,37 @@
+.versions-dropdown {
+ float: right;
+}
+
+.versions-dropdown-button {
+ border: none;
+ cursor: pointer;
+ padding: 5px;
+}
+
+.versions-dropdown-button:hover,
+.versions-dropdown-button:focus {
+ background-color: #f1f1f1;
+}
+
+.versions-dropdown-data {
+ display: none;
+ position: absolute;
+ background-color: #f1f1f1;
+ box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
+ z-index: 1;
+}
+
+.versions-dropdown-data a {
+ padding: 5px;
+ text-decoration: none;
+ display: block;
+ color: black;
+}
+
+.versions-dropdown-data a:hover {
+ background-color: #ddd
+}
+
+.versions-dropdown:hover .versions-dropdown-data {
+ display: block;
+} \ No newline at end of file