From b9b1b588fad604c0cfc3e481f48338437dcaba5f Mon Sep 17 00:00:00 2001 From: Vadim Mishenev Date: Tue, 22 Feb 2022 12:56:41 +0300 Subject: Fix HTML head and favicon in multi-module projects (#2365) --- plugins/all-modules-page/api/all-modules-page.api | 2 + .../src/main/kotlin/ResolveLinkCommandHandler.kt | 10 ++-- plugins/base/api/base.api | 5 ++ .../src/main/kotlin/renderers/html/HtmlRenderer.kt | 22 ++++---- .../base/src/main/kotlin/renderers/html/Tags.kt | 12 +++++ .../command/consumers/ReplaceVersionsConsumer.kt | 2 +- .../test/kotlin/resourceLinks/ResourceLinksTest.kt | 7 +-- plugins/templating/api/templating.api | 24 ++++++--- .../templates/AddToNavigationCommandHandler.kt | 4 +- .../src/main/kotlin/templates/CommandHandler.kt | 12 ++++- .../templates/DirectiveBasedTemplateProcessing.kt | 60 +++++++++++++++++++-- .../templates/ReplaceVersionCommandHandler.kt | 8 +-- .../kotlin/templates/SubstitutionCommandHandler.kt | 29 ++++++---- .../src/main/kotlin/templates/TemplateProcessor.kt | 4 +- .../src/main/kotlin/templates/TemplatingPlugin.kt | 1 + .../templates/SubstitutionCommandResolutionTest.kt | 62 ++++++++++++++++++---- plugins/versioning/api/versioning.api | 2 + .../kotlin/versioning/ReplaceVersionsCommand.kt | 2 +- 18 files changed, 208 insertions(+), 60 deletions(-) diff --git a/plugins/all-modules-page/api/all-modules-page.api b/plugins/all-modules-page/api/all-modules-page.api index 9d8bca9b..0763d75a 100644 --- a/plugins/all-modules-page/api/all-modules-page.api +++ b/plugins/all-modules-page/api/all-modules-page.api @@ -83,5 +83,7 @@ public final class org/jetbrains/dokka/allModulesPage/ResolveLinkCommandHandler public fun canHandle (Lorg/jetbrains/dokka/base/templating/Command;)Z public fun finish (Ljava/io/File;)V public fun handleCommand (Lorg/jsoup/nodes/Element;Lorg/jetbrains/dokka/base/templating/Command;Ljava/io/File;Ljava/io/File;)V + public fun handleCommandAsComment (Lorg/jetbrains/dokka/base/templating/Command;Ljava/util/List;Ljava/io/File;Ljava/io/File;)V + public fun handleCommandAsTag (Lorg/jetbrains/dokka/base/templating/Command;Lorg/jsoup/nodes/Element;Ljava/io/File;Ljava/io/File;)V } diff --git a/plugins/all-modules-page/src/main/kotlin/ResolveLinkCommandHandler.kt b/plugins/all-modules-page/src/main/kotlin/ResolveLinkCommandHandler.kt index e881a5ab..7976ba5a 100644 --- a/plugins/all-modules-page/src/main/kotlin/ResolveLinkCommandHandler.kt +++ b/plugins/all-modules-page/src/main/kotlin/ResolveLinkCommandHandler.kt @@ -16,29 +16,29 @@ class ResolveLinkCommandHandler(context: DokkaContext) : CommandHandler { private val externalModuleLinkResolver = context.plugin().querySingle { externalModuleLinkResolver } - override fun handleCommand(element: Element, command: Command, input: File, output: File) { + override fun handleCommandAsTag(command: Command, body: Element, input: File, output: File) { command as ResolveLinkCommand val link = externalModuleLinkResolver.resolve(command.dri, output) if (link == null) { - val children = element.childNodes().toList() + val children = body.childNodes().toList() val attributes = Attributes().apply { put("data-unresolved-link", command.dri.toString()) } val el = Element(Tag.valueOf("span"), "", attributes).apply { children.forEach { ch -> appendChild(ch) } } - element.replaceWith(el) + body.replaceWith(el) return } val attributes = Attributes().apply { put("href", link) } - val children = element.childNodes().toList() + val children = body.childNodes().toList() val el = Element(Tag.valueOf("a"), "", attributes).apply { children.forEach { ch -> appendChild(ch) } } - element.replaceWith(el) + body.replaceWith(el) } override fun canHandle(command: Command): Boolean = command is ResolveLinkCommand diff --git a/plugins/base/api/base.api b/plugins/base/api/base.api index 40ee6813..a38c2cfd 100644 --- a/plugins/base/api/base.api +++ b/plugins/base/api/base.api @@ -516,6 +516,9 @@ public final class org/jetbrains/dokka/base/renderers/html/StylesInstaller : org } public final class org/jetbrains/dokka/base/renderers/html/TagsKt { + public static final field TEMPLATE_COMMAND_BEGIN_BORDER Ljava/lang/String; + public static final field TEMPLATE_COMMAND_END_BORDER Ljava/lang/String; + public static final field TEMPLATE_COMMAND_SEPARATOR Ljava/lang/String; public static final fun buildAsInnerHtml (Lkotlin/jvm/functions/Function1;)Ljava/lang/String; public static final fun strike (Lkotlinx/html/FlowOrPhrasingContent;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V public static synthetic fun strike$default (Lkotlinx/html/FlowOrPhrasingContent;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V @@ -523,6 +526,8 @@ public final class org/jetbrains/dokka/base/renderers/html/TagsKt { public static final fun templateCommand (Lkotlinx/html/TagConsumer;Lorg/jetbrains/dokka/base/templating/Command;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun templateCommand$default (Lkotlinx/html/FlowOrMetaDataContent;Lorg/jetbrains/dokka/base/templating/Command;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public static synthetic fun templateCommand$default (Lkotlinx/html/TagConsumer;Lorg/jetbrains/dokka/base/templating/Command;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun templateCommandAsHtmlComment (Lkotlinx/html/FlowOrMetaDataContent;Lorg/jetbrains/dokka/base/templating/Command;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun templateCommandAsHtmlComment$default (Lkotlinx/html/FlowOrMetaDataContent;Lorg/jetbrains/dokka/base/templating/Command;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public static final fun templateCommandFor (Lorg/jetbrains/dokka/base/templating/Command;Lkotlinx/html/TagConsumer;)Lorg/jetbrains/dokka/base/renderers/html/TemplateCommand; public static final fun wbr (Lkotlinx/html/FlowOrPhrasingContent;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V public static synthetic fun wbr$default (Lkotlinx/html/FlowOrPhrasingContent;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt index 4dd020ce..0ba085cd 100644 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt @@ -23,6 +23,8 @@ import org.jetbrains.dokka.utilities.htmlEscape import org.jetbrains.kotlin.utils.addIfNotNull import java.net.URI +internal const val TEMPLATE_REPLACEMENT: String = "###" + open class HtmlRenderer( context: DokkaContext ) : DefaultRenderer(context) { @@ -779,11 +781,11 @@ open class HtmlRenderer( head { meta(name = "viewport", content = "width=device-width, initial-scale=1", charset = "UTF-8") title(page.name) - templateCommand(PathToRootSubstitutionCommand("###", default = pathToRoot)) { - link(href = "###images/logo-icon.svg", rel = "icon", type = "image/svg") + templateCommandAsHtmlComment(PathToRootSubstitutionCommand(TEMPLATE_REPLACEMENT, default = pathToRoot)) { + link(href = "${TEMPLATE_REPLACEMENT}images/logo-icon.svg", rel = "icon", type = "image/svg") } - templateCommand(PathToRootSubstitutionCommand("###", default = pathToRoot)) { - script { unsafe { +"""var pathToRoot = "###";""" } } + templateCommandAsHtmlComment(PathToRootSubstitutionCommand(TEMPLATE_REPLACEMENT, default = pathToRoot)) { + script { unsafe { +"""var pathToRoot = "$TEMPLATE_REPLACEMENT";""" } } } // This script doesn't need to be there but it is nice to have since app in dark mode doesn't 'blink' (class is added before it is rendered) script { @@ -804,10 +806,10 @@ open class HtmlRenderer( rel = LinkRel.stylesheet, href = it ) - else templateCommand(PathToRootSubstitutionCommand("###", default = pathToRoot)) { + else templateCommandAsHtmlComment(PathToRootSubstitutionCommand(TEMPLATE_REPLACEMENT, default = pathToRoot)) { link( rel = LinkRel.stylesheet, - href = "###$it" + href = TEMPLATE_REPLACEMENT + it ) } it.substringBefore('?').substringAfterLast('.') == "js" -> @@ -816,10 +818,10 @@ open class HtmlRenderer( src = it ) { async = true - } else templateCommand(PathToRootSubstitutionCommand("###", default = pathToRoot)) { + } else templateCommandAsHtmlComment(PathToRootSubstitutionCommand(TEMPLATE_REPLACEMENT, default = pathToRoot)) { script( type = ScriptType.textJavaScript, - src = "###$it" + src = TEMPLATE_REPLACEMENT + it ) { if (it == "scripts/main.js") defer = true @@ -828,8 +830,8 @@ open class HtmlRenderer( } } it.isImage() -> if (it.isAbsolute) link(href = it) - else templateCommand(PathToRootSubstitutionCommand("###", default = pathToRoot)) { - link(href = "###$it") + else templateCommandAsHtmlComment(PathToRootSubstitutionCommand(TEMPLATE_REPLACEMENT, default = pathToRoot)) { + link(href = TEMPLATE_REPLACEMENT + it) } else -> unsafe { +it } } diff --git a/plugins/base/src/main/kotlin/renderers/html/Tags.kt b/plugins/base/src/main/kotlin/renderers/html/Tags.kt index a23c7bf1..94a53c27 100644 --- a/plugins/base/src/main/kotlin/renderers/html/Tags.kt +++ b/plugins/base/src/main/kotlin/renderers/html/Tags.kt @@ -26,6 +26,18 @@ inline fun FlowOrPhrasingContent.strike(classes : String? = null, crossinline bl open class STRIKE(initialAttributes: Map, override val consumer: TagConsumer<*>) : HTMLTag("strike", consumer, initialAttributes, null, false, false), HtmlBlockInlineTag +const val TEMPLATE_COMMAND_SEPARATOR = ":" +const val TEMPLATE_COMMAND_BEGIN_BORDER = "[+]cmd" +const val TEMPLATE_COMMAND_END_BORDER = "[-]cmd" + +fun FlowOrMetaDataContent.templateCommandAsHtmlComment(data: Command, block: FlowOrMetaDataContent.() -> Unit = {}): Unit = + (consumer as? ImmediateResolutionTagConsumer)?.processCommand(data, block) + ?: let{ + comment( "$TEMPLATE_COMMAND_BEGIN_BORDER$TEMPLATE_COMMAND_SEPARATOR${toJsonString(data)}") + block() + comment(TEMPLATE_COMMAND_END_BORDER) + } + fun FlowOrMetaDataContent.templateCommand(data: Command, block: TemplateBlock = {}): Unit = (consumer as? ImmediateResolutionTagConsumer)?.processCommand(data, block) ?: TemplateCommand(attributesMapOf("data", toJsonString(data)), consumer).visit(block) diff --git a/plugins/base/src/main/kotlin/renderers/html/command/consumers/ReplaceVersionsConsumer.kt b/plugins/base/src/main/kotlin/renderers/html/command/consumers/ReplaceVersionsConsumer.kt index ea8cde0f..bbc33164 100644 --- a/plugins/base/src/main/kotlin/renderers/html/command/consumers/ReplaceVersionsConsumer.kt +++ b/plugins/base/src/main/kotlin/renderers/html/command/consumers/ReplaceVersionsConsumer.kt @@ -19,7 +19,7 @@ class ReplaceVersionsConsumer(private val context: DokkaContext) : ImmediateHtml } override fun processCommandAndFinalize(command: Command, block: TemplateBlock, tagConsumer: ImmediateResolutionTagConsumer): R { - PathToRootConsumer.processCommand(command, block, tagConsumer) + processCommand(command, block, tagConsumer) return tagConsumer.finalize() } } \ No newline at end of file diff --git a/plugins/base/src/test/kotlin/resourceLinks/ResourceLinksTest.kt b/plugins/base/src/test/kotlin/resourceLinks/ResourceLinksTest.kt index 9b010b56..fb9ec10e 100644 --- a/plugins/base/src/test/kotlin/resourceLinks/ResourceLinksTest.kt +++ b/plugins/base/src/test/kotlin/resourceLinks/ResourceLinksTest.kt @@ -4,10 +4,11 @@ import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.PluginConfigurationImpl import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.DokkaBaseConfiguration +import org.jetbrains.dokka.base.renderers.html.TEMPLATE_REPLACEMENT import org.jetbrains.dokka.base.templating.toJsonString +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest import org.jetbrains.dokka.pages.RootPageNode import org.jetbrains.dokka.plugability.DokkaPlugin -import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest import org.jetbrains.dokka.transformers.pages.PageTransformer import org.jsoup.Jsoup import org.junit.jupiter.api.Test @@ -125,11 +126,11 @@ class ResourceLinksTest : BaseAbstractTest() { if (isMultiModule) { Jsoup .parse(writerPlugin.writer.contents["example.html"]) - .body() + .head() .select("link, script") .let { listOf("styles/customStyle.css").forEach { r -> - assert(it.`is`("[href=###$r]")) + assert(it.`is`("[href=$TEMPLATE_REPLACEMENT$r]")) } } } else { diff --git a/plugins/templating/api/templating.api b/plugins/templating/api/templating.api index a0fb5122..aedd8ef3 100644 --- a/plugins/templating/api/templating.api +++ b/plugins/templating/api/templating.api @@ -28,16 +28,23 @@ public final class org/jetbrains/dokka/templates/AddToNavigationCommandHandler : public fun finish (Ljava/io/File;)V public final fun getContext ()Lorg/jetbrains/dokka/plugability/DokkaContext; public fun handleCommand (Lorg/jsoup/nodes/Element;Lorg/jetbrains/dokka/base/templating/Command;Ljava/io/File;Ljava/io/File;)V + public fun handleCommandAsComment (Lorg/jetbrains/dokka/base/templating/Command;Ljava/util/List;Ljava/io/File;Ljava/io/File;)V + public fun handleCommandAsTag (Lorg/jetbrains/dokka/base/templating/Command;Lorg/jsoup/nodes/Element;Ljava/io/File;Ljava/io/File;)V } public abstract interface class org/jetbrains/dokka/templates/CommandHandler { public abstract fun canHandle (Lorg/jetbrains/dokka/base/templating/Command;)Z public abstract fun finish (Ljava/io/File;)V public abstract fun handleCommand (Lorg/jsoup/nodes/Element;Lorg/jetbrains/dokka/base/templating/Command;Ljava/io/File;Ljava/io/File;)V + public abstract fun handleCommandAsComment (Lorg/jetbrains/dokka/base/templating/Command;Ljava/util/List;Ljava/io/File;Ljava/io/File;)V + public abstract fun handleCommandAsTag (Lorg/jetbrains/dokka/base/templating/Command;Lorg/jsoup/nodes/Element;Ljava/io/File;Ljava/io/File;)V } public final class org/jetbrains/dokka/templates/CommandHandler$DefaultImpls { public static fun finish (Lorg/jetbrains/dokka/templates/CommandHandler;Ljava/io/File;)V + public static fun handleCommand (Lorg/jetbrains/dokka/templates/CommandHandler;Lorg/jsoup/nodes/Element;Lorg/jetbrains/dokka/base/templating/Command;Ljava/io/File;Ljava/io/File;)V + public static fun handleCommandAsComment (Lorg/jetbrains/dokka/templates/CommandHandler;Lorg/jetbrains/dokka/base/templating/Command;Ljava/util/List;Ljava/io/File;Ljava/io/File;)V + public static fun handleCommandAsTag (Lorg/jetbrains/dokka/templates/CommandHandler;Lorg/jetbrains/dokka/base/templating/Command;Lorg/jsoup/nodes/Element;Ljava/io/File;Ljava/io/File;)V } public final class org/jetbrains/dokka/templates/DefaultMultiModuleTemplateProcessor : org/jetbrains/dokka/templates/MultiModuleTemplateProcessor { @@ -54,7 +61,8 @@ public final class org/jetbrains/dokka/templates/DefaultSubmoduleTemplateProcess public final class org/jetbrains/dokka/templates/DirectiveBasedHtmlTemplateProcessingStrategy : org/jetbrains/dokka/templates/TemplateProcessingStrategy { public fun (Lorg/jetbrains/dokka/plugability/DokkaContext;)V public fun finish (Ljava/io/File;)V - public final fun handleCommand (Lorg/jsoup/nodes/Element;Lorg/jetbrains/dokka/base/templating/Command;Ljava/io/File;Ljava/io/File;)V + public final fun handleCommandAsComment (Lorg/jetbrains/dokka/base/templating/Command;Ljava/util/List;Ljava/io/File;Ljava/io/File;)V + public final fun handleCommandAsTag (Lorg/jsoup/nodes/Element;Lorg/jetbrains/dokka/base/templating/Command;Ljava/io/File;Ljava/io/File;)V public fun process (Ljava/io/File;Ljava/io/File;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaModuleDescription;)Z } @@ -82,6 +90,8 @@ public final class org/jetbrains/dokka/templates/SubstitutionCommandHandler : or public fun canHandle (Lorg/jetbrains/dokka/base/templating/Command;)Z public fun finish (Ljava/io/File;)V public fun handleCommand (Lorg/jsoup/nodes/Element;Lorg/jetbrains/dokka/base/templating/Command;Ljava/io/File;Ljava/io/File;)V + public fun handleCommandAsComment (Lorg/jetbrains/dokka/base/templating/Command;Ljava/util/List;Ljava/io/File;Ljava/io/File;)V + public fun handleCommandAsTag (Lorg/jetbrains/dokka/base/templating/Command;Lorg/jsoup/nodes/Element;Ljava/io/File;Ljava/io/File;)V } public abstract interface class org/jetbrains/dokka/templates/Substitutor { @@ -101,16 +111,16 @@ public abstract interface class org/jetbrains/dokka/templates/TemplateProcessor } public final class org/jetbrains/dokka/templates/TemplatingContext { - public fun (Ljava/io/File;Ljava/io/File;Lorg/jsoup/nodes/Element;Lorg/jetbrains/dokka/base/templating/Command;)V + public fun (Ljava/io/File;Ljava/io/File;Ljava/util/List;Lorg/jetbrains/dokka/base/templating/Command;)V public final fun component1 ()Ljava/io/File; public final fun component2 ()Ljava/io/File; - public final fun component3 ()Lorg/jsoup/nodes/Element; + public final fun component3 ()Ljava/util/List; public final fun component4 ()Lorg/jetbrains/dokka/base/templating/Command; - public final fun copy (Ljava/io/File;Ljava/io/File;Lorg/jsoup/nodes/Element;Lorg/jetbrains/dokka/base/templating/Command;)Lorg/jetbrains/dokka/templates/TemplatingContext; - public static synthetic fun copy$default (Lorg/jetbrains/dokka/templates/TemplatingContext;Ljava/io/File;Ljava/io/File;Lorg/jsoup/nodes/Element;Lorg/jetbrains/dokka/base/templating/Command;ILjava/lang/Object;)Lorg/jetbrains/dokka/templates/TemplatingContext; + public final fun copy (Ljava/io/File;Ljava/io/File;Ljava/util/List;Lorg/jetbrains/dokka/base/templating/Command;)Lorg/jetbrains/dokka/templates/TemplatingContext; + public static synthetic fun copy$default (Lorg/jetbrains/dokka/templates/TemplatingContext;Ljava/io/File;Ljava/io/File;Ljava/util/List;Lorg/jetbrains/dokka/base/templating/Command;ILjava/lang/Object;)Lorg/jetbrains/dokka/templates/TemplatingContext; public fun equals (Ljava/lang/Object;)Z + public final fun getBody ()Ljava/util/List; public final fun getCommand ()Lorg/jetbrains/dokka/base/templating/Command; - public final fun getElement ()Lorg/jsoup/nodes/Element; public final fun getInput ()Ljava/io/File; public final fun getOutput ()Ljava/io/File; public fun hashCode ()I @@ -162,6 +172,8 @@ public final class templates/ReplaceVersionCommandHandler : org/jetbrains/dokka/ public fun canHandle (Lorg/jetbrains/dokka/base/templating/Command;)Z public fun finish (Ljava/io/File;)V public fun handleCommand (Lorg/jsoup/nodes/Element;Lorg/jetbrains/dokka/base/templating/Command;Ljava/io/File;Ljava/io/File;)V + public fun handleCommandAsComment (Lorg/jetbrains/dokka/base/templating/Command;Ljava/util/List;Ljava/io/File;Ljava/io/File;)V + public fun handleCommandAsTag (Lorg/jetbrains/dokka/base/templating/Command;Lorg/jsoup/nodes/Element;Ljava/io/File;Ljava/io/File;)V } public final class templates/SourcesetDependencyProcessingStrategy : org/jetbrains/dokka/templates/TemplateProcessingStrategy { 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() - 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, 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, 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) -> Unit + ) { + val nodes: MutableList = 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, 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().query { substitutor } private fun findSubstitution(commandContext: TemplatingContext, match: MatchResult): String = substitutors.asSequence().mapNotNull { it.trySubstitute(commandContext, match) }.firstOrNull() ?: match.value - private fun substitute(element: Element, commandContext: TemplatingContext) { + private fun substitute(elements: List, commandContext: TemplatingContext) { 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) { 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( val input: File, val output: File, - val element: Element, + val body: List, 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() 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 - } - } diff --git a/plugins/versioning/api/versioning.api b/plugins/versioning/api/versioning.api index 15ebe7be..b163fd4a 100644 --- a/plugins/versioning/api/versioning.api +++ b/plugins/versioning/api/versioning.api @@ -59,6 +59,8 @@ public final class org/jetbrains/dokka/versioning/ReplaceVersionCommandHandler : public fun finish (Ljava/io/File;)V public final fun getVersionsNavigationCreator ()Lorg/jetbrains/dokka/versioning/VersionsNavigationCreator; public fun handleCommand (Lorg/jsoup/nodes/Element;Lorg/jetbrains/dokka/base/templating/Command;Ljava/io/File;Ljava/io/File;)V + public fun handleCommandAsComment (Lorg/jetbrains/dokka/base/templating/Command;Ljava/util/List;Ljava/io/File;Ljava/io/File;)V + public fun handleCommandAsTag (Lorg/jetbrains/dokka/base/templating/Command;Lorg/jsoup/nodes/Element;Ljava/io/File;Ljava/io/File;)V } public final class org/jetbrains/dokka/versioning/SemVerVersionOrdering : org/jetbrains/dokka/versioning/VersionsOrdering { diff --git a/plugins/versioning/src/main/kotlin/versioning/ReplaceVersionsCommand.kt b/plugins/versioning/src/main/kotlin/versioning/ReplaceVersionsCommand.kt index 728eac09..ad1edd2b 100644 --- a/plugins/versioning/src/main/kotlin/versioning/ReplaceVersionsCommand.kt +++ b/plugins/versioning/src/main/kotlin/versioning/ReplaceVersionsCommand.kt @@ -18,7 +18,7 @@ class ReplaceVersionCommandHandler(context: DokkaContext) : CommandHandler { override fun canHandle(command: Command): Boolean = command is ReplaceVersionsCommand - override fun handleCommand(element: Element, command: Command, input: File, output: File) { + override fun handleCommandAsTag(command: Command, element: Element, input: File, output: File) { element.empty() element.append(versionsNavigationCreator(output)) } -- cgit