-
Notifications
You must be signed in to change notification settings - Fork 418
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement custom code block renderers support
- Loading branch information
Showing
5 changed files
with
258 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
plugins/base/src/main/kotlin/renderers/html/HtmlCodeBlockRenderer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. | ||
*/ | ||
|
||
package org.jetbrains.dokka.base.renderers.html | ||
|
||
import kotlinx.html.FlowContent | ||
|
||
/** | ||
* Provides an ability to override code blocks rendering differently dependent on the code language. | ||
* | ||
* Multiple renderers can be installed to support different languages in an independent way. | ||
*/ | ||
public interface HtmlCodeBlockRenderer { | ||
|
||
/** | ||
* Whether this renderer supports given [language]. | ||
* | ||
* [code] can be useful to determine applicability if [language] is not provided (empty string) | ||
*/ | ||
public fun isApplicable(language: String, code: String): Boolean | ||
|
||
/** | ||
* Defines how to render [code] for specified [language] via HTML tags | ||
*/ | ||
public fun FlowContent.buildCodeBlock(language: String, code: String) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
202 changes: 202 additions & 0 deletions
202
plugins/base/src/test/kotlin/renderers/html/CodeBlocksTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
/* | ||
* Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. | ||
*/ | ||
|
||
package renderers.html | ||
|
||
import kotlinx.html.FlowContent | ||
import kotlinx.html.div | ||
import org.jetbrains.dokka.base.DokkaBase | ||
import org.jetbrains.dokka.base.renderers.html.HtmlCodeBlockRenderer | ||
import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest | ||
import org.jetbrains.dokka.plugability.DokkaPlugin | ||
import org.jetbrains.dokka.plugability.DokkaPluginApiPreview | ||
import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement | ||
import signatures.renderedContent | ||
import utils.TestOutputWriter | ||
import utils.TestOutputWriterPlugin | ||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
|
||
class CodeBlocksTest : BaseAbstractTest() { | ||
|
||
private val configuration = dokkaConfiguration { | ||
sourceSets { | ||
sourceSet { | ||
sourceRoots = listOf("src/") | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
fun `default code block rendering`() = testCode( | ||
""" | ||
/src/test.kt | ||
package test | ||
/** | ||
* Hello, world! | ||
* | ||
* ```kotlin | ||
* test("hello kotlin") | ||
* ``` | ||
* | ||
* ```custom | ||
* test("hello custom") | ||
* ``` | ||
* | ||
* ```other | ||
* test("hello other") | ||
* ``` | ||
*/ | ||
fun test(string: String) {} | ||
""".trimIndent(), | ||
emptyList() | ||
) { | ||
val content = renderedContent("root/test/test.html") | ||
|
||
// by default, every code block is rendered as an element with `lang-XXX` class, | ||
// where XXX=language of code block | ||
assertEquals( | ||
"""test("hello kotlin")""", | ||
content.getElementsByClass("lang-kotlin").singleOrNull()?.text() | ||
) | ||
assertEquals( | ||
"""test("hello custom")""", | ||
content.getElementsByClass("lang-custom").singleOrNull()?.text() | ||
) | ||
assertEquals( | ||
"""test("hello other")""", | ||
content.getElementsByClass("lang-other").singleOrNull()?.text() | ||
) | ||
} | ||
|
||
@Test | ||
fun `code block rendering with custom renderer`() = testCode( | ||
""" | ||
/src/test.kt | ||
package test | ||
/** | ||
* Hello, world! | ||
* | ||
* ```kotlin | ||
* test("hello kotlin") | ||
* ``` | ||
* | ||
* ```custom | ||
* test("hello custom") | ||
* ``` | ||
* | ||
* ```other | ||
* test("hello other") | ||
* ``` | ||
*/ | ||
fun test(string: String) {} | ||
""".trimIndent(), | ||
listOf(CustomPlugin(applyOtherRenderer = false)) // we add only one custom renderer | ||
) { | ||
val content = renderedContent("root/test/test.html") | ||
assertEquals( | ||
"""test("hello kotlin")""", | ||
content.getElementsByClass("lang-kotlin").singleOrNull()?.text() | ||
) | ||
assertEquals( | ||
"""test("hello custom")""", | ||
content.getElementsByClass("custom-language-block").singleOrNull()?.text() | ||
) | ||
assertEquals( | ||
"""test("hello other")""", | ||
content.getElementsByClass("lang-other").singleOrNull()?.text() | ||
) | ||
} | ||
|
||
@Test | ||
fun `code block rendering with multiple custom renderers`() = testCode( | ||
""" | ||
/src/test.kt | ||
package test | ||
/** | ||
* Hello, world! | ||
* | ||
* ```kotlin | ||
* test("hello kotlin") | ||
* ``` | ||
* | ||
* ```custom | ||
* test("hello custom") | ||
* ``` | ||
* | ||
* ```other | ||
* test("hello other") | ||
* ``` | ||
*/ | ||
fun test(string: String) {} | ||
""".trimIndent(), | ||
listOf(CustomPlugin(applyOtherRenderer = true)) | ||
) { | ||
val content = renderedContent("root/test/test.html") | ||
assertEquals( | ||
"""test("hello kotlin")""", | ||
content.getElementsByClass("lang-kotlin").singleOrNull()?.text() | ||
) | ||
assertEquals( | ||
"""test("hello custom")""", | ||
content.getElementsByClass("custom-language-block").singleOrNull()?.text() | ||
) | ||
assertEquals( | ||
"""test("hello other")""", | ||
content.getElementsByClass("other-language-block").singleOrNull()?.text() | ||
) | ||
} | ||
|
||
private fun testCode( | ||
source: String, | ||
pluginOverrides: List<DokkaPlugin>, | ||
block: TestOutputWriter.() -> Unit | ||
) { | ||
val writerPlugin = TestOutputWriterPlugin() | ||
testInline(source, configuration, pluginOverrides = pluginOverrides + listOf(writerPlugin)) { | ||
renderingStage = { _, _ -> | ||
writerPlugin.writer.block() | ||
} | ||
} | ||
} | ||
|
||
private object CustomHtmlBlockRenderer : HtmlCodeBlockRenderer { | ||
override fun isApplicable(language: String, code: String): Boolean = language == "custom" | ||
|
||
override fun FlowContent.buildCodeBlock(language: String, code: String) { | ||
div("custom-language-block") { | ||
text(code) | ||
} | ||
} | ||
} | ||
|
||
private object CustomOtherHtmlBlockRenderer : HtmlCodeBlockRenderer { | ||
override fun isApplicable(language: String, code: String): Boolean = language == "other" | ||
|
||
override fun FlowContent.buildCodeBlock(language: String, code: String) { | ||
div("other-language-block") { | ||
text(code) | ||
} | ||
} | ||
} | ||
|
||
class CustomPlugin(applyOtherRenderer: Boolean) : DokkaPlugin() { | ||
val customHtmlBlockRenderer by extending { | ||
plugin<DokkaBase>().htmlCodeBlockRenderers with CustomHtmlBlockRenderer | ||
} | ||
|
||
val otherHtmlBlockRenderer by extending { | ||
plugin<DokkaBase>().htmlCodeBlockRenderers with CustomOtherHtmlBlockRenderer applyIf { | ||
applyOtherRenderer | ||
} | ||
} | ||
|
||
@OptIn(DokkaPluginApiPreview::class) | ||
override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = | ||
PluginApiPreviewAcknowledgement | ||
} | ||
} |