Skip to content

Commit

Permalink
Add global settings to JSON dokka cli input (#2292)
Browse files Browse the repository at this point in the history
* Add global settings to JSON dokka cli input

* Apply requested changes

* Move initialization of global arguments to extension function in core module
  • Loading branch information
BarkingBad committed Jan 12, 2022
1 parent b30db0a commit 6b72925
Show file tree
Hide file tree
Showing 12 changed files with 309 additions and 13 deletions.
17 changes: 17 additions & 0 deletions core/api/core.api
Expand Up @@ -4,6 +4,8 @@ public final class org/jetbrains/dokka/ConfigurationKt {
public static final fun ExternalDocumentationLink (Ljava/net/URL;Ljava/net/URL;)Lorg/jetbrains/dokka/ExternalDocumentationLinkImpl;
public static synthetic fun ExternalDocumentationLink$default (Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/dokka/ExternalDocumentationLinkImpl;
public static synthetic fun ExternalDocumentationLink$default (Ljava/net/URL;Ljava/net/URL;ILjava/lang/Object;)Lorg/jetbrains/dokka/ExternalDocumentationLinkImpl;
public static final fun GlobalDokkaConfiguration (Ljava/lang/String;)Lorg/jetbrains/dokka/GlobalDokkaConfiguration;
public static final fun apply (Lorg/jetbrains/dokka/DokkaConfiguration;Lorg/jetbrains/dokka/GlobalDokkaConfiguration;)Lorg/jetbrains/dokka/DokkaConfiguration;
public static final fun build (Ljava/lang/Iterable;)Ljava/util/List;
public static final fun toJsonString (Lorg/jetbrains/dokka/DokkaConfiguration;)Ljava/lang/String;
public static final fun toJsonString (Lorg/jetbrains/dokka/plugability/ConfigurableBlock;)Ljava/lang/String;
Expand Down Expand Up @@ -343,6 +345,21 @@ public final class org/jetbrains/dokka/ExternalDocumentationLinkImpl : org/jetbr
public fun toString ()Ljava/lang/String;
}

public final class org/jetbrains/dokka/GlobalDokkaConfiguration {
public fun <init> (Ljava/util/List;Ljava/util/List;Ljava/util/List;)V
public final fun component1 ()Ljava/util/List;
public final fun component2 ()Ljava/util/List;
public final fun component3 ()Ljava/util/List;
public final fun copy (Ljava/util/List;Ljava/util/List;Ljava/util/List;)Lorg/jetbrains/dokka/GlobalDokkaConfiguration;
public static synthetic fun copy$default (Lorg/jetbrains/dokka/GlobalDokkaConfiguration;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/dokka/GlobalDokkaConfiguration;
public fun equals (Ljava/lang/Object;)Z
public final fun getExternalDocumentationLinks ()Ljava/util/List;
public final fun getPerPackageOptions ()Ljava/util/List;
public final fun getSourceLinks ()Ljava/util/List;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class org/jetbrains/dokka/PackageOptionsImpl : org/jetbrains/dokka/DokkaConfiguration$PackageOptions {
public fun <init> (Ljava/lang/String;ZLjava/lang/Boolean;ZZLjava/util/Set;)V
public final fun component1 ()Ljava/lang/String;
Expand Down
29 changes: 29 additions & 0 deletions core/src/main/kotlin/configuration.kt
Expand Up @@ -3,6 +3,7 @@
package org.jetbrains.dokka

import org.jetbrains.dokka.plugability.ConfigurableBlock
import org.jetbrains.dokka.utilities.cast
import org.jetbrains.dokka.utilities.parseJson
import org.jetbrains.dokka.utilities.toJsonString
import java.io.File
Expand Down Expand Up @@ -86,6 +87,34 @@ data class DokkaSourceSetID(

fun DokkaConfigurationImpl(json: String): DokkaConfigurationImpl = parseJson(json)

/**
* Global options are applied to all packages and modules and overwrite package configuration.
*
* These are handy if we have multiple sourcesets sharing the same global options as it reduces the size of the boilerplate.
* Otherwise, the user would be enforced to repeat all these options per each sourceset.
*/
data class GlobalDokkaConfiguration(
val perPackageOptions: List<PackageOptionsImpl>?,
val externalDocumentationLinks: List<ExternalDocumentationLinkImpl>?,
val sourceLinks: List<SourceLinkDefinitionImpl>?
)

fun GlobalDokkaConfiguration(json: String): GlobalDokkaConfiguration = parseJson(json)

fun DokkaConfiguration.apply(globals: GlobalDokkaConfiguration): DokkaConfiguration = this.apply {
sourceSets.forEach {
it.perPackageOptions.cast<MutableList<DokkaConfiguration.PackageOptions>>().addAll(globals.perPackageOptions ?: emptyList())
}

sourceSets.forEach {
it.externalDocumentationLinks.cast<MutableSet<DokkaConfiguration.ExternalDocumentationLink>>().addAll(globals.externalDocumentationLinks ?: emptyList())
}

sourceSets.forEach {
it.sourceLinks.cast<MutableSet<SourceLinkDefinitionImpl>>().addAll(globals.sourceLinks ?: emptyList())
}
}

fun DokkaConfiguration.toJsonString(): String = toJsonString(this)
fun <T : ConfigurableBlock> T.toJsonString(): String = toJsonString(this)

Expand Down
1 change: 0 additions & 1 deletion core/src/main/kotlin/utilities/json.kt
Expand Up @@ -34,7 +34,6 @@ internal fun toJsonString(value: Any): String = objectMapper.writeValueAsString(
@PublishedApi
internal inline fun <reified T : Any> parseJson(json: String): T = parseJson(json, TypeReference())


@PublishedApi
internal fun <T : Any> parseJson(json: String, typeReference: TypeReference<T>): T =
objectMapper.readValue(json, typeReference.jackson)
Expand Down
Expand Up @@ -2,6 +2,8 @@ package org.jetbrains.dokka.it.cli

import org.jetbrains.dokka.it.awaitProcessResult
import java.io.File
import java.io.PrintWriter
import java.lang.IllegalStateException
import kotlin.test.*

class CliIntegrationTest : AbstractCliIntegrationTest() {
Expand Down Expand Up @@ -193,7 +195,7 @@ class CliIntegrationTest : AbstractCliIntegrationTest() {
}

@Test
fun `logging level should be respected`(){
fun `logging level should be respected`() {
val dokkaOutputDir = File(projectDir, "output")
assertTrue(dokkaOutputDir.mkdirs())
val process = ProcessBuilder(
Expand Down Expand Up @@ -259,4 +261,111 @@ class CliIntegrationTest : AbstractCliIntegrationTest() {
)
)
}


@Test
fun `should accept json as input configuration`() {
val dokkaOutputDir = File(projectDir, "output")
assertTrue(dokkaOutputDir.mkdirs())
val jsonPath = javaClass.getResource("/my-file.json")?.path ?: throw IllegalStateException("No JSON found!")
PrintWriter(jsonPath).run {
write(jsonBuilder(dokkaOutputDir.path, basePluginJarFile.path, File(projectDir, "src").path, reportUndocumented = true))
close()
}

val process = ProcessBuilder(
"java", "-jar", cliJarFile.path, jsonPath
).redirectErrorStream(true).start()

val result = process.awaitProcessResult()
assertEquals(0, result.exitCode, "Expected exitCode 0 (Success)")

val extensionLoadedRegex = Regex("""Extension: org\.jetbrains\.dokka\.base\.DokkaBase""")
val amountOfExtensionsLoaded = extensionLoadedRegex.findAll(result.output).count()

assertTrue(
amountOfExtensionsLoaded > 10,
"Expected more than 10 extensions being present (found $amountOfExtensionsLoaded)"
)

val undocumentedReportRegex = Regex("""Undocumented:""")
val amountOfUndocumentedReports = undocumentedReportRegex.findAll(result.output).count()
assertTrue(
amountOfUndocumentedReports > 0,
"Expected at least one report of undocumented code (found $amountOfUndocumentedReports)"
)

assertTrue(dokkaOutputDir.isDirectory, "Missing dokka output directory")
}

/**
* This test disables global `reportUndocumneted` property and set `reportUndocumented` via perPackageOptions to
* make sure that global settings apply to dokka context.
*/
@Test
fun `global settings should overwrite package options in configuration`() {
val dokkaOutputDir = File(projectDir, "output")
assertTrue(dokkaOutputDir.mkdirs())
val jsonPath = javaClass.getResource("/my-file.json")?.path ?: throw IllegalStateException("No JSON found!")
PrintWriter(jsonPath).run {
write(
jsonBuilder(
outputPath = dokkaOutputDir.path,
pluginsClasspath = basePluginJarFile.path,
projectPath = File(projectDir, "src").path,
globalSourceLinks = """
{
"localDirectory": "/home/Vadim.Mishenev/dokka/examples/cli/src/main/kotlin",
"remoteUrl": "https://github.com/Kotlin/dokka/tree/master/examples/gradle/dokka-gradle-example/src/main/kotlin",
"remoteLineSuffix": "#L"
}
""".trimIndent(),
globalExternalDocumentationLinks = """
{
"url": "https://docs.oracle.com/javase/8/docs/api/",
"packageListUrl": "https://docs.oracle.com/javase/8/docs/api/package-list"
},
{
"url": "https://kotlinlang.org/api/latest/jvm/stdlib/",
"packageListUrl": "https://kotlinlang.org/api/latest/jvm/stdlib/package-list"
}
""".trimIndent(),
globalPerPackageOptions = """
{
"matchingRegex": ".*",
"skipDeprecated": "true",
"reportUndocumented": "true",
"documentedVisibilities": ["PUBLIC", "PRIVATE", "PROTECTED", "INTERNAL", "PACKAGE"]
}
""".trimIndent(),
reportUndocumented = false
),
)
close()
}

val process = ProcessBuilder(
"java", "-jar", cliJarFile.path, jsonPath
).redirectErrorStream(true).start()

val result = process.awaitProcessResult()
assertEquals(0, result.exitCode, "Expected exitCode 0 (Success)")

val extensionLoadedRegex = Regex("""Extension: org\.jetbrains\.dokka\.base\.DokkaBase""")
val amountOfExtensionsLoaded = extensionLoadedRegex.findAll(result.output).count()

assertTrue(
amountOfExtensionsLoaded > 10,
"Expected more than 10 extensions being present (found $amountOfExtensionsLoaded)"
)

val undocumentedReportRegex = Regex("""Undocumented:""")
val amountOfUndocumentedReports = undocumentedReportRegex.findAll(result.output).count()
assertTrue(
amountOfUndocumentedReports > 0,
"Expected at least one report of undocumented code (found $amountOfUndocumentedReports)"
)

assertTrue(dokkaOutputDir.isDirectory, "Missing dokka output directory")
}
}
@@ -0,0 +1,52 @@
package org.jetbrains.dokka.it.cli

fun jsonBuilder(
outputPath: String,
pluginsClasspath: String,
projectPath: String,
globalSourceLinks: String = "",
globalExternalDocumentationLinks: String = "",
globalPerPackageOptions: String = "",
reportUndocumented: Boolean = false

): String {
return """{
"moduleName": "Dokka Example",
"moduleVersion": null,
"outputDir": "$outputPath",
"pluginsClasspath": ["$pluginsClasspath"],
"cacheRoot": null,
"offlineMode": false,
"sourceLinks": [$globalSourceLinks],
"externalDocumentationLinks": [$globalExternalDocumentationLinks],
"perPackageOptions": [$globalPerPackageOptions],
"sourceSets": [
{
"displayName": "jvm",
"sourceSetID": {
"scopeId": ":dokkaHtml",
"sourceSetName": "main"
},
"sourceRoots": [
"$projectPath"
],
"dependentSourceSets": [],
"samples": [],
"includes": [],
"includeNonPublic": false,
"reportUndocumented": $reportUndocumented,
"skipEmptyPackages": true,
"skipDeprecated": false,
"jdkVersion": 8,
"sourceLinks": [],
"perPackageOptions": [],
"externalDocumentationLinks": [],
"noStdlibLink": false,
"noJdkLink": false,
"suppressedFiles": [],
"analysisPlatform": "jvm"
}
]
}
"""
}
Empty file.
2 changes: 1 addition & 1 deletion plugins/base/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions runners/cli/api/cli.api
Expand Up @@ -89,6 +89,7 @@ public final class org/jetbrains/dokka/GlobalArguments : org/jetbrains/dokka/Dok

public final class org/jetbrains/dokka/MainKt {
public static final fun defaultLinks (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Ljava/util/List;
public static final fun initializeConfiguration (Lorg/jetbrains/dokka/GlobalArguments;)Lorg/jetbrains/dokka/DokkaConfiguration;
public static final fun main ([Ljava/lang/String;)V
public static final fun parseLinks (Ljava/util/List;)Ljava/util/List;
}
Expand Down
1 change: 1 addition & 0 deletions runners/cli/build.gradle.kts
Expand Up @@ -10,6 +10,7 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.3")
implementation(project(":core"))
implementation(kotlin("stdlib"))
testImplementation(kotlin("test-junit"))
}

tasks {
Expand Down
27 changes: 17 additions & 10 deletions runners/cli/src/main/kotlin/cli/main.kt
Expand Up @@ -2,10 +2,7 @@ package org.jetbrains.dokka

import kotlinx.cli.*
import org.jetbrains.dokka.DokkaConfiguration.ExternalDocumentationLink
import org.jetbrains.dokka.utilities.DokkaConsoleLogger
import org.jetbrains.dokka.utilities.DokkaLogger
import org.jetbrains.dokka.utilities.LoggingLevel
import org.jetbrains.dokka.utilities.cast
import org.jetbrains.dokka.utilities.*
import java.io.*
import java.net.MalformedURLException
import java.net.URL
Expand Down Expand Up @@ -80,6 +77,7 @@ class GlobalArguments(args: Array<String>) : DokkaConfiguration {

private val _includes by parser.option(
ArgTypeFile,
fullName = "includes",
description = "Markdown files that would be displayed in multi-module page separated by the semicolon `;`)"
).delimiter(";")

Expand Down Expand Up @@ -406,14 +404,23 @@ fun parseLinks(links: List<String>): List<ExternalDocumentationLink> {
}
}

fun initializeConfiguration(globalArguments: GlobalArguments): DokkaConfiguration = if (globalArguments.json != null) {
val jsonContent = Paths.get(checkNotNull(globalArguments.json)).toFile().readText()
val globals = GlobalDokkaConfiguration(jsonContent)
val dokkaConfigurationImpl = DokkaConfigurationImpl(jsonContent)

dokkaConfigurationImpl.apply(globals).apply {
sourceSets.forEach {
it.externalDocumentationLinks.cast<MutableSet<ExternalDocumentationLink>>().addAll(defaultLinks(it))
}
}
} else {
globalArguments
}

fun main(args: Array<String>) {
val globalArguments = GlobalArguments(args)
val configuration = if (globalArguments.json != null)
DokkaConfigurationImpl(
Paths.get(checkNotNull(globalArguments.json)).toFile().readText()
)
else
globalArguments
val configuration = initializeConfiguration(globalArguments)
DokkaGenerator(configuration, globalArguments.logger).generate()
}

30 changes: 30 additions & 0 deletions runners/cli/src/test/kotlin/cli/CliTest.kt
@@ -0,0 +1,30 @@
package org.jetbrains.dokka

import junit.framework.Assert.assertTrue
import org.junit.Test
import java.lang.IllegalStateException
import java.nio.file.Paths
import kotlin.test.assertEquals

class CliIntegrationTest {

@Test
fun `should apply global settings to all source sets`() {
val jsonPath = Paths.get(javaClass.getResource("/my-file.json")?.toURI() ?: throw IllegalStateException("No JSON found!")).toFile().toString()
val globalArguments = GlobalArguments(arrayOf(jsonPath))

val configuration = initializeConfiguration(globalArguments)

configuration.sourceSets.forEach {
assertTrue(it.perPackageOptions.isNotEmpty())
assertTrue(it.sourceLinks.isNotEmpty())
assertTrue(it.externalDocumentationLinks.isNotEmpty())

assertTrue(it.externalDocumentationLinks.any { it.url.toString() == "https://docs.oracle.com/javase/8/docs/api/" })
assertEquals(it.sourceLinks.single().localDirectory, "/home/Vadim.Mishenev/dokka/examples/cli/src/main/kotlin")
assertEquals(it.perPackageOptions.single().matchingRegex, "my-custom-regex")
}

}

}

0 comments on commit 6b72925

Please sign in to comment.