diff options
Diffstat (limited to 'plugins/versioning')
11 files changed, 388 insertions, 0 deletions
diff --git a/plugins/versioning/build.gradle.kts b/plugins/versioning/build.gradle.kts new file mode 100644 index 00000000..39cc0bdf --- /dev/null +++ b/plugins/versioning/build.gradle.kts @@ -0,0 +1,19 @@ +import org.jetbrains.registerDokkaArtifactPublication + +registerDokkaArtifactPublication("versioning-plugin") { + artifactId = "versioning-plugin" +} + +dependencies { + implementation(project(":plugins:base")) + implementation(project(":plugins:templating")) + + val coroutines_version: String by project + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.11.1") + val kotlinx_html_version: String by project + implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:$kotlinx_html_version") + + implementation("org.jsoup:jsoup:1.12.1") + implementation("org.apache.maven:maven-artifact:3.6.3") +}
\ No newline at end of file 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 |