diff options
10 files changed, 154 insertions, 15 deletions
diff --git a/plugins/all-module-page/src/main/kotlin/AllModulesPagePlugin.kt b/plugins/all-module-page/src/main/kotlin/AllModulesPagePlugin.kt index 584de32f..f1ed8c1e 100644 --- a/plugins/all-module-page/src/main/kotlin/AllModulesPagePlugin.kt +++ b/plugins/all-module-page/src/main/kotlin/AllModulesPagePlugin.kt @@ -1,15 +1,15 @@ package org.jetbrains.dokka.allModulesPage import org.jetbrains.dokka.CoreExtensions -import org.jetbrains.dokka.allModulesPage.templates.DefaultTemplateProcessor -import org.jetbrains.dokka.allModulesPage.templates.DirectiveBasedTemplateProcessingStrategy -import org.jetbrains.dokka.allModulesPage.templates.TemplateProcessor +import org.jetbrains.dokka.allModulesPage.templates.* import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.plugability.DokkaPlugin class AllModulesPagePlugin : DokkaPlugin() { val templateProcessor by extensionPoint<TemplateProcessor>() + val substitutor by extensionPoint<Substitutor>() + val allModulePageCreators by extending { (CoreExtensions.allModulePageCreator providing ::MultimodulePageCreator) @@ -31,4 +31,8 @@ class AllModulesPagePlugin : DokkaPlugin() { val defaultTemplateProcessor by extending { templateProcessor providing { DefaultTemplateProcessor(it, DirectiveBasedTemplateProcessingStrategy(it)) } } + + val pathToRootSubstitutor by extending { + substitutor providing ::PathToRootSubstitutor + } }
\ No newline at end of file diff --git a/plugins/all-module-page/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt b/plugins/all-module-page/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt index 705e6678..d50cbc32 100644 --- a/plugins/all-module-page/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt +++ b/plugins/all-module-page/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt @@ -4,22 +4,27 @@ import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.jetbrains.dokka.base.templating.AddToNavigationCommand -import org.jetbrains.dokka.base.templating.Command -import org.jetbrains.dokka.base.templating.ResolveLinkCommand -import org.jetbrains.dokka.base.templating.parseJson +import org.jetbrains.dokka.allModulesPage.AllModulesPagePlugin +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.resolvers.shared.ExternalDocumentation +import org.jetbrains.dokka.base.resolvers.shared.PackageList +import org.jetbrains.dokka.base.templating.* 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.Attributes -import org.jsoup.nodes.Element +import org.jsoup.nodes.* import org.jsoup.parser.Tag import java.io.File +import java.net.URL import java.nio.file.Files import java.util.concurrent.ConcurrentHashMap class DirectiveBasedTemplateProcessingStrategy(private val context: DokkaContext) : TemplateProcessingStrategy { private val navigationFragments = ConcurrentHashMap<String, Element>() + private val substitutors = context.plugin<AllModulesPagePlugin>().query { substitutor } + override suspend fun process(input: File, output: File): Unit = coroutineScope { if (input.extension == "html") { launch { @@ -28,8 +33,9 @@ class DirectiveBasedTemplateProcessingStrategy(private val context: DokkaContext document.select("dokka-template-command").forEach { val command = parseJson<Command>(it.attr("data")) when (command) { - is ResolveLinkCommand -> resolveLink(it, command) + is ResolveLinkCommand -> resolveLink(it, command, output) is AddToNavigationCommand -> navigationFragments[command.moduleName] = it + is SubstitutionCommand -> substitute(it, TemplatingContext(input, output, it, command)) else -> context.logger.warn("Unknown templating command $command") } } @@ -42,6 +48,42 @@ class DirectiveBasedTemplateProcessingStrategy(private val context: DokkaContext } } + private fun substitute(element: Element, 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) + } + + private fun Node.traverseToSubstitute(regex: Regex, commandContext: TemplatingContext<SubstitutionCommand>) { + when (this) { + is TextNode -> replaceWith(TextNode(wholeText.substitute(regex, commandContext))) + is DataNode -> replaceWith(DataNode(wholeData.substitute(regex, commandContext))) + is Element -> { + attributes().forEach { attr(it.key, it.value.substitute(regex, commandContext)) } + childNodes().forEach { it.traverseToSubstitute(regex, commandContext) } + } + } + } + + private fun String.substitute(regex: Regex, commandContext: TemplatingContext<SubstitutionCommand>) = buildString { + var lastOffset = 0 + regex.findAll(this@substitute).forEach { match -> + append(this@substitute, lastOffset, match.range.first) + append(findSubstitution(commandContext, match)) + lastOffset = match.range.last + 1 + } + append(this@substitute, lastOffset, this@substitute.length) + } + + private fun findSubstitution(commandContext: TemplatingContext<SubstitutionCommand>, match: MatchResult): String = + substitutors.asSequence().mapNotNull { it.trySubstitute(commandContext, match) }.firstOrNull() ?: match.value + override suspend fun finish(output: File) { val attributes = Attributes().apply { put("class", "sideMenu") @@ -76,9 +118,57 @@ class DirectiveBasedTemplateProcessingStrategy(private val context: DokkaContext } } - private fun resolveLink(it: Element, command: ResolveLinkCommand) { + private fun resolveLink(it: Element, command: ResolveLinkCommand, fileContext: File) { + val elpFactory = context.plugin<DokkaBase>().query { externalLocationProviderFactory } + + val packageLists = + context.configuration.modules.map { it.sourceOutputDirectory.resolve(it.relativePathToOutputDirectory) } + .map { module -> + module to PackageList.load( + URL("file:" + module.resolve("package-list").path), + 8, + true + ) + }.toMap() + + val externalDocumentations = + packageLists.map { (module, pckgList) -> + ExternalDocumentation( + URL("file:/${module.name}/${module.name}"), + pckgList!! + ) + } + + val elps = elpFactory + .flatMap { externalDocumentations.map { ed -> it.getExternalLocationProvider(ed) } } + .filterNotNull() + + val absoluteLink = elps.mapNotNull { it.resolve(command.dri) }.firstOrNull() + if (absoluteLink == null) { + val children = it.childNodes().toList() + val attributes = Attributes().apply { + put("data-unresolved-link", command.dri.toString()) + } + val element = Element(Tag.valueOf("span"), "", attributes).apply { + children.forEach { ch -> appendChild(ch) } + } + it.replaceWith(element) + return + } + + val modulePath = context.configuration.outputDir.absolutePath.split("/") + val contextPath = fileContext.absolutePath.split("/") + val commonPathElements = modulePath.zip(contextPath) + .takeWhile { (a, b) -> a == b }.count() + + // -1 here to not drop the last directory + val link = + (List(contextPath.size - commonPathElements - 1) { ".." } + modulePath.drop(commonPathElements)).joinToString( + "/" + ) + absoluteLink.removePrefix("file:") + val attributes = Attributes().apply { - put("href", "") // TODO: resolve + put("href", link) // TODO: resolve } val children = it.childNodes().toList() val element = Element(Tag.valueOf("a"), "", attributes).apply { @@ -86,4 +176,4 @@ class DirectiveBasedTemplateProcessingStrategy(private val context: DokkaContext } it.replaceWith(element) } -}
\ No newline at end of file +} diff --git a/plugins/all-module-page/src/main/kotlin/templates/PathToRootSubstitutor.kt b/plugins/all-module-page/src/main/kotlin/templates/PathToRootSubstitutor.kt new file mode 100644 index 00000000..992ee7ed --- /dev/null +++ b/plugins/all-module-page/src/main/kotlin/templates/PathToRootSubstitutor.kt @@ -0,0 +1,16 @@ +package org.jetbrains.dokka.allModulesPage.templates + +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.templating.PathToRootSubstitutionCommand +import org.jetbrains.dokka.base.templating.SubstitutionCommand +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.query +import java.nio.file.Path + +class PathToRootSubstitutor(private val dokkaContext: DokkaContext) : Substitutor { + override fun trySubstitute(context: TemplatingContext<SubstitutionCommand>, match: MatchResult): String? = + if (context.command is PathToRootSubstitutionCommand) { + context.output.toPath().parent.relativize(dokkaContext.configuration.outputDir.toPath()).toString() + "/" + } else null + +}
\ No newline at end of file diff --git a/plugins/all-module-page/src/main/kotlin/templates/Substitutor.kt b/plugins/all-module-page/src/main/kotlin/templates/Substitutor.kt new file mode 100644 index 00000000..98f1d88e --- /dev/null +++ b/plugins/all-module-page/src/main/kotlin/templates/Substitutor.kt @@ -0,0 +1,7 @@ +package org.jetbrains.dokka.allModulesPage.templates + +import org.jetbrains.dokka.base.templating.SubstitutionCommand + +fun interface Substitutor { + fun trySubstitute(context: TemplatingContext<SubstitutionCommand>, match: MatchResult): String? +}
\ No newline at end of file diff --git a/plugins/all-module-page/src/main/kotlin/templates/TemplateProcessor.kt b/plugins/all-module-page/src/main/kotlin/templates/TemplateProcessor.kt index cd144046..eafc3669 100644 --- a/plugins/all-module-page/src/main/kotlin/templates/TemplateProcessor.kt +++ b/plugins/all-module-page/src/main/kotlin/templates/TemplateProcessor.kt @@ -1,7 +1,9 @@ package org.jetbrains.dokka.allModulesPage.templates import kotlinx.coroutines.* +import org.jetbrains.dokka.base.templating.Command import org.jetbrains.dokka.plugability.DokkaContext +import org.jsoup.nodes.Element import java.io.File import java.nio.file.Files import java.nio.file.Path @@ -44,3 +46,9 @@ class DefaultTemplateProcessor( } } +data class TemplatingContext<out T: Command>( + val input: File, + val output: File, + val element: Element, + val command: T, +)
\ No newline at end of file diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt index 16f60a83..d344dafe 100644 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt @@ -13,6 +13,7 @@ import org.jetbrains.dokka.base.renderers.isImage import org.jetbrains.dokka.base.renderers.pageId import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint import org.jetbrains.dokka.base.resolvers.local.DokkaBaseLocationProvider +import org.jetbrains.dokka.base.templating.PathToRootSubstitutionCommand import org.jetbrains.dokka.base.templating.ResolveLinkCommand import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.DisplaySourceSet @@ -736,7 +737,9 @@ open class HtmlRenderer( else -> unsafe { +it } } } - script { unsafe { +"""var pathToRoot = "${locationProvider.pathToRoot(page)}";""" } } + templateCommand(PathToRootSubstitutionCommand("###")) { + script { unsafe { +"""var pathToRoot = "###";""" } } + } } body { div { diff --git a/plugins/base/src/main/kotlin/renderers/html/Tags.kt b/plugins/base/src/main/kotlin/renderers/html/Tags.kt index 3db49ebf..59d711e9 100644 --- a/plugins/base/src/main/kotlin/renderers/html/Tags.kt +++ b/plugins/base/src/main/kotlin/renderers/html/Tags.kt @@ -13,7 +13,7 @@ open class WBR(initialAttributes: Map<String, String>, consumer: TagConsumer<*>) HTMLTag("wbr", consumer, initialAttributes, namespace = null, inlineTag = true, emptyTag = false), HtmlBlockInlineTag -fun FlowOrPhrasingContent.templateCommand(data: Command, block: TemplateCommand.() -> Unit = {}): Unit = +fun FlowOrPhrasingOrMetaDataContent.templateCommand(data: Command, block: TemplateCommand.() -> Unit = {}): Unit = TemplateCommand(attributesMapOf("data", toJsonString(data)), consumer).visit(block) fun <T> TagConsumer<T>.templateCommand(data: Command, block: TemplateCommand.() -> Unit = {}): T = diff --git a/plugins/base/src/main/kotlin/resolvers/shared/PackageList.kt b/plugins/base/src/main/kotlin/resolvers/shared/PackageList.kt index c1b76a3b..8d3b7521 100644 --- a/plugins/base/src/main/kotlin/resolvers/shared/PackageList.kt +++ b/plugins/base/src/main/kotlin/resolvers/shared/PackageList.kt @@ -1,6 +1,7 @@ package org.jetbrains.dokka.base.resolvers.shared import org.jetbrains.dokka.base.renderers.PackageListService +import java.io.File import java.net.URL data class PackageList( @@ -14,6 +15,9 @@ data class PackageList( if (offlineMode && url.protocol.toLowerCase() != "file") return null + if (!File(url.file).isFile) + return null + val packageListStream = url.readContent() val (params, packages) = packageListStream diff --git a/plugins/base/src/main/kotlin/templating/Command.kt b/plugins/base/src/main/kotlin/templating/Command.kt index e352ba32..0b998dee 100644 --- a/plugins/base/src/main/kotlin/templating/Command.kt +++ b/plugins/base/src/main/kotlin/templating/Command.kt @@ -7,3 +7,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.CLASS @JsonTypeInfo(use= CLASS) interface Command + +abstract class SubstitutionCommand: Command { + abstract val pattern: String +} diff --git a/plugins/base/src/main/kotlin/templating/PathToRootSubstitutionCommand.kt b/plugins/base/src/main/kotlin/templating/PathToRootSubstitutionCommand.kt new file mode 100644 index 00000000..03f091c3 --- /dev/null +++ b/plugins/base/src/main/kotlin/templating/PathToRootSubstitutionCommand.kt @@ -0,0 +1,3 @@ +package org.jetbrains.dokka.base.templating + +data class PathToRootSubstitutionCommand(override val pattern: String): SubstitutionCommand()
\ No newline at end of file |