Skip to content

Commit

Permalink
Keep template commands in html comments
Browse files Browse the repository at this point in the history
  • Loading branch information
vmishenev committed Feb 16, 2022
1 parent 2372302 commit 4339c15
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 42 deletions.
4 changes: 4 additions & 0 deletions plugins/base/api/base.api
Expand Up @@ -510,13 +510,17 @@ 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 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
public static final fun templateCommand (Lkotlinx/html/FlowOrMetaDataContent;Lorg/jetbrains/dokka/base/templating/Command;Lkotlin/jvm/functions/Function1;)V
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
Expand Down
22 changes: 12 additions & 10 deletions plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt
Expand Up @@ -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<FlowContent>(context) {
Expand Down Expand Up @@ -778,11 +780,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 {
Expand All @@ -803,10 +805,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" ->
Expand All @@ -815,10 +817,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
Expand All @@ -827,8 +829,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 }
}
Expand Down
10 changes: 10 additions & 0 deletions plugins/base/src/main/kotlin/renderers/html/Tags.kt
Expand Up @@ -26,6 +26,16 @@ inline fun FlowOrPhrasingContent.strike(classes : String? = null, crossinline bl
open class STRIKE(initialAttributes : Map<String, String>, override val consumer : TagConsumer<*>) : HTMLTag("strike", consumer, initialAttributes, null, false, false), HtmlBlockInlineTag {

}
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${toJsonString(data)}")
block()
comment(TEMPLATE_COMMAND_END_BORDER)
}

fun FlowOrMetaDataContent.templateCommand(data: Command, block: TemplateBlock = {}): Unit =
(consumer as? ImmediateResolutionTagConsumer)?.processCommand(data, block)
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
24 changes: 18 additions & 6 deletions plugins/templating/api/templating.api
Expand Up @@ -40,6 +40,16 @@ public final class org/jetbrains/dokka/templates/CommandHandler$DefaultImpls {
public static fun finish (Lorg/jetbrains/dokka/templates/CommandHandler;Ljava/io/File;)V
}

public abstract interface class org/jetbrains/dokka/templates/CommentCommandHandler {
public abstract fun canHandle (Lorg/jetbrains/dokka/base/templating/Command;)Z
public abstract fun finish (Ljava/io/File;)V
public abstract fun handleCommand (Ljava/util/List;Lorg/jetbrains/dokka/base/templating/Command;Ljava/io/File;Ljava/io/File;)V
}

public final class org/jetbrains/dokka/templates/CommentCommandHandler$DefaultImpls {
public static fun finish (Lorg/jetbrains/dokka/templates/CommentCommandHandler;Ljava/io/File;)V
}

public final class org/jetbrains/dokka/templates/DefaultMultiModuleTemplateProcessor : org/jetbrains/dokka/templates/MultiModuleTemplateProcessor {
public fun <init> (Lorg/jetbrains/dokka/plugability/DokkaContext;)V
public final fun getContext ()Lorg/jetbrains/dokka/plugability/DokkaContext;
Expand All @@ -54,6 +64,7 @@ public final class org/jetbrains/dokka/templates/DefaultSubmoduleTemplateProcess
public final class org/jetbrains/dokka/templates/DirectiveBasedHtmlTemplateProcessingStrategy : org/jetbrains/dokka/templates/TemplateProcessingStrategy {
public fun <init> (Lorg/jetbrains/dokka/plugability/DokkaContext;)V
public fun finish (Ljava/io/File;)V
public final fun handleCommand (Ljava/util/List;Lorg/jetbrains/dokka/base/templating/Command;Ljava/io/File;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 fun process (Ljava/io/File;Ljava/io/File;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaModuleDescription;)Z
}
Expand All @@ -77,10 +88,11 @@ public abstract interface class org/jetbrains/dokka/templates/SubmoduleTemplateP
public abstract fun process (Ljava/util/List;)Lorg/jetbrains/dokka/templates/TemplatingResult;
}

public final class org/jetbrains/dokka/templates/SubstitutionCommandHandler : org/jetbrains/dokka/templates/CommandHandler {
public final class org/jetbrains/dokka/templates/SubstitutionCommandHandler : org/jetbrains/dokka/templates/CommandHandler, org/jetbrains/dokka/templates/CommentCommandHandler {
public fun <init> (Lorg/jetbrains/dokka/plugability/DokkaContext;)V
public fun canHandle (Lorg/jetbrains/dokka/base/templating/Command;)Z
public fun finish (Ljava/io/File;)V
public fun handleCommand (Ljava/util/List;Lorg/jetbrains/dokka/base/templating/Command;Ljava/io/File;Ljava/io/File;)V
public fun handleCommand (Lorg/jsoup/nodes/Element;Lorg/jetbrains/dokka/base/templating/Command;Ljava/io/File;Ljava/io/File;)V
}

Expand All @@ -101,17 +113,17 @@ public abstract interface class org/jetbrains/dokka/templates/TemplateProcessor
}

public final class org/jetbrains/dokka/templates/TemplatingContext {
public fun <init> (Ljava/io/File;Ljava/io/File;Lorg/jsoup/nodes/Element;Lorg/jetbrains/dokka/base/templating/Command;)V
public fun <init> (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 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 getNodes ()Ljava/util/List;
public final fun getOutput ()Ljava/io/File;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
Expand Down
Expand Up @@ -2,10 +2,17 @@ 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)
fun canHandle(command: Command): Boolean
fun finish(output: File) {}
}

interface CommentCommandHandler {
fun handleCommand(nodes: List<Node>, command: Command, input: File, output: File)
fun canHandle(command: Command): Boolean
fun finish(output: File) {}
}
@@ -1,13 +1,18 @@
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.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

Expand All @@ -20,9 +25,17 @@ 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)
}
extractCommandsFromComments(document) { nodes, command ->
val nodesTrimed =
nodes.dropWhile { (it is TextNode && it.isBlank).also { res -> if (res) it.remove() } }
.dropLastWhile { (it is TextNode && it.isBlank).also { res -> if (res) it.remove() } }
handleCommand(nodesTrimed, command, input, output)
}

Files.write(output.toPath(), listOf(document.outerHtml()))
true
} else false
Expand All @@ -33,7 +46,43 @@ class DirectiveBasedHtmlTemplateProcessingStrategy(private val context: DokkaCon
context.logger.warn("Unknown templating command $command")
else
handlers.forEach { it.handleCommand(element, command, input, output) }
}

fun handleCommand(nodes: List<Node>, command: Command, input: File, output: File) {
val handlers = directiveBasedCommandHandlers.filterIsInstance<CommentCommandHandler>().filter { it.canHandle(command) }
if (handlers.isEmpty())
context.logger.warn("Unknown templating command $command")
else
handlers.forEach { it.handleCommand(nodes, command, input, output) }
}

private fun extractCommandsFromComments(node: Node, startFrom: Int = 0, handler: (List<Node>, Command) -> Unit) {
val nodes: MutableList<Node> = mutableListOf()
var lastStartBorder: Comment? = null
var firstStartBorder: Comment? = null
for (ind in startFrom until node.childNodeSize()) {
when (val currentChild = node.childNode(ind)) {
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)?.let { parseJson(it) }
cmd?.let { handler(nodes, it) }
currentChild.remove()
extractCommandsFromComments(node, firstStartBorder?.siblingIndex() ?: 0, handler)
return
} else {
lastStartBorder?.let { nodes.add(currentChild) }
}
else -> {
extractCommandsFromComments(currentChild, handler = handler)
lastStartBorder?.let { nodes.add(currentChild) }
}
}
}
}

override fun finish(output: File) {
Expand Down
Expand Up @@ -11,30 +11,37 @@ import org.jsoup.nodes.Node
import org.jsoup.nodes.TextNode
import java.io.File

class SubstitutionCommandHandler(context: DokkaContext) : CommandHandler {
class SubstitutionCommandHandler(context: DokkaContext) : CommandHandler, CommentCommandHandler {

override fun handleCommand(element: Element, command: Command, input: File, output: File) {
command as SubstitutionCommand
substitute(element, TemplatingContext(input, output, element, command))
val childrenCopy = element.children().toList()
substitute(childrenCopy, TemplatingContext(input, output, childrenCopy, command))

val position = element.elementSiblingIndex()
val parent = element.parent()
element.remove()

parent?.insertChildren(position, childrenCopy)
}

override fun handleCommand(nodes: List<Node>, command: Command, input: File, output: File) {
command as SubstitutionCommand
substitute(nodes, TemplatingContext(input, output, nodes, 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>) {
Expand Down

0 comments on commit 4339c15

Please sign in to comment.