aboutsummaryrefslogtreecommitdiff
path: root/plugins/templating/src
diff options
context:
space:
mode:
authorVadim Mishenev <vad-mishenev@yandex.ru>2022-02-22 12:56:41 +0300
committerGitHub <noreply@github.com>2022-02-22 12:56:41 +0300
commitb9b1b588fad604c0cfc3e481f48338437dcaba5f (patch)
tree7c6a04d95f0f9b0c931b25dca91f1cf411f01105 /plugins/templating/src
parentc44bf5487bd32f90a4576859548f1db0e9355a07 (diff)
downloaddokka-b9b1b588fad604c0cfc3e481f48338437dcaba5f.tar.gz
dokka-b9b1b588fad604c0cfc3e481f48338437dcaba5f.tar.bz2
dokka-b9b1b588fad604c0cfc3e481f48338437dcaba5f.zip
Fix HTML head and favicon in multi-module projects (#2365)
Diffstat (limited to 'plugins/templating/src')
-rw-r--r--plugins/templating/src/main/kotlin/templates/AddToNavigationCommandHandler.kt4
-rw-r--r--plugins/templating/src/main/kotlin/templates/CommandHandler.kt12
-rw-r--r--plugins/templating/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt60
-rw-r--r--plugins/templating/src/main/kotlin/templates/ReplaceVersionCommandHandler.kt8
-rw-r--r--plugins/templating/src/main/kotlin/templates/SubstitutionCommandHandler.kt29
-rw-r--r--plugins/templating/src/main/kotlin/templates/TemplateProcessor.kt4
-rw-r--r--plugins/templating/src/main/kotlin/templates/TemplatingPlugin.kt1
-rw-r--r--plugins/templating/src/test/kotlin/templates/SubstitutionCommandResolutionTest.kt62
8 files changed, 146 insertions, 34 deletions
diff --git a/plugins/templating/src/main/kotlin/templates/AddToNavigationCommandHandler.kt b/plugins/templating/src/main/kotlin/templates/AddToNavigationCommandHandler.kt
index 3e7e1290..9531c279 100644
--- a/plugins/templating/src/main/kotlin/templates/AddToNavigationCommandHandler.kt
+++ b/plugins/templating/src/main/kotlin/templates/AddToNavigationCommandHandler.kt
@@ -13,12 +13,12 @@ import java.util.concurrent.ConcurrentHashMap
class AddToNavigationCommandHandler(val context: DokkaContext) : CommandHandler {
private val navigationFragments = ConcurrentHashMap<String, Element>()
- override fun handleCommand(element: Element, command: Command, input: File, output: File) {
+ override fun handleCommandAsTag(command: Command, body: Element, input: File, output: File) {
command as AddToNavigationCommand
context.configuration.modules.find { it.name == command.moduleName }
?.relativePathToOutputDirectory
?.relativeToOrSelf(context.configuration.outputDir)
- ?.let { key -> navigationFragments[key.toString()] = element }
+ ?.let { key -> navigationFragments[key.toString()] = body }
}
override fun canHandle(command: Command) = command is AddToNavigationCommand
diff --git a/plugins/templating/src/main/kotlin/templates/CommandHandler.kt b/plugins/templating/src/main/kotlin/templates/CommandHandler.kt
index d72092a1..1956310b 100644
--- a/plugins/templating/src/main/kotlin/templates/CommandHandler.kt
+++ b/plugins/templating/src/main/kotlin/templates/CommandHandler.kt
@@ -2,10 +2,18 @@ package org.jetbrains.dokka.templates
import org.jetbrains.dokka.base.templating.Command
import org.jsoup.nodes.Element
+import org.jsoup.nodes.Node
import java.io.File
-interface CommandHandler {
- fun handleCommand(element: Element, command: Command, input: File, output: File)
+
+interface CommandHandler {
+ @Deprecated("This was renamed to handleCommandAsTag", ReplaceWith("handleCommandAsTag(command, element, input, output)"))
+ fun handleCommand(element: Element, command: Command, input: File, output: File) { }
+
+ @Suppress("DEPRECATION")
+ fun handleCommandAsTag(command: Command, body: Element, input: File, output: File) =
+ handleCommand(body, command, input, output)
+ fun handleCommandAsComment(command: Command, body: List<Node>, input: File, output: File) { }
fun canHandle(command: Command): Boolean
fun finish(output: File) {}
} \ No newline at end of file
diff --git a/plugins/templating/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt b/plugins/templating/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt
index 2b4951a1..7ef4cb10 100644
--- a/plugins/templating/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt
+++ b/plugins/templating/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt
@@ -1,13 +1,19 @@
package org.jetbrains.dokka.templates
import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.base.renderers.html.TEMPLATE_COMMAND_BEGIN_BORDER
+import org.jetbrains.dokka.base.renderers.html.TEMPLATE_COMMAND_END_BORDER
+import org.jetbrains.dokka.base.renderers.html.TEMPLATE_COMMAND_SEPARATOR
import org.jetbrains.dokka.base.templating.Command
import org.jetbrains.dokka.base.templating.parseJson
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.plugin
import org.jetbrains.dokka.plugability.query
import org.jsoup.Jsoup
+import org.jsoup.nodes.Comment
import org.jsoup.nodes.Element
+import org.jsoup.nodes.Node
+import org.jsoup.nodes.TextNode
import java.io.File
import java.nio.file.Files
@@ -20,20 +26,68 @@ class DirectiveBasedHtmlTemplateProcessingStrategy(private val context: DokkaCon
if (input.isFile && input.extension == "html") {
val document = Jsoup.parse(input, "UTF-8")
document.outputSettings().indentAmount(0).prettyPrint(false)
+
document.select("dokka-template-command").forEach {
- handleCommand(it, parseJson(it.attr("data")), input, output)
+ handleCommandAsTag(it, parseJson(it.attr("data")), input, output)
+ }
+ extractCommandsFromComments(document) { command, body ->
+ val bodyTrimed =
+ body.dropWhile { node -> (node is TextNode && node.isBlank).also { if (it) node.remove() } }
+ .dropLastWhile { node -> (node is TextNode && node.isBlank).also { if (it) node.remove() } }
+ handleCommandAsComment(command, bodyTrimed, input, output)
}
+
Files.write(output.toPath(), listOf(document.outerHtml()))
true
} else false
- fun handleCommand(element: Element, command: Command, input: File, output: File) {
+ fun handleCommandAsTag(element: Element, command: Command, input: File, output: File) {
+ traverseHandlers(command) { handleCommandAsTag(command, element, input, output) }
+ }
+
+ fun handleCommandAsComment(command: Command, body: List<Node>, input: File, output: File) {
+ traverseHandlers(command) { handleCommandAsComment(command, body, input, output) }
+ }
+
+ private fun traverseHandlers(command: Command, action: CommandHandler.() -> Unit) {
val handlers = directiveBasedCommandHandlers.filter { it.canHandle(command) }
if (handlers.isEmpty())
context.logger.warn("Unknown templating command $command")
else
- handlers.forEach { it.handleCommand(element, command, input, output) }
+ handlers.forEach(action)
+ }
+ private fun extractCommandsFromComments(
+ node: Node,
+ startFrom: Int = 0,
+ handler: (command: Command, body: List<Node>) -> Unit
+ ) {
+ val nodes: MutableList<Node> = mutableListOf()
+ var lastStartBorder: Comment? = null
+ var firstStartBorder: Comment? = null
+ for (index in startFrom until node.childNodeSize()) {
+ when (val currentChild = node.childNode(index)) {
+ is Comment -> if (currentChild.data?.startsWith(TEMPLATE_COMMAND_BEGIN_BORDER) == true) {
+ lastStartBorder = currentChild
+ firstStartBorder = firstStartBorder ?: currentChild
+ nodes.clear()
+ } else if (lastStartBorder != null && currentChild.data?.startsWith(TEMPLATE_COMMAND_END_BORDER) == true) {
+ lastStartBorder.remove()
+ val cmd: Command? =
+ lastStartBorder.data?.removePrefix("$TEMPLATE_COMMAND_BEGIN_BORDER$TEMPLATE_COMMAND_SEPARATOR")?.let { parseJson(it) }
+ cmd?.let { handler(it, nodes) }
+ currentChild.remove()
+ extractCommandsFromComments(node, firstStartBorder?.siblingIndex() ?: 0, handler)
+ return
+ } else {
+ if (lastStartBorder != null) nodes.add(currentChild)
+ }
+ else -> {
+ extractCommandsFromComments(currentChild, handler = handler)
+ if (lastStartBorder != null) nodes.add(currentChild)
+ }
+ }
+ }
}
override fun finish(output: File) {
diff --git a/plugins/templating/src/main/kotlin/templates/ReplaceVersionCommandHandler.kt b/plugins/templating/src/main/kotlin/templates/ReplaceVersionCommandHandler.kt
index 8035fc83..02570849 100644
--- a/plugins/templating/src/main/kotlin/templates/ReplaceVersionCommandHandler.kt
+++ b/plugins/templating/src/main/kotlin/templates/ReplaceVersionCommandHandler.kt
@@ -12,10 +12,10 @@ class ReplaceVersionCommandHandler(private val context: DokkaContext) : CommandH
override fun canHandle(command: Command): Boolean = command is ReplaceVersionsCommand
- override fun handleCommand(element: Element, command: Command, input: File, output: File) {
- val position = element.elementSiblingIndex()
- val parent = element.parent()
- element.remove()
+ override fun handleCommandAsTag(command: Command, body: Element, input: File, output: File) {
+ val position = body.elementSiblingIndex()
+ val parent = body.parent()
+ body.remove()
context.configuration.moduleVersion?.takeIf { it.isNotEmpty() }
?.let { parent.insertChildren(position, TextNode(it)) }
}
diff --git a/plugins/templating/src/main/kotlin/templates/SubstitutionCommandHandler.kt b/plugins/templating/src/main/kotlin/templates/SubstitutionCommandHandler.kt
index c7b15137..178f52dc 100644
--- a/plugins/templating/src/main/kotlin/templates/SubstitutionCommandHandler.kt
+++ b/plugins/templating/src/main/kotlin/templates/SubstitutionCommandHandler.kt
@@ -13,28 +13,35 @@ import java.io.File
class SubstitutionCommandHandler(context: DokkaContext) : CommandHandler {
- override fun handleCommand(element: Element, command: Command, input: File, output: File) {
+ override fun handleCommandAsTag(command: Command, body: Element, input: File, output: File) {
command as SubstitutionCommand
- substitute(element, TemplatingContext(input, output, element, command))
+ val childrenCopy = body.children().toList()
+ substitute(childrenCopy, TemplatingContext(input, output, childrenCopy, command))
+
+ val position = body.elementSiblingIndex()
+ val parent = body.parent()
+ body.remove()
+
+ parent?.insertChildren(position, childrenCopy)
+ }
+
+ override fun handleCommandAsComment(command: Command, body: List<Node>, input: File, output: File) {
+ command as SubstitutionCommand
+ substitute(body, TemplatingContext(input, output, body, command))
}
override fun canHandle(command: Command): Boolean = command is SubstitutionCommand
+ override fun finish(output: File) { }
+
private val substitutors = context.plugin<TemplatingPlugin>().query { substitutor }
private fun findSubstitution(commandContext: TemplatingContext<SubstitutionCommand>, match: MatchResult): String =
substitutors.asSequence().mapNotNull { it.trySubstitute(commandContext, match) }.firstOrNull() ?: match.value
- private fun substitute(element: Element, commandContext: TemplatingContext<SubstitutionCommand>) {
+ private fun substitute(elements: List<Node>, commandContext: TemplatingContext<SubstitutionCommand>) {
val regex = commandContext.command.pattern.toRegex()
- element.children().forEach { it.traverseToSubstitute(regex, commandContext) }
-
- val childrenCopy = element.children().toList()
- val position = element.elementSiblingIndex()
- val parent = element.parent()
- element.remove()
-
- parent.insertChildren(position, childrenCopy)
+ elements.forEach { it.traverseToSubstitute(regex, commandContext) }
}
private fun Node.traverseToSubstitute(regex: Regex, commandContext: TemplatingContext<SubstitutionCommand>) {
diff --git a/plugins/templating/src/main/kotlin/templates/TemplateProcessor.kt b/plugins/templating/src/main/kotlin/templates/TemplateProcessor.kt
index 5f36530b..01c10067 100644
--- a/plugins/templating/src/main/kotlin/templates/TemplateProcessor.kt
+++ b/plugins/templating/src/main/kotlin/templates/TemplateProcessor.kt
@@ -12,7 +12,7 @@ import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.plugin
import org.jetbrains.dokka.plugability.query
import org.jetbrains.dokka.plugability.querySingle
-import org.jsoup.nodes.Element
+import org.jsoup.nodes.Node
import java.io.File
interface TemplateProcessor
@@ -88,7 +88,7 @@ class DefaultMultiModuleTemplateProcessor(
data class TemplatingContext<out T : Command>(
val input: File,
val output: File,
- val element: Element,
+ val body: List<Node>,
val command: T,
)
diff --git a/plugins/templating/src/main/kotlin/templates/TemplatingPlugin.kt b/plugins/templating/src/main/kotlin/templates/TemplatingPlugin.kt
index 7001b1ba..aea01970 100644
--- a/plugins/templating/src/main/kotlin/templates/TemplatingPlugin.kt
+++ b/plugins/templating/src/main/kotlin/templates/TemplatingPlugin.kt
@@ -7,6 +7,7 @@ import templates.ProjectNameSubstitutor
import templates.ReplaceVersionCommandHandler
import templates.SourcesetDependencyProcessingStrategy
+@Suppress("unused")
class TemplatingPlugin : DokkaPlugin() {
val submoduleTemplateProcessor by extensionPoint<SubmoduleTemplateProcessor>()
diff --git a/plugins/templating/src/test/kotlin/templates/SubstitutionCommandResolutionTest.kt b/plugins/templating/src/test/kotlin/templates/SubstitutionCommandResolutionTest.kt
index ce2a8afd..44acf340 100644
--- a/plugins/templating/src/test/kotlin/templates/SubstitutionCommandResolutionTest.kt
+++ b/plugins/templating/src/test/kotlin/templates/SubstitutionCommandResolutionTest.kt
@@ -3,13 +3,15 @@ package org.jetbrains.dokka.templates
import kotlinx.html.a
import kotlinx.html.div
import kotlinx.html.id
+import kotlinx.html.span
import kotlinx.html.stream.createHTML
import org.jetbrains.dokka.DokkaModuleDescriptionImpl
import org.jetbrains.dokka.base.renderers.html.templateCommand
+import org.jetbrains.dokka.base.renderers.html.templateCommandAsHtmlComment
import org.jetbrains.dokka.base.templating.PathToRootSubstitutionCommand
import org.junit.Rule
-import org.junit.rules.TemporaryFolder
import org.junit.jupiter.api.Test
+import org.junit.rules.TemporaryFolder
import utils.assertHtmlEqualsIgnoringWhitespace
import java.io.File
@@ -36,7 +38,56 @@ class SubstitutionCommandResolutionTest : TemplatingAbstractTest() {
id = "logo"
}
}
+ checkSubstitutedResult(template, expected)
+ }
+
+ @Test
+ fun `should handle PathToRootCommand as HTML comment`() {
+ val template = createHTML().span {
+ templateCommandAsHtmlComment(PathToRootSubstitutionCommand(pattern = "###", default = "default")) {
+ this@span.a {
+ href = "###index.html"
+ div {
+ id = "logo"
+ }
+ }
+ templateCommandAsHtmlComment(PathToRootSubstitutionCommand(pattern = "####", default = "default")) {
+ this@span.a {
+ href = "####index.html"
+ div {
+ id = "logo"
+ }
+ }
+ }
+ }
+ }
+
+ val expected = createHTML().span {
+ a {
+ href = "../index.html"
+ div {
+ id = "logo"
+ }
+ }
+ a {
+ href = "../index.html"
+ div {
+ id = "logo"
+ }
+ }
+ }
+ checkSubstitutedResult(template, expected)
+ }
+
+ private fun createDirectoriesAndWriteContent(content: String): File {
+ folder.create()
+ val module1 = folder.newFolder("module1")
+ val module1Content = module1.resolve("index.html")
+ module1Content.writeText(content)
+ return module1Content
+ }
+ private fun checkSubstitutedResult(template: String, expected:String) {
val testedFile = createDirectoriesAndWriteContent(template)
val configuration = dokkaConfiguration {
@@ -57,13 +108,4 @@ class SubstitutionCommandResolutionTest : TemplatingAbstractTest() {
}
}
}
-
- private fun createDirectoriesAndWriteContent(content: String): File {
- folder.create()
- val module1 = folder.newFolder("module1")
- val module1Content = module1.resolve("index.html")
- module1Content.writeText(content)
- return module1Content
- }
-
}