From 3cb4702a68139788de6e1f7b087ced345f2b71ba Mon Sep 17 00:00:00 2001 From: Błażej Kardyś Date: Fri, 20 Nov 2020 17:23:10 +0100 Subject: Changing how multimodule location provider works and improving gfm link substitution --- plugins/all-module-page/build.gradle.kts | 14 -- .../src/main/kotlin/AllModulesPageGeneration.kt | 40 ------ .../src/main/kotlin/AllModulesPagePlugin.kt | 53 -------- .../src/main/kotlin/MultimoduleLocationProvider.kt | 40 ------ .../src/main/kotlin/MultimodulePageCreator.kt | 97 -------------- .../templates/DirectiveBasedTemplateProcessing.kt | 138 -------------------- .../kotlin/templates/ExternalModuleLinkResolver.kt | 49 ------- .../FallbackTemplateProcessingStrategy.kt | 18 --- .../main/kotlin/templates/PathToRootSubstitutor.kt | 14 -- .../src/main/kotlin/templates/Substitutor.kt | 7 - .../src/main/kotlin/templates/TemplateProcessor.kt | 60 --------- .../org.jetbrains.dokka.plugability.DokkaPlugin | 1 - plugins/all-modules-page/build.gradle.kts | 14 ++ .../src/main/kotlin/AllModulesPageGeneration.kt | 43 +++++++ .../src/main/kotlin/AllModulesPagePlugin.kt | 64 ++++++++++ .../src/main/kotlin/MultimoduleLocationProvider.kt | 36 ++++++ .../src/main/kotlin/MultimodulePageCreator.kt | 101 +++++++++++++++ .../templates/DirectiveBasedTemplateProcessing.kt | 141 +++++++++++++++++++++ .../kotlin/templates/ExternalModuleLinkResolver.kt | 77 +++++++++++ .../FallbackTemplateProcessingStrategy.kt | 18 +++ .../main/kotlin/templates/PathToRootSubstitutor.kt | 14 ++ .../src/main/kotlin/templates/Substitutor.kt | 7 + .../src/main/kotlin/templates/TemplateProcessor.kt | 60 +++++++++ .../org.jetbrains.dokka.plugability.DokkaPlugin | 1 + .../src/main/kotlin/testRunner/baseTestApi.kt | 2 +- plugins/base/src/main/kotlin/DokkaBase.kt | 3 +- .../kotlin/generation/SingleModuleGeneration.kt | 2 + .../src/main/kotlin/renderers/html/HtmlRenderer.kt | 1 - .../kotlin/renderers/html/htmlPreprocessors.kt | 3 +- .../resolvers/local/DefaultLocationProvider.kt | 4 +- .../resolvers/local/DokkaBaseLocationProvider.kt | 5 +- .../resolvers/local/DokkaLocationProvider.kt | 4 +- .../main/kotlin/resolvers/shared/PackageList.kt | 5 - .../templating/ImmediateHtmlCommandConsumer.kt | 3 +- plugins/gfm/.DS_Store | Bin 0 -> 6148 bytes plugins/gfm/gfm-all-module-page/build.gradle.kts | 14 -- .../gfm/allModulesPage/GfmAllModulesPagaPlugin.kt | 28 ---- .../GfmMultimoduleLocationProvider.kt | 17 --- .../GfmTemplateProcessingStrategy.kt | 45 ------- .../org.jetbrains.dokka.plugability.DokkaPlugin | 1 - .../gfm/gfm-template-processing/build.gradle.kts | 14 ++ .../GfmTemplateProcessingPlugin.kt | 31 +++++ .../GfmTemplateProcessingStrategy.kt | 51 ++++++++ .../org.jetbrains.dokka.plugability.DokkaPlugin | 1 + .../kotlin/org/jetbrains/dokka/gfm/GfmPlugin.kt | 4 +- .../org/jetbrains/dokka/gfm/gfmTemplating.kt | 15 ++- .../dokka/gfm/location/MarkdownLocationProvider.kt | 12 +- .../dokka/gfm/renderer/CommonmarkRenderer.kt | 15 ++- .../renderers/gfm/GfmRenderingOnlyTestBase.kt | 16 +-- .../javadoc/location/JavadocLocationProvider.kt | 2 +- 50 files changed, 726 insertions(+), 679 deletions(-) delete mode 100644 plugins/all-module-page/build.gradle.kts delete mode 100644 plugins/all-module-page/src/main/kotlin/AllModulesPageGeneration.kt delete mode 100644 plugins/all-module-page/src/main/kotlin/AllModulesPagePlugin.kt delete mode 100644 plugins/all-module-page/src/main/kotlin/MultimoduleLocationProvider.kt delete mode 100644 plugins/all-module-page/src/main/kotlin/MultimodulePageCreator.kt delete mode 100644 plugins/all-module-page/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt delete mode 100644 plugins/all-module-page/src/main/kotlin/templates/ExternalModuleLinkResolver.kt delete mode 100644 plugins/all-module-page/src/main/kotlin/templates/FallbackTemplateProcessingStrategy.kt delete mode 100644 plugins/all-module-page/src/main/kotlin/templates/PathToRootSubstitutor.kt delete mode 100644 plugins/all-module-page/src/main/kotlin/templates/Substitutor.kt delete mode 100644 plugins/all-module-page/src/main/kotlin/templates/TemplateProcessor.kt delete mode 100644 plugins/all-module-page/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin create mode 100644 plugins/all-modules-page/build.gradle.kts create mode 100644 plugins/all-modules-page/src/main/kotlin/AllModulesPageGeneration.kt create mode 100644 plugins/all-modules-page/src/main/kotlin/AllModulesPagePlugin.kt create mode 100644 plugins/all-modules-page/src/main/kotlin/MultimoduleLocationProvider.kt create mode 100644 plugins/all-modules-page/src/main/kotlin/MultimodulePageCreator.kt create mode 100644 plugins/all-modules-page/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt create mode 100644 plugins/all-modules-page/src/main/kotlin/templates/ExternalModuleLinkResolver.kt create mode 100644 plugins/all-modules-page/src/main/kotlin/templates/FallbackTemplateProcessingStrategy.kt create mode 100644 plugins/all-modules-page/src/main/kotlin/templates/PathToRootSubstitutor.kt create mode 100644 plugins/all-modules-page/src/main/kotlin/templates/Substitutor.kt create mode 100644 plugins/all-modules-page/src/main/kotlin/templates/TemplateProcessor.kt create mode 100644 plugins/all-modules-page/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin create mode 100644 plugins/gfm/.DS_Store delete mode 100644 plugins/gfm/gfm-all-module-page/build.gradle.kts delete mode 100644 plugins/gfm/gfm-all-module-page/src/main/kotlin/org/jetbrains/dokka/gfm/allModulesPage/GfmAllModulesPagaPlugin.kt delete mode 100644 plugins/gfm/gfm-all-module-page/src/main/kotlin/org/jetbrains/dokka/gfm/allModulesPage/GfmMultimoduleLocationProvider.kt delete mode 100644 plugins/gfm/gfm-all-module-page/src/main/kotlin/org/jetbrains/dokka/gfm/allModulesPage/GfmTemplateProcessingStrategy.kt delete mode 100644 plugins/gfm/gfm-all-module-page/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin create mode 100644 plugins/gfm/gfm-template-processing/build.gradle.kts create mode 100644 plugins/gfm/gfm-template-processing/src/main/kotlin/org/jetbrains/dokka/gfm/templateProcessing/GfmTemplateProcessingPlugin.kt create mode 100644 plugins/gfm/gfm-template-processing/src/main/kotlin/org/jetbrains/dokka/gfm/templateProcessing/GfmTemplateProcessingStrategy.kt create mode 100644 plugins/gfm/gfm-template-processing/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin (limited to 'plugins') diff --git a/plugins/all-module-page/build.gradle.kts b/plugins/all-module-page/build.gradle.kts deleted file mode 100644 index a0c5a5ed..00000000 --- a/plugins/all-module-page/build.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -import org.jetbrains.registerDokkaArtifactPublication - -registerDokkaArtifactPublication("dokkaAllModulesPage") { - artifactId = "all-modules-page-plugin" -} - -dependencies { - implementation(project(":plugins:base")) - - val coroutines_version: String by project - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version") - - implementation("org.jsoup:jsoup:1.12.1") -} \ No newline at end of file diff --git a/plugins/all-module-page/src/main/kotlin/AllModulesPageGeneration.kt b/plugins/all-module-page/src/main/kotlin/AllModulesPageGeneration.kt deleted file mode 100644 index 1ba63627..00000000 --- a/plugins/all-module-page/src/main/kotlin/AllModulesPageGeneration.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.jetbrains.dokka.allModulesPage - -import org.jetbrains.dokka.CoreExtensions -import org.jetbrains.dokka.Timer -import org.jetbrains.dokka.generation.Generation -import org.jetbrains.dokka.pages.RootPageNode -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.plugability.plugin -import org.jetbrains.dokka.plugability.query -import org.jetbrains.dokka.plugability.querySingle - -class AllModulesPageGeneration(private val context: DokkaContext) : Generation { - - override fun Timer.generate() { - report("Creating all modules page") - val pages = createAllModulePage() - - report("Transforming pages") - val transformedPages = transformAllModulesPage(pages) - - report("Rendering") - render(transformedPages) - - report("Processing submodules") - allModulesPagePlugin().querySingle { templateProcessor }.process() - } - - override val generationName = "index page for project" - - fun createAllModulePage() = allModulesPagePlugin().querySingle { allModulePageCreator }.invoke() - - fun transformAllModulesPage(pages: RootPageNode) = - allModulesPagePlugin().query { allModulePageTransformer }.fold(pages) { acc, t -> t(acc) } - - fun render(transformedPages: RootPageNode) { - context.single(CoreExtensions.renderer).render(transformedPages) - } - - private fun allModulesPagePlugin() = context.plugin() -} \ No newline at end of file diff --git a/plugins/all-module-page/src/main/kotlin/AllModulesPagePlugin.kt b/plugins/all-module-page/src/main/kotlin/AllModulesPagePlugin.kt deleted file mode 100644 index bcc43043..00000000 --- a/plugins/all-module-page/src/main/kotlin/AllModulesPagePlugin.kt +++ /dev/null @@ -1,53 +0,0 @@ -package org.jetbrains.dokka.allModulesPage - -import org.jetbrains.dokka.CoreExtensions -import org.jetbrains.dokka.allModulesPage.templates.* -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.plugability.DokkaPlugin -import org.jetbrains.dokka.transformers.pages.PageCreator -import org.jetbrains.dokka.transformers.pages.PageTransformer - -class AllModulesPagePlugin : DokkaPlugin() { - - val templateProcessor by extensionPoint() - val templateProcessingStrategy by extensionPoint() - val allModulePageCreator by extensionPoint() - val allModulePageTransformer by extensionPoint() - - val substitutor by extensionPoint() - - val allModulePageCreators by extending { - allModulePageCreator providing ::MultimodulePageCreator - } - - val multimoduleLocationProvider by extending { - (plugin().locationProviderFactory - providing MultimoduleLocationProvider::Factory - override plugin().locationProvider - applyIf { modules.size > 1 }) - } - - val allModulesPageGeneration by extending { - (CoreExtensions.generation - providing ::AllModulesPageGeneration - override plugin().singleGeneration) - } - - val defaultTemplateProcessor by extending { - templateProcessor providing ::DefaultTemplateProcessor - } - - val directiveBasedHtmlTemplateProcessingStrategy by extending { - templateProcessingStrategy providing ::DirectiveBasedHtmlTemplateProcessingStrategy order { - before(fallbackProcessingStrategy) - } - } - - val fallbackProcessingStrategy by extending { - templateProcessingStrategy providing ::FallbackTemplateProcessingStrategy - } - - val pathToRootSubstitutor by extending { - substitutor providing ::PathToRootSubstitutor - } -} \ No newline at end of file diff --git a/plugins/all-module-page/src/main/kotlin/MultimoduleLocationProvider.kt b/plugins/all-module-page/src/main/kotlin/MultimoduleLocationProvider.kt deleted file mode 100644 index cb1eca7b..00000000 --- a/plugins/all-module-page/src/main/kotlin/MultimoduleLocationProvider.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.jetbrains.dokka.allModulesPage - -import org.jetbrains.dokka.base.resolvers.local.DokkaBaseLocationProvider -import org.jetbrains.dokka.base.resolvers.local.DokkaLocationProvider -import org.jetbrains.dokka.base.resolvers.local.DokkaLocationProvider.Companion.identifierToFilename -import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.DisplaySourceSet -import org.jetbrains.dokka.pages.PageNode -import org.jetbrains.dokka.pages.RootPageNode -import org.jetbrains.dokka.plugability.DokkaContext - -open class MultimoduleLocationProvider(private val root: RootPageNode, val context: DokkaContext, private val fileExtension: String = ".html") : DokkaBaseLocationProvider(root, context, fileExtension) { - - protected open val defaultLocationProvider = DokkaLocationProvider(root, context) - val paths = context.configuration.modules.map { - it.name to it.relativePathToOutputDirectory - }.toMap() - - override fun resolve(dri: DRI, sourceSets: Set, context: PageNode?) = - dri.takeIf { it.packageName == MULTIMODULE_PACKAGE_PLACEHOLDER }?.classNames?.let { paths[it] }?.let { - "$it/${identifierToFilename(dri.classNames.orEmpty())}/index$fileExtension" - } ?: defaultLocationProvider.resolve(dri, sourceSets, context) - - override fun resolve(node: PageNode, context: PageNode?, skipExtension: Boolean) = - defaultLocationProvider.resolve(node, context, skipExtension) - - override fun pathToRoot(from: PageNode): String = defaultLocationProvider.pathToRoot(from) - - override fun ancestors(node: PageNode): List = listOf(root) - - companion object { - const val MULTIMODULE_PACKAGE_PLACEHOLDER = ".ext" - } - - class Factory(private val context: DokkaContext): LocationProviderFactory { - override fun getLocationProvider(pageNode: RootPageNode) = - MultimoduleLocationProvider(pageNode, context) - } -} diff --git a/plugins/all-module-page/src/main/kotlin/MultimodulePageCreator.kt b/plugins/all-module-page/src/main/kotlin/MultimodulePageCreator.kt deleted file mode 100644 index 123dee2b..00000000 --- a/plugins/all-module-page/src/main/kotlin/MultimodulePageCreator.kt +++ /dev/null @@ -1,97 +0,0 @@ -package org.jetbrains.dokka.allModulesPage - -import org.jetbrains.dokka.DokkaConfiguration.DokkaModuleDescription -import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet -import org.jetbrains.dokka.allModulesPage.MultimoduleLocationProvider.Companion.MULTIMODULE_PACKAGE_PLACEHOLDER -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentation.Classifier.Module -import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentationParsingContext -import org.jetbrains.dokka.base.parsers.moduleAndPackage.parseModuleAndPackageDocumentation -import org.jetbrains.dokka.base.parsers.moduleAndPackage.parseModuleAndPackageDocumentationFragments -import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint -import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter -import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.doc.DocTag -import org.jetbrains.dokka.model.doc.DocumentationNode -import org.jetbrains.dokka.model.doc.P -import org.jetbrains.dokka.model.properties.PropertyContainer -import org.jetbrains.dokka.pages.* -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.plugability.plugin -import org.jetbrains.dokka.plugability.querySingle -import org.jetbrains.dokka.transformers.pages.PageCreator -import org.jetbrains.dokka.utilities.DokkaLogger - -class MultimodulePageCreator( - private val context: DokkaContext, -) : PageCreator { - private val logger: DokkaLogger = context.logger - - private val commentsConverter by lazy { context.plugin().querySingle { commentsToContentConverter } } - private val signatureProvider by lazy { context.plugin().querySingle { signatureProvider } } - - override fun invoke(): RootPageNode { - val modules = context.configuration.modules - val sourceSetData = emptySet() - val builder = PageContentBuilder(commentsConverter, signatureProvider, context.logger) - val contentNode = builder.contentFor( - dri = DRI(MULTIMODULE_PACKAGE_PLACEHOLDER), - kind = ContentKind.Cover, - sourceSets = sourceSetData - ) { - header(2, "All modules:") - table(styles = setOf(MultimoduleTable)) { - modules.map { module -> - val displayedModuleDocumentation = getDisplayedModuleDocumentation(module) - val dri = DRI(packageName = MULTIMODULE_PACKAGE_PLACEHOLDER, classNames = module.name) - val dci = DCI(setOf(dri), ContentKind.Comment) - val extraWithAnchor = PropertyContainer.withAll(SymbolAnchorHint(module.name, ContentKind.Main)) - val header = linkNode(module.name, dri, DCI(setOf(dri), ContentKind.Main), extra = extraWithAnchor) - val content = ContentGroup( - children = - if (displayedModuleDocumentation != null) - DocTagToContentConverter().buildContent(displayedModuleDocumentation, dci, emptySet()) - else emptyList(), - dci = dci, - sourceSets = emptySet(), - style = emptySet() - ) - ContentGroup(listOf(header, content), dci, emptySet(), emptySet(), extraWithAnchor) - } - } - } - return MultimoduleRootPageNode( - setOf(DRI(packageName = MULTIMODULE_PACKAGE_PLACEHOLDER, classNames = "allModules")), - contentNode - ) - } - - private fun getDisplayedModuleDocumentation(module: DokkaModuleDescription): P? { - val parsingContext = ModuleAndPackageDocumentationParsingContext(logger) - - val documentationFragment = module.includes - .flatMap { include -> parseModuleAndPackageDocumentationFragments(include) } - .firstOrNull { fragment -> fragment.classifier == Module && fragment.name == module.name } - ?: return null - - val moduleDocumentation = parseModuleAndPackageDocumentation(parsingContext, documentationFragment) - return moduleDocumentation.documentation.firstParagraph() - } - - private fun DocumentationNode.firstParagraph(): P? = - this.children - .map { it.root } - .mapNotNull { it.firstParagraph() } - .firstOrNull() - - /** - * @return The very first, most inner paragraph. If any [P] is wrapped inside another [P], the inner one - * is preferred. - */ - private fun DocTag.firstParagraph(): P? { - val firstChildParagraph = children.mapNotNull { it.firstParagraph() }.firstOrNull() - return if (firstChildParagraph == null && this is P) this - else firstChildParagraph - } -} diff --git a/plugins/all-module-page/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt b/plugins/all-module-page/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt deleted file mode 100644 index feba2d4e..00000000 --- a/plugins/all-module-page/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt +++ /dev/null @@ -1,138 +0,0 @@ -package org.jetbrains.dokka.allModulesPage.templates - -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.jetbrains.dokka.allModulesPage.AllModulesPagePlugin -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.* -import org.jsoup.parser.Tag -import java.io.File -import java.nio.file.Files -import java.util.concurrent.ConcurrentHashMap - -class DirectiveBasedHtmlTemplateProcessingStrategy(private val context: DokkaContext) : TemplateProcessingStrategy { - private val navigationFragments = ConcurrentHashMap() - - private val substitutors = context.plugin().query { substitutor } - private val externalModuleLinkResolver = ExternalModuleLinkResolver(context) - - override suspend fun process(input: File, output: File): Boolean = coroutineScope { - if (input.extension == "html") { - launch { - val document = withContext(IO) { Jsoup.parse(input, "UTF-8") } - document.outputSettings().indentAmount(0).prettyPrint(false) - document.select("dokka-template-command").forEach { - when (val command = parseJson(it.attr("data"))) { - 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") - } - } - withContext(IO) { Files.write(output.toPath(), listOf(document.outerHtml())) } - } - true - } else false - } - - private fun substitute(element: Element, 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) - } - - private fun Node.traverseToSubstitute(regex: Regex, commandContext: TemplatingContext) { - 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) = 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, 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") - } - val node = Element(Tag.valueOf("div"), "", attributes) - navigationFragments.entries.sortedBy { it.key }.forEach { (moduleName, command) -> - command.select("a").forEach { a -> - a.attr("href")?.also { a.attr("href", "${moduleName}/${it}") } - } - command.childNodes().toList().forEachIndexed { index, child -> - if (index == 0) { - child.attr("id", "$moduleName-nav-submenu") - } - node.appendChild(child) - } - } - - withContext(IO) { - Files.write(output.resolve("navigation.html").toPath(), listOf(node.outerHtml())) - } - - node.select("a").forEach { a -> - a.attr("href")?.also { a.attr("href", "../${it}") } - } - navigationFragments.keys.forEach { - withContext(IO) { - Files.write( - output.resolve(it).resolve("navigation.html").toPath(), - listOf(node.outerHtml()) - ) - } - } - } - - private fun resolveLink(it: Element, command: ResolveLinkCommand, fileContext: File) { - - val link = externalModuleLinkResolver.resolve(command.dri, fileContext) - if (link == 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 attributes = Attributes().apply { - put("href", link) // TODO: resolve - } - val children = it.childNodes().toList() - val element = Element(Tag.valueOf("a"), "", attributes).apply { - children.forEach { ch -> appendChild(ch) } - } - it.replaceWith(element) - } -} diff --git a/plugins/all-module-page/src/main/kotlin/templates/ExternalModuleLinkResolver.kt b/plugins/all-module-page/src/main/kotlin/templates/ExternalModuleLinkResolver.kt deleted file mode 100644 index 5a3d3d33..00000000 --- a/plugins/all-module-page/src/main/kotlin/templates/ExternalModuleLinkResolver.kt +++ /dev/null @@ -1,49 +0,0 @@ -package org.jetbrains.dokka.allModulesPage.templates - -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.links.DRI -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.plugability.plugin -import org.jetbrains.dokka.plugability.query -import java.net.URL -import java.io.File - -class ExternalModuleLinkResolver(val context: DokkaContext) { - private val elpFactory = context.plugin().query { externalLocationProviderFactory } - private val externalDocumentations by lazy(::setupExternalDocumentations) - private val elps by lazy { - elpFactory.flatMap { externalDocumentations.map { ed -> it.getExternalLocationProvider(ed) } }.filterNotNull() - } - - private fun setupExternalDocumentations(): List { - 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() - return packageLists.map { (module, pckgList) -> - ExternalDocumentation( - URL("file:/${module.name}/${module.name}"), - pckgList!! - ) - } - } - - fun resolve(dri: DRI, fileContext: File): String? { - val absoluteLink = elps.mapNotNull { it.resolve(dri) }.firstOrNull() ?: return null - val modulePath = context.configuration.outputDir.absolutePath.split(File.separator) - val contextPath = fileContext.absolutePath.split(File.separator) - val commonPathElements = modulePath.zip(contextPath) - .takeWhile { (a, b) -> a == b }.count() - - return (List(contextPath.size - commonPathElements - 1) { ".." } + modulePath.drop(commonPathElements)).joinToString( - "/" - ) + absoluteLink.removePrefix("file:") - } -} \ No newline at end of file diff --git a/plugins/all-module-page/src/main/kotlin/templates/FallbackTemplateProcessingStrategy.kt b/plugins/all-module-page/src/main/kotlin/templates/FallbackTemplateProcessingStrategy.kt deleted file mode 100644 index 9b5251ac..00000000 --- a/plugins/all-module-page/src/main/kotlin/templates/FallbackTemplateProcessingStrategy.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.jetbrains.dokka.allModulesPage.templates - -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import org.jetbrains.dokka.plugability.DokkaContext -import java.io.File -import java.nio.file.Files - -class FallbackTemplateProcessingStrategy(dokkaContext: DokkaContext) : TemplateProcessingStrategy { - - override suspend fun process(input: File, output: File): Boolean = coroutineScope { - launch(IO) { - Files.copy(input.toPath(), output.toPath()) - } - true - } -} \ 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 deleted file mode 100644 index 5056b724..00000000 --- a/plugins/all-module-page/src/main/kotlin/templates/PathToRootSubstitutor.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.jetbrains.dokka.allModulesPage.templates - -import org.jetbrains.dokka.base.templating.PathToRootSubstitutionCommand -import org.jetbrains.dokka.base.templating.SubstitutionCommand -import org.jetbrains.dokka.plugability.DokkaContext -import java.io.File - -class PathToRootSubstitutor(private val dokkaContext: DokkaContext) : Substitutor { - override fun trySubstitute(context: TemplatingContext, match: MatchResult): String? = - if (context.command is PathToRootSubstitutionCommand) { - context.output.toPath().parent.relativize(dokkaContext.configuration.outputDir.toPath()).toString().split(File.separator).joinToString(separator = "/", postfix = "/") { it } - } 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 deleted file mode 100644 index 98f1d88e..00000000 --- a/plugins/all-module-page/src/main/kotlin/templates/Substitutor.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.jetbrains.dokka.allModulesPage.templates - -import org.jetbrains.dokka.base.templating.SubstitutionCommand - -fun interface Substitutor { - fun trySubstitute(context: TemplatingContext, 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 deleted file mode 100644 index 18d63df0..00000000 --- a/plugins/all-module-page/src/main/kotlin/templates/TemplateProcessor.kt +++ /dev/null @@ -1,60 +0,0 @@ -package org.jetbrains.dokka.allModulesPage.templates - -import kotlinx.coroutines.* -import org.jetbrains.dokka.allModulesPage.AllModulesPagePlugin -import org.jetbrains.dokka.base.templating.Command -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.plugability.plugin -import org.jetbrains.dokka.plugability.query -import org.jsoup.nodes.Element -import java.io.File -import java.nio.file.Files -import java.nio.file.Path -import kotlin.coroutines.coroutineContext - -interface TemplateProcessor { - fun process() -} - -interface TemplateProcessingStrategy { - suspend fun process(input: File, output: File): Boolean - suspend fun finish(output: File) {} -} - -class DefaultTemplateProcessor( - private val context: DokkaContext, -): TemplateProcessor { - - private val strategies: List = context.plugin().query { templateProcessingStrategy } - - override fun process() = runBlocking(Dispatchers.Default) { - coroutineScope { - context.configuration.modules.forEach { - launch { - it.sourceOutputDirectory.visit(context.configuration.outputDir.resolve(it.relativePathToOutputDirectory)) - } - } - } - strategies.map { it.finish(context.configuration.outputDir) } - Unit - } - - private suspend fun File.visit(target: File): Unit = coroutineScope { - val source = this@visit - if (source.isDirectory) { - target.mkdir() - source.list()?.forEach { - launch { source.resolve(it).visit(target.resolve(it)) } - } - } else { - strategies.asSequence().first { it.process(source, target) } - } - } -} - -data class TemplatingContext( - val input: File, - val output: File, - val element: Element, - val command: T, -) \ No newline at end of file diff --git a/plugins/all-module-page/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/plugins/all-module-page/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin deleted file mode 100644 index 3ac59dc6..00000000 --- a/plugins/all-module-page/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin +++ /dev/null @@ -1 +0,0 @@ -org.jetbrains.dokka.allModulesPage.AllModulesPagePlugin diff --git a/plugins/all-modules-page/build.gradle.kts b/plugins/all-modules-page/build.gradle.kts new file mode 100644 index 00000000..a0c5a5ed --- /dev/null +++ b/plugins/all-modules-page/build.gradle.kts @@ -0,0 +1,14 @@ +import org.jetbrains.registerDokkaArtifactPublication + +registerDokkaArtifactPublication("dokkaAllModulesPage") { + artifactId = "all-modules-page-plugin" +} + +dependencies { + implementation(project(":plugins:base")) + + val coroutines_version: String by project + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version") + + implementation("org.jsoup:jsoup:1.12.1") +} \ No newline at end of file diff --git a/plugins/all-modules-page/src/main/kotlin/AllModulesPageGeneration.kt b/plugins/all-modules-page/src/main/kotlin/AllModulesPageGeneration.kt new file mode 100644 index 00000000..5ac854b4 --- /dev/null +++ b/plugins/all-modules-page/src/main/kotlin/AllModulesPageGeneration.kt @@ -0,0 +1,43 @@ +package org.jetbrains.dokka.allModulesPage + +import org.jetbrains.dokka.CoreExtensions +import org.jetbrains.dokka.Timer +import org.jetbrains.dokka.generation.Generation +import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.query +import org.jetbrains.dokka.plugability.querySingle + +class AllModulesPageGeneration(private val context: DokkaContext) : Generation { + + private val allModulesPagePlugin by lazy { context.plugin() } + + override fun Timer.generate() { + report("Creating all modules page") + val pages = createAllModulesPage() + + report("Transforming pages") + val transformedPages = transformAllModulesPage(pages) + + report("Rendering") + render(transformedPages) + + report("Processing submodules") + processSubmodules() + } + + override val generationName = "index page for project" + + fun createAllModulesPage() = allModulesPagePlugin.querySingle { allModulesPageCreator }.invoke() + + fun transformAllModulesPage(pages: RootPageNode) = + allModulesPagePlugin.query { allModulesPageTransformer }.fold(pages) { acc, t -> t(acc) } + + fun render(transformedPages: RootPageNode) { + context.single(CoreExtensions.renderer).render(transformedPages) + } + + fun processSubmodules() = + allModulesPagePlugin.querySingle { templateProcessor }.process() +} \ No newline at end of file diff --git a/plugins/all-modules-page/src/main/kotlin/AllModulesPagePlugin.kt b/plugins/all-modules-page/src/main/kotlin/AllModulesPagePlugin.kt new file mode 100644 index 00000000..95a94cf4 --- /dev/null +++ b/plugins/all-modules-page/src/main/kotlin/AllModulesPagePlugin.kt @@ -0,0 +1,64 @@ +package org.jetbrains.dokka.allModulesPage + +import org.jetbrains.dokka.CoreExtensions +import org.jetbrains.dokka.allModulesPage.templates.* +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.resolvers.local.DokkaLocationProviderFactory +import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory +import org.jetbrains.dokka.plugability.DokkaPlugin +import org.jetbrains.dokka.transformers.pages.PageCreator +import org.jetbrains.dokka.transformers.pages.PageTransformer + +class AllModulesPagePlugin : DokkaPlugin() { + + val templateProcessor by extensionPoint() + val templateProcessingStrategy by extensionPoint() + val partialLocationProviderFactory by extensionPoint() + val allModulesPageCreator by extensionPoint() + val allModulesPageTransformer by extensionPoint() + val externalModuleLinkResolver by extensionPoint() + + val substitutor by extensionPoint() + + val allModulesPageCreators by extending { + allModulesPageCreator providing ::MultimodulePageCreator + } + + val multimoduleLocationProvider by extending { + (plugin().locationProviderFactory + providing MultimoduleLocationProvider::Factory + override plugin().locationProvider) + } + + val baseLocationProviderFactory by extending { + partialLocationProviderFactory providing ::DokkaLocationProviderFactory + } + + val allModulesPageGeneration by extending { + (CoreExtensions.generation + providing ::AllModulesPageGeneration + override plugin().singleGeneration) + } + + val defaultTemplateProcessor by extending { + templateProcessor providing ::DefaultTemplateProcessor + } + + val directiveBasedHtmlTemplateProcessingStrategy by extending { + templateProcessingStrategy providing ::DirectiveBasedHtmlTemplateProcessingStrategy order { + before(fallbackProcessingStrategy) + } + } + + val fallbackProcessingStrategy by extending { + templateProcessingStrategy providing ::FallbackTemplateProcessingStrategy + } + + val pathToRootSubstitutor by extending { + substitutor providing ::PathToRootSubstitutor + } + + val multiModuleLinkResolver by extending { + externalModuleLinkResolver providing ::DefaultExternalModuleLinkResolver + } +} \ No newline at end of file diff --git a/plugins/all-modules-page/src/main/kotlin/MultimoduleLocationProvider.kt b/plugins/all-modules-page/src/main/kotlin/MultimoduleLocationProvider.kt new file mode 100644 index 00000000..1dbbd386 --- /dev/null +++ b/plugins/all-modules-page/src/main/kotlin/MultimoduleLocationProvider.kt @@ -0,0 +1,36 @@ +package org.jetbrains.dokka.allModulesPage + +import org.jetbrains.dokka.allModulesPage.MultimodulePageCreator.Companion.MULTIMODULE_PACKAGE_PLACEHOLDER +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.resolvers.local.DokkaBaseLocationProvider +import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.DisplaySourceSet +import org.jetbrains.dokka.pages.PageNode +import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle + +open class MultimoduleLocationProvider(private val root: RootPageNode, dokkaContext: DokkaContext) : DokkaBaseLocationProvider(root, dokkaContext) { + + private val defaultLocationProvider = dokkaContext.plugin().querySingle { partialLocationProviderFactory }.getLocationProvider(root) + private val externalModuleLinkResolver = dokkaContext.plugin().querySingle { externalModuleLinkResolver } + + override fun resolve(dri: DRI, sourceSets: Set, context: PageNode?) = + if (dri == MultimodulePageCreator.MULTIMODULE_ROOT_DRI) pathToRoot(root) + else dri.takeIf { it.packageName == MULTIMODULE_PACKAGE_PLACEHOLDER }?.classNames + ?.let(externalModuleLinkResolver::resolveLinkToModuleIndex) + + override fun resolve(node: PageNode, context: PageNode?, skipExtension: Boolean) = + defaultLocationProvider.resolve(node, context, skipExtension) + + override fun pathToRoot(from: PageNode): String = defaultLocationProvider.pathToRoot(from) + + override fun ancestors(node: PageNode): List = listOf(root) + + class Factory(private val context: DokkaContext) : LocationProviderFactory { + override fun getLocationProvider(pageNode: RootPageNode) = + MultimoduleLocationProvider(pageNode, context) + } +} diff --git a/plugins/all-modules-page/src/main/kotlin/MultimodulePageCreator.kt b/plugins/all-modules-page/src/main/kotlin/MultimodulePageCreator.kt new file mode 100644 index 00000000..7f441aa1 --- /dev/null +++ b/plugins/all-modules-page/src/main/kotlin/MultimodulePageCreator.kt @@ -0,0 +1,101 @@ +package org.jetbrains.dokka.allModulesPage + +import org.jetbrains.dokka.DokkaConfiguration.DokkaModuleDescription +import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentation.Classifier.Module +import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentationParsingContext +import org.jetbrains.dokka.base.parsers.moduleAndPackage.parseModuleAndPackageDocumentation +import org.jetbrains.dokka.base.parsers.moduleAndPackage.parseModuleAndPackageDocumentationFragments +import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint +import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter +import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.doc.DocTag +import org.jetbrains.dokka.model.doc.DocumentationNode +import org.jetbrains.dokka.model.doc.P +import org.jetbrains.dokka.model.properties.PropertyContainer +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle +import org.jetbrains.dokka.transformers.pages.PageCreator +import org.jetbrains.dokka.utilities.DokkaLogger + +class MultimodulePageCreator( + private val context: DokkaContext, +) : PageCreator { + private val logger: DokkaLogger = context.logger + + private val commentsConverter by lazy { context.plugin().querySingle { commentsToContentConverter } } + private val signatureProvider by lazy { context.plugin().querySingle { signatureProvider } } + + override fun invoke(): RootPageNode { + val modules = context.configuration.modules + val sourceSetData = emptySet() + val builder = PageContentBuilder(commentsConverter, signatureProvider, context.logger) + val contentNode = builder.contentFor( + dri = DRI(MULTIMODULE_PACKAGE_PLACEHOLDER), + kind = ContentKind.Cover, + sourceSets = sourceSetData + ) { + header(2, "All modules:") + table(styles = setOf(MultimoduleTable)) { + modules.map { module -> + val displayedModuleDocumentation = getDisplayedModuleDocumentation(module) + val dri = DRI(packageName = MULTIMODULE_PACKAGE_PLACEHOLDER, classNames = module.name) + val dci = DCI(setOf(dri), ContentKind.Comment) + val extraWithAnchor = PropertyContainer.withAll(SymbolAnchorHint(module.name, ContentKind.Main)) + val header = linkNode(module.name, dri, DCI(setOf(dri), ContentKind.Main), extra = extraWithAnchor) + val content = ContentGroup( + children = + if (displayedModuleDocumentation != null) + DocTagToContentConverter().buildContent(displayedModuleDocumentation, dci, emptySet()) + else emptyList(), + dci = dci, + sourceSets = emptySet(), + style = emptySet() + ) + ContentGroup(listOf(header, content), dci, emptySet(), emptySet(), extraWithAnchor) + } + } + } + return MultimoduleRootPageNode( + setOf(MULTIMODULE_ROOT_DRI), + contentNode + ) + } + + private fun getDisplayedModuleDocumentation(module: DokkaModuleDescription): P? { + val parsingContext = ModuleAndPackageDocumentationParsingContext(logger) + + val documentationFragment = module.includes + .flatMap { include -> parseModuleAndPackageDocumentationFragments(include) } + .firstOrNull { fragment -> fragment.classifier == Module && fragment.name == module.name } + ?: return null + + val moduleDocumentation = parseModuleAndPackageDocumentation(parsingContext, documentationFragment) + return moduleDocumentation.documentation.firstParagraph() + } + + private fun DocumentationNode.firstParagraph(): P? = + this.children + .map { it.root } + .mapNotNull { it.firstParagraph() } + .firstOrNull() + + /** + * @return The very first, most inner paragraph. If any [P] is wrapped inside another [P], the inner one + * is preferred. + */ + private fun DocTag.firstParagraph(): P? { + val firstChildParagraph = children.mapNotNull { it.firstParagraph() }.firstOrNull() + return if (firstChildParagraph == null && this is P) this + else firstChildParagraph + } + + companion object { + const val MULTIMODULE_PACKAGE_PLACEHOLDER = ".ext" + val MULTIMODULE_ROOT_DRI = DRI(packageName = MULTIMODULE_PACKAGE_PLACEHOLDER, classNames = "allModules") + } +} diff --git a/plugins/all-modules-page/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt b/plugins/all-modules-page/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt new file mode 100644 index 00000000..2b065731 --- /dev/null +++ b/plugins/all-modules-page/src/main/kotlin/templates/DirectiveBasedTemplateProcessing.kt @@ -0,0 +1,141 @@ +package org.jetbrains.dokka.allModulesPage.templates + +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jetbrains.dokka.allModulesPage.AllModulesPagePlugin +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.jetbrains.dokka.plugability.querySingle +import org.jsoup.Jsoup +import org.jsoup.nodes.* +import org.jsoup.parser.Tag +import java.io.File +import java.nio.file.Files +import java.util.concurrent.ConcurrentHashMap + +class DirectiveBasedHtmlTemplateProcessingStrategy(private val context: DokkaContext) : TemplateProcessingStrategy { + private val navigationFragments = ConcurrentHashMap() + + private val substitutors = context.plugin().query { substitutor } + private val externalModuleLinkResolver = context.plugin().querySingle { externalModuleLinkResolver } + + override suspend fun process(input: File, output: File): Boolean = coroutineScope { + if (input.extension == "html") { + launch { + val document = withContext(IO) { Jsoup.parse(input, "UTF-8") } + document.outputSettings().indentAmount(0).prettyPrint(false) + document.select("dokka-template-command").forEach { + when (val command = parseJson(it.attr("data"))) { + 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") + } + } + withContext(IO) { Files.write(output.toPath(), listOf(document.outerHtml())) } + } + true + } else false + } + + private fun substitute(element: Element, 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) + } + + private fun Node.traverseToSubstitute(regex: Regex, commandContext: TemplatingContext) { + 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) = 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, match: MatchResult): String = + substitutors.asSequence().mapNotNull { it.trySubstitute(commandContext, match) }.firstOrNull() ?: match.value + + override suspend fun finish(output: File) { + if (navigationFragments.isNotEmpty()) { + val attributes = Attributes().apply { + put("class", "sideMenu") + } + val node = Element(Tag.valueOf("div"), "", attributes) + navigationFragments.entries.sortedBy { it.key }.forEach { (moduleName, command) -> + command.select("a").forEach { a -> + a.attr("href")?.also { a.attr("href", "${moduleName}/${it}") } + } + command.childNodes().toList().forEachIndexed { index, child -> + if (index == 0) { + child.attr("id", "$moduleName-nav-submenu") + } + node.appendChild(child) + } + } + + withContext(IO) { + Files.write(output.resolve("navigation.html").toPath(), listOf(node.outerHtml())) + } + + node.select("a").forEach { a -> + a.attr("href")?.also { a.attr("href", "../${it}") } + } + navigationFragments.keys.forEach { + withContext(IO) { + Files.write( + output.resolve(it).resolve("navigation.html").toPath(), + listOf(node.outerHtml()) + ) + } + } + } + } + + private fun resolveLink(it: Element, command: ResolveLinkCommand, fileContext: File) { + + val link = externalModuleLinkResolver.resolve(command.dri, fileContext) + if (link == 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 attributes = Attributes().apply { + put("href", link) + } + val children = it.childNodes().toList() + val element = Element(Tag.valueOf("a"), "", attributes).apply { + children.forEach { ch -> appendChild(ch) } + } + it.replaceWith(element) + } +} diff --git a/plugins/all-modules-page/src/main/kotlin/templates/ExternalModuleLinkResolver.kt b/plugins/all-modules-page/src/main/kotlin/templates/ExternalModuleLinkResolver.kt new file mode 100644 index 00000000..d0e787b6 --- /dev/null +++ b/plugins/all-modules-page/src/main/kotlin/templates/ExternalModuleLinkResolver.kt @@ -0,0 +1,77 @@ +package org.jetbrains.dokka.allModulesPage.templates + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.resolvers.local.DokkaLocationProvider.Companion.identifierToFilename +import org.jetbrains.dokka.base.resolvers.shared.ExternalDocumentation +import org.jetbrains.dokka.base.resolvers.shared.PackageList +import org.jetbrains.dokka.base.resolvers.shared.RecognizedLinkFormat +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.query +import java.io.File +import java.net.URL + +interface ExternalModuleLinkResolver { + fun resolve(dri: DRI, fileContext: File): String? + fun resolveLinkToModuleIndex(moduleName: String): String? +} + +class DefaultExternalModuleLinkResolver(val context: DokkaContext) : ExternalModuleLinkResolver { + private val elpFactory = context.plugin().query { externalLocationProviderFactory } + private val externalDocumentations by lazy(::setupExternalDocumentations) + private val elps by lazy { + elpFactory.flatMap { externalDocumentations.map { ed -> it.getExternalLocationProvider(ed) } }.filterNotNull() + } + + private fun setupExternalDocumentations(): List { + val packageLists = + context.configuration.modules.map(::loadPackageListForModule).toMap() + return packageLists.mapNotNull { (module, packageList) -> + packageList?.let { + ExternalDocumentation( + URL("file:/${module.name}/${module.name}"), + packageList + ) + } + } + } + + private fun loadPackageListForModule(module: DokkaConfiguration.DokkaModuleDescription) = + module.sourceOutputDirectory.resolve(File(identifierToFilename(module.name))).let { + it to PackageList.load( + URL("file:" + it.resolve("package-list").path), + 8, + true + ) + } + + override fun resolve(dri: DRI, fileContext: File): String? { + val absoluteLink = elps.mapNotNull { it.resolve(dri) }.firstOrNull() ?: return null + val modulePath = context.configuration.outputDir.absolutePath.split(File.separator) + val contextPath = fileContext.absolutePath.split(File.separator) + val commonPathElements = modulePath.zip(contextPath) + .takeWhile { (a, b) -> a == b }.count() + + return (List(contextPath.size - commonPathElements - 1) { ".." } + modulePath.drop(commonPathElements)).joinToString( + "/" + ) + absoluteLink.removePrefix("file:") + } + + override fun resolveLinkToModuleIndex(moduleName: String): String? = + context.configuration.modules.firstOrNull { it.name == moduleName } + ?.let { module -> + val (_, packageList) = loadPackageListForModule(module) + val extension = when (packageList?.linkFormat) { + RecognizedLinkFormat.KotlinWebsiteHtml, + RecognizedLinkFormat.DokkaOldHtml, + RecognizedLinkFormat.DokkaHtml -> ".html" + RecognizedLinkFormat.DokkaGFM, + RecognizedLinkFormat.DokkaJekyll -> ".md" + else -> "" + } + "${module.relativePathToOutputDirectory}/${identifierToFilename(moduleName)}/index$extension" + } + +} diff --git a/plugins/all-modules-page/src/main/kotlin/templates/FallbackTemplateProcessingStrategy.kt b/plugins/all-modules-page/src/main/kotlin/templates/FallbackTemplateProcessingStrategy.kt new file mode 100644 index 00000000..9b5251ac --- /dev/null +++ b/plugins/all-modules-page/src/main/kotlin/templates/FallbackTemplateProcessingStrategy.kt @@ -0,0 +1,18 @@ +package org.jetbrains.dokka.allModulesPage.templates + +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import org.jetbrains.dokka.plugability.DokkaContext +import java.io.File +import java.nio.file.Files + +class FallbackTemplateProcessingStrategy(dokkaContext: DokkaContext) : TemplateProcessingStrategy { + + override suspend fun process(input: File, output: File): Boolean = coroutineScope { + launch(IO) { + Files.copy(input.toPath(), output.toPath()) + } + true + } +} \ No newline at end of file diff --git a/plugins/all-modules-page/src/main/kotlin/templates/PathToRootSubstitutor.kt b/plugins/all-modules-page/src/main/kotlin/templates/PathToRootSubstitutor.kt new file mode 100644 index 00000000..5056b724 --- /dev/null +++ b/plugins/all-modules-page/src/main/kotlin/templates/PathToRootSubstitutor.kt @@ -0,0 +1,14 @@ +package org.jetbrains.dokka.allModulesPage.templates + +import org.jetbrains.dokka.base.templating.PathToRootSubstitutionCommand +import org.jetbrains.dokka.base.templating.SubstitutionCommand +import org.jetbrains.dokka.plugability.DokkaContext +import java.io.File + +class PathToRootSubstitutor(private val dokkaContext: DokkaContext) : Substitutor { + override fun trySubstitute(context: TemplatingContext, match: MatchResult): String? = + if (context.command is PathToRootSubstitutionCommand) { + context.output.toPath().parent.relativize(dokkaContext.configuration.outputDir.toPath()).toString().split(File.separator).joinToString(separator = "/", postfix = "/") { it } + } else null + +} \ No newline at end of file diff --git a/plugins/all-modules-page/src/main/kotlin/templates/Substitutor.kt b/plugins/all-modules-page/src/main/kotlin/templates/Substitutor.kt new file mode 100644 index 00000000..98f1d88e --- /dev/null +++ b/plugins/all-modules-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, match: MatchResult): String? +} \ No newline at end of file diff --git a/plugins/all-modules-page/src/main/kotlin/templates/TemplateProcessor.kt b/plugins/all-modules-page/src/main/kotlin/templates/TemplateProcessor.kt new file mode 100644 index 00000000..18d63df0 --- /dev/null +++ b/plugins/all-modules-page/src/main/kotlin/templates/TemplateProcessor.kt @@ -0,0 +1,60 @@ +package org.jetbrains.dokka.allModulesPage.templates + +import kotlinx.coroutines.* +import org.jetbrains.dokka.allModulesPage.AllModulesPagePlugin +import org.jetbrains.dokka.base.templating.Command +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.query +import org.jsoup.nodes.Element +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import kotlin.coroutines.coroutineContext + +interface TemplateProcessor { + fun process() +} + +interface TemplateProcessingStrategy { + suspend fun process(input: File, output: File): Boolean + suspend fun finish(output: File) {} +} + +class DefaultTemplateProcessor( + private val context: DokkaContext, +): TemplateProcessor { + + private val strategies: List = context.plugin().query { templateProcessingStrategy } + + override fun process() = runBlocking(Dispatchers.Default) { + coroutineScope { + context.configuration.modules.forEach { + launch { + it.sourceOutputDirectory.visit(context.configuration.outputDir.resolve(it.relativePathToOutputDirectory)) + } + } + } + strategies.map { it.finish(context.configuration.outputDir) } + Unit + } + + private suspend fun File.visit(target: File): Unit = coroutineScope { + val source = this@visit + if (source.isDirectory) { + target.mkdir() + source.list()?.forEach { + launch { source.resolve(it).visit(target.resolve(it)) } + } + } else { + strategies.asSequence().first { it.process(source, target) } + } + } +} + +data class TemplatingContext( + val input: File, + val output: File, + val element: Element, + val command: T, +) \ No newline at end of file diff --git a/plugins/all-modules-page/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/plugins/all-modules-page/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin new file mode 100644 index 00000000..3ac59dc6 --- /dev/null +++ b/plugins/all-modules-page/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin @@ -0,0 +1 @@ +org.jetbrains.dokka.allModulesPage.AllModulesPagePlugin diff --git a/plugins/base/base-test-utils/src/main/kotlin/testRunner/baseTestApi.kt b/plugins/base/base-test-utils/src/main/kotlin/testRunner/baseTestApi.kt index cfc5ed43..a0c681eb 100644 --- a/plugins/base/base-test-utils/src/main/kotlin/testRunner/baseTestApi.kt +++ b/plugins/base/base-test-utils/src/main/kotlin/testRunner/baseTestApi.kt @@ -104,7 +104,7 @@ class BaseTestBuilder : TestBuilder() { ) } -open class BaseAbstractTest(logger: TestLogger = TestLogger(DokkaConsoleLogger)) : AbstractTest( +abstract class BaseAbstractTest(logger: TestLogger = TestLogger(DokkaConsoleLogger)) : AbstractTest( ::BaseTestBuilder, ::BaseDokkaTestGenerator, logger, diff --git a/plugins/base/src/main/kotlin/DokkaBase.kt b/plugins/base/src/main/kotlin/DokkaBase.kt index c4dacdcd..ac070ab4 100644 --- a/plugins/base/src/main/kotlin/DokkaBase.kt +++ b/plugins/base/src/main/kotlin/DokkaBase.kt @@ -32,8 +32,7 @@ import org.jetbrains.dokka.plugability.DokkaPlugin import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer import org.jetbrains.dokka.transformers.pages.PageTransformer -class -DokkaBase : DokkaPlugin() { +class DokkaBase : DokkaPlugin() { val preMergeDocumentableTransformer by extensionPoint() val pageMergerStrategy by extensionPoint() diff --git a/plugins/base/src/main/kotlin/generation/SingleModuleGeneration.kt b/plugins/base/src/main/kotlin/generation/SingleModuleGeneration.kt index eb405a3d..1689a1dd 100644 --- a/plugins/base/src/main/kotlin/generation/SingleModuleGeneration.kt +++ b/plugins/base/src/main/kotlin/generation/SingleModuleGeneration.kt @@ -8,6 +8,8 @@ import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.DokkaException import org.jetbrains.dokka.Timer import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.generation.Generation +import org.jetbrains.dokka.generation.exitGenerationGracefully import org.jetbrains.dokka.model.DModule import org.jetbrains.dokka.pages.RootPageNode import org.jetbrains.dokka.plugability.DokkaContext diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt index ba5329bf..3bafb8f5 100644 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt @@ -712,7 +712,6 @@ open class HtmlRenderer( buildHtml(page, page.embeddedResources) { div("main-content") { id = "content" - // TODO: Investigate possible problem attributes["pageIds"] = "${context.configuration.moduleName}::${page.pageId}" content(this, page) } diff --git a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt index fbebec70..cc97bb72 100644 --- a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt +++ b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt @@ -70,7 +70,8 @@ open class NavigationSearchInstaller(val context: DokkaContext) : NavigationData open class NavigationPageInstaller(val context: DokkaContext) : NavigationDataProvider(), PageTransformer { override fun invoke(input: RootPageNode): RootPageNode = - input.modified(children = input.children + NavigationPage(navigableChildren(input))) + input.modified(children = input.children + NavigationPage(navigableChildren(input), + (listOf(input) + input.children).firstOrNull { it is ContentPage && it.name.isNotBlank() }?.name.orEmpty())) } class CustomResourceInstaller(val dokkaContext: DokkaContext) : PageTransformer { diff --git a/plugins/base/src/main/kotlin/resolvers/local/DefaultLocationProvider.kt b/plugins/base/src/main/kotlin/resolvers/local/DefaultLocationProvider.kt index 293ca2d4..c956b308 100644 --- a/plugins/base/src/main/kotlin/resolvers/local/DefaultLocationProvider.kt +++ b/plugins/base/src/main/kotlin/resolvers/local/DefaultLocationProvider.kt @@ -4,7 +4,6 @@ import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.resolvers.external.ExternalLocationProvider import org.jetbrains.dokka.base.resolvers.shared.ExternalDocumentation import org.jetbrains.dokka.base.resolvers.shared.PackageList -import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.DisplaySourceSet import org.jetbrains.dokka.pages.RootPageNode @@ -14,8 +13,7 @@ import org.jetbrains.dokka.plugability.query abstract class DefaultLocationProvider( protected val pageGraphRoot: RootPageNode, - protected val dokkaContext: DokkaContext, - protected val extension: String + protected val dokkaContext: DokkaContext ) : LocationProvider { protected val externalLocationProviderFactories = dokkaContext.plugin().query { externalLocationProviderFactory } diff --git a/plugins/base/src/main/kotlin/resolvers/local/DokkaBaseLocationProvider.kt b/plugins/base/src/main/kotlin/resolvers/local/DokkaBaseLocationProvider.kt index b1213b19..fa133045 100644 --- a/plugins/base/src/main/kotlin/resolvers/local/DokkaBaseLocationProvider.kt +++ b/plugins/base/src/main/kotlin/resolvers/local/DokkaBaseLocationProvider.kt @@ -8,9 +8,8 @@ import org.jetbrains.dokka.utilities.urlEncoded abstract class DokkaBaseLocationProvider( pageGraphRoot: RootPageNode, - dokkaContext: DokkaContext, - extension: String -) : DefaultLocationProvider(pageGraphRoot, dokkaContext, extension) { + dokkaContext: DokkaContext +) : DefaultLocationProvider(pageGraphRoot, dokkaContext) { /** * Anchors should be unique and should contain sourcesets, dri and contentKind. diff --git a/plugins/base/src/main/kotlin/resolvers/local/DokkaLocationProvider.kt b/plugins/base/src/main/kotlin/resolvers/local/DokkaLocationProvider.kt index 437a1c58..c10029bc 100644 --- a/plugins/base/src/main/kotlin/resolvers/local/DokkaLocationProvider.kt +++ b/plugins/base/src/main/kotlin/resolvers/local/DokkaLocationProvider.kt @@ -14,8 +14,8 @@ import java.util.* open class DokkaLocationProvider( pageGraphRoot: RootPageNode, dokkaContext: DokkaContext, - extension: String = ".html" -) : DokkaBaseLocationProvider(pageGraphRoot, dokkaContext, extension) { + val extension: String = ".html" +) : DokkaBaseLocationProvider(pageGraphRoot, dokkaContext) { protected open val PAGE_WITH_CHILDREN_SUFFIX = "index" protected open val pathsIndex: Map> = IdentityHashMap>().apply { diff --git a/plugins/base/src/main/kotlin/resolvers/shared/PackageList.kt b/plugins/base/src/main/kotlin/resolvers/shared/PackageList.kt index e4b8fb6f..089bda55 100644 --- a/plugins/base/src/main/kotlin/resolvers/shared/PackageList.kt +++ b/plugins/base/src/main/kotlin/resolvers/shared/PackageList.kt @@ -1,7 +1,6 @@ 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( @@ -15,9 +14,6 @@ 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 @@ -31,7 +27,6 @@ data class PackageList( return PackageList(format, packages.filter(String::isNotBlank).toSet(), locations, url) } - private fun splitParams(params: List) = params.asSequence() .map { it.removePrefix("${PackageListService.DOKKA_PARAM_PREFIX}.").split(":", limit = 2) } .groupBy({ (key, _) -> key }, { (_, value) -> value }) diff --git a/plugins/base/src/main/kotlin/templating/ImmediateHtmlCommandConsumer.kt b/plugins/base/src/main/kotlin/templating/ImmediateHtmlCommandConsumer.kt index a2a3b31e..2b721e8c 100644 --- a/plugins/base/src/main/kotlin/templating/ImmediateHtmlCommandConsumer.kt +++ b/plugins/base/src/main/kotlin/templating/ImmediateHtmlCommandConsumer.kt @@ -2,10 +2,9 @@ package org.jetbrains.dokka.base.templating import org.jetbrains.dokka.base.renderers.html.TemplateBlock import org.jetbrains.dokka.base.renderers.html.command.consumers.ImmediateResolutionTagConsumer -import org.jetbrains.dokka.plugability.DokkaContext interface ImmediateHtmlCommandConsumer { - fun canProcess(command:Command): Boolean + fun canProcess(command: Command): Boolean fun processCommand(command: Command, block: TemplateBlock, tagConsumer: ImmediateResolutionTagConsumer) diff --git a/plugins/gfm/.DS_Store b/plugins/gfm/.DS_Store new file mode 100644 index 00000000..343f9af6 Binary files /dev/null and b/plugins/gfm/.DS_Store differ diff --git a/plugins/gfm/gfm-all-module-page/build.gradle.kts b/plugins/gfm/gfm-all-module-page/build.gradle.kts deleted file mode 100644 index 85f83587..00000000 --- a/plugins/gfm/gfm-all-module-page/build.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -import org.jetbrains.registerDokkaArtifactPublication - -dependencies { - implementation(project(":plugins:base")) - implementation(project(":plugins:gfm")) - implementation(project(":plugins:all-module-page")) - - val coroutines_version: String by project - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version") -} - -registerDokkaArtifactPublication("dokkaGfmAllModulePage") { - artifactId = "gfm-all-module-page-plugin" -} \ No newline at end of file diff --git a/plugins/gfm/gfm-all-module-page/src/main/kotlin/org/jetbrains/dokka/gfm/allModulesPage/GfmAllModulesPagaPlugin.kt b/plugins/gfm/gfm-all-module-page/src/main/kotlin/org/jetbrains/dokka/gfm/allModulesPage/GfmAllModulesPagaPlugin.kt deleted file mode 100644 index eb5e7e45..00000000 --- a/plugins/gfm/gfm-all-module-page/src/main/kotlin/org/jetbrains/dokka/gfm/allModulesPage/GfmAllModulesPagaPlugin.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.jetbrains.dokka.gfm.allModulesPage - -import org.jetbrains.dokka.allModulesPage.AllModulesPagePlugin -import org.jetbrains.dokka.allModulesPage.templates.DefaultTemplateProcessor -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.gfm.GfmPlugin -import org.jetbrains.dokka.plugability.DokkaPlugin - -class GfmAllModulesPagePlugin : DokkaPlugin() { - - private val gfmPlugin by lazy { plugin() } - - private val dokkaBase by lazy { plugin() } - - private val allModulesPagePlugin by lazy { plugin() } - - val locationProvider by extending { - (dokkaBase.locationProviderFactory providing GfmMultimoduleLocationProvider::Factory - override listOf(allModulesPagePlugin.multimoduleLocationProvider, gfmPlugin.locationProvider)) - } - - val defaultTemplateProcessor by extending { - (allModulesPagePlugin.templateProcessingStrategy - providing ::GfmTemplateProcessingStrategy - override allModulesPagePlugin.directiveBasedHtmlTemplateProcessingStrategy - order { before(allModulesPagePlugin.fallbackProcessingStrategy) }) - } -} \ No newline at end of file diff --git a/plugins/gfm/gfm-all-module-page/src/main/kotlin/org/jetbrains/dokka/gfm/allModulesPage/GfmMultimoduleLocationProvider.kt b/plugins/gfm/gfm-all-module-page/src/main/kotlin/org/jetbrains/dokka/gfm/allModulesPage/GfmMultimoduleLocationProvider.kt deleted file mode 100644 index 9f2ee140..00000000 --- a/plugins/gfm/gfm-all-module-page/src/main/kotlin/org/jetbrains/dokka/gfm/allModulesPage/GfmMultimoduleLocationProvider.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.jetbrains.dokka.gfm.allModulesPage - -import org.jetbrains.dokka.allModulesPage.MultimoduleLocationProvider -import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory -import org.jetbrains.dokka.gfm.location.MarkdownLocationProvider -import org.jetbrains.dokka.pages.RootPageNode -import org.jetbrains.dokka.plugability.DokkaContext - -class GfmMultimoduleLocationProvider(root: RootPageNode, context: DokkaContext) : MultimoduleLocationProvider(root, context, ".md") { - - override val defaultLocationProvider = MarkdownLocationProvider(root, context) - - class Factory(private val context: DokkaContext): LocationProviderFactory { - override fun getLocationProvider(pageNode: RootPageNode) = - GfmMultimoduleLocationProvider(pageNode, context) - } -} \ No newline at end of file diff --git a/plugins/gfm/gfm-all-module-page/src/main/kotlin/org/jetbrains/dokka/gfm/allModulesPage/GfmTemplateProcessingStrategy.kt b/plugins/gfm/gfm-all-module-page/src/main/kotlin/org/jetbrains/dokka/gfm/allModulesPage/GfmTemplateProcessingStrategy.kt deleted file mode 100644 index 37226d1e..00000000 --- a/plugins/gfm/gfm-all-module-page/src/main/kotlin/org/jetbrains/dokka/gfm/allModulesPage/GfmTemplateProcessingStrategy.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.jetbrains.dokka.gfm.allModulesPage - -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import org.jetbrains.dokka.allModulesPage.templates.ExternalModuleLinkResolver -import org.jetbrains.dokka.allModulesPage.templates.TemplateProcessingStrategy -import org.jetbrains.dokka.base.templating.parseJson -import org.jetbrains.dokka.gfm.GfmCommand -import org.jetbrains.dokka.gfm.GfmCommand.Companion.templateCommandRegex -import org.jetbrains.dokka.gfm.ResolveLinkGfmCommand -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.plugability.DokkaContext -import java.io.File - -class GfmTemplateProcessingStrategy(val context: DokkaContext) : TemplateProcessingStrategy { - - private val externalModuleLinkResolver = ExternalModuleLinkResolver(context) - - override suspend fun process(input: File, output: File): Boolean = coroutineScope { - if (input.extension == "md") { - launch(IO) { - val reader = input.bufferedReader() - val writer = output.bufferedWriter() - do { - val line = reader.readLine() - if (line != null) { - writer.write(line.replace(templateCommandRegex) { - when (val command = parseJson(it.groupValues.last())) { - is ResolveLinkGfmCommand -> resolveLink(output, command.dri) - } - }) - writer.newLine() - } - } while (line != null) - reader.close() - writer.close() - } - true - } else false - } - - private fun resolveLink(fileContext: File, dri: DRI): String = - externalModuleLinkResolver.resolve(dri, fileContext) ?: "" -} \ No newline at end of file diff --git a/plugins/gfm/gfm-all-module-page/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/plugins/gfm/gfm-all-module-page/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin deleted file mode 100644 index 1a8acd51..00000000 --- a/plugins/gfm/gfm-all-module-page/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin +++ /dev/null @@ -1 +0,0 @@ -org.jetbrains.dokka.gfm.allModulesPage.GfmAllModulesPagePlugin \ No newline at end of file diff --git a/plugins/gfm/gfm-template-processing/build.gradle.kts b/plugins/gfm/gfm-template-processing/build.gradle.kts new file mode 100644 index 00000000..f95ef0e4 --- /dev/null +++ b/plugins/gfm/gfm-template-processing/build.gradle.kts @@ -0,0 +1,14 @@ +import org.jetbrains.registerDokkaArtifactPublication + +dependencies { + implementation(project(":plugins:base")) + implementation(project(":plugins:gfm")) + implementation(project(":plugins:all-modules-page")) + + val coroutines_version: String by project + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version") +} + +registerDokkaArtifactPublication("dokkaGfmTemplateProcessing") { + artifactId = "gfm-template-processing-plugin" +} \ No newline at end of file diff --git a/plugins/gfm/gfm-template-processing/src/main/kotlin/org/jetbrains/dokka/gfm/templateProcessing/GfmTemplateProcessingPlugin.kt b/plugins/gfm/gfm-template-processing/src/main/kotlin/org/jetbrains/dokka/gfm/templateProcessing/GfmTemplateProcessingPlugin.kt new file mode 100644 index 00000000..7df740a5 --- /dev/null +++ b/plugins/gfm/gfm-template-processing/src/main/kotlin/org/jetbrains/dokka/gfm/templateProcessing/GfmTemplateProcessingPlugin.kt @@ -0,0 +1,31 @@ +package org.jetbrains.dokka.gfm.templateProcessing + +import org.jetbrains.dokka.allModulesPage.AllModulesPagePlugin +import org.jetbrains.dokka.allModulesPage.MultimoduleLocationProvider +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.gfm.GfmPlugin +import org.jetbrains.dokka.gfm.location.MarkdownLocationProvider +import org.jetbrains.dokka.plugability.DokkaPlugin + +class GfmTemplateProcessingPlugin : DokkaPlugin() { + + private val allModulesPagePlugin by lazy { plugin() } + + private val gfmPlugin by lazy { plugin() } + + private val dokkaBase by lazy { plugin()} + + val gfmTemplateProcessingStrategy by extending { + (allModulesPagePlugin.templateProcessingStrategy + providing ::GfmTemplateProcessingStrategy + order { before(allModulesPagePlugin.fallbackProcessingStrategy) }) + } + + val gfmLocationProvider by extending { + dokkaBase.locationProviderFactory providing MultimoduleLocationProvider::Factory override listOf(gfmPlugin.locationProvider, allModulesPagePlugin.multimoduleLocationProvider) + } + + val gfmPartialLocationProvider by extending { + allModulesPagePlugin.partialLocationProviderFactory providing MarkdownLocationProvider::Factory override allModulesPagePlugin.baseLocationProviderFactory + } +} \ No newline at end of file diff --git a/plugins/gfm/gfm-template-processing/src/main/kotlin/org/jetbrains/dokka/gfm/templateProcessing/GfmTemplateProcessingStrategy.kt b/plugins/gfm/gfm-template-processing/src/main/kotlin/org/jetbrains/dokka/gfm/templateProcessing/GfmTemplateProcessingStrategy.kt new file mode 100644 index 00000000..b2ef4d06 --- /dev/null +++ b/plugins/gfm/gfm-template-processing/src/main/kotlin/org/jetbrains/dokka/gfm/templateProcessing/GfmTemplateProcessingStrategy.kt @@ -0,0 +1,51 @@ +package org.jetbrains.dokka.gfm.templateProcessing + +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import org.jetbrains.dokka.allModulesPage.AllModulesPagePlugin +import org.jetbrains.dokka.allModulesPage.templates.TemplateProcessingStrategy +import org.jetbrains.dokka.base.templating.parseJson +import org.jetbrains.dokka.gfm.GfmCommand +import org.jetbrains.dokka.gfm.GfmCommand.Companion.command +import org.jetbrains.dokka.gfm.GfmCommand.Companion.label +import org.jetbrains.dokka.gfm.GfmCommand.Companion.templateCommandRegex +import org.jetbrains.dokka.gfm.ResolveLinkGfmCommand +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle +import java.io.File + +class GfmTemplateProcessingStrategy(context: DokkaContext) : TemplateProcessingStrategy { + + private val externalModuleLinkResolver = context.plugin().querySingle { externalModuleLinkResolver } + + override suspend fun process(input: File, output: File): Boolean = coroutineScope { + if (input.extension == "md") { + launch(IO) { + input.bufferedReader().use { reader -> + output.bufferedWriter().use { writer -> + do { + val line = reader.readLine() + if (line != null) { + writer.write(line.replace(templateCommandRegex) { + when (val command = parseJson(it.command)) { + is ResolveLinkGfmCommand -> resolveLink(output, command.dri, it.label) + } + }) + writer.newLine() + } + } while (line != null) + } + } + } + true + } else false + } + + private fun resolveLink(fileContext: File, dri: DRI, label: String): String = + externalModuleLinkResolver.resolve(dri, fileContext)?.let { address -> + "[$label]($address)" + } ?: label +} \ No newline at end of file diff --git a/plugins/gfm/gfm-template-processing/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/plugins/gfm/gfm-template-processing/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin new file mode 100644 index 00000000..197823e7 --- /dev/null +++ b/plugins/gfm/gfm-template-processing/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin @@ -0,0 +1 @@ +org.jetbrains.dokka.gfm.templateProcessing.GfmTemplateProcessingPlugin \ No newline at end of file diff --git a/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/GfmPlugin.kt b/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/GfmPlugin.kt index 3f2eae4d..4ca639b2 100644 --- a/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/GfmPlugin.kt +++ b/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/GfmPlugin.kt @@ -5,7 +5,7 @@ import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.renderers.PackageListCreator import org.jetbrains.dokka.base.renderers.RootCreator import org.jetbrains.dokka.base.resolvers.shared.RecognizedLinkFormat -import org.jetbrains.dokka.gfm.location.MarkdownLocationProviderFactory +import org.jetbrains.dokka.gfm.location.MarkdownLocationProvider import org.jetbrains.dokka.gfm.renderer.CommonmarkRenderer import org.jetbrains.dokka.plugability.DokkaPlugin import org.jetbrains.dokka.transformers.pages.PageTransformer @@ -21,7 +21,7 @@ class GfmPlugin : DokkaPlugin() { } val locationProvider by extending { - dokkaBase.locationProviderFactory providing ::MarkdownLocationProviderFactory override dokkaBase.locationProvider + dokkaBase.locationProviderFactory providing MarkdownLocationProvider::Factory override dokkaBase.locationProvider } val rootCreator by extending { diff --git a/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/gfmTemplating.kt b/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/gfmTemplating.kt index 1473ceee..4fa6a36e 100644 --- a/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/gfmTemplating.kt +++ b/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/gfmTemplating.kt @@ -9,11 +9,20 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.CLASS sealed class GfmCommand { companion object { private const val delimiter = "\u1680" - fun templateCommand(command: GfmCommand): String = "$delimiter GfmCommand ${toJsonString(command)}$delimiter" - val templateCommandRegex: Regex = Regex("$delimiter GfmCommand ([^$delimiter ]*)$delimiter") + val templateCommandRegex: Regex = + Regex("(.+?)(?=") + val MatchResult.command + get() = groupValues[1] + val MatchResult.label + get() = groupValues[2] + fun Appendable.templateCommand(command: GfmCommand, content: Appendable.() -> Unit): Unit { + append("") + content() + append("") + } } } -class ResolveLinkGfmCommand(val dri: DRI): GfmCommand() +class ResolveLinkGfmCommand(val dri: DRI) : GfmCommand() diff --git a/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/location/MarkdownLocationProvider.kt b/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/location/MarkdownLocationProvider.kt index 6f96dbd5..bd789464 100644 --- a/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/location/MarkdownLocationProvider.kt +++ b/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/location/MarkdownLocationProvider.kt @@ -5,12 +5,14 @@ import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory import org.jetbrains.dokka.pages.RootPageNode import org.jetbrains.dokka.plugability.DokkaContext -class MarkdownLocationProviderFactory(val context: DokkaContext) : LocationProviderFactory { - override fun getLocationProvider(pageNode: RootPageNode) = MarkdownLocationProvider(pageNode, context) -} - class MarkdownLocationProvider( pageGraphRoot: RootPageNode, dokkaContext: DokkaContext -) : DokkaLocationProvider(pageGraphRoot, dokkaContext, ".md") +) : DokkaLocationProvider(pageGraphRoot, dokkaContext, ".md") { + + class Factory(private val context: DokkaContext) : LocationProviderFactory { + override fun getLocationProvider(pageNode: RootPageNode) = + MarkdownLocationProvider(pageNode, context) + } +} diff --git a/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/renderer/CommonmarkRenderer.kt b/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/renderer/CommonmarkRenderer.kt index 25b24f0c..9fb92272 100644 --- a/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/renderer/CommonmarkRenderer.kt +++ b/plugins/gfm/src/main/kotlin/org/jetbrains/dokka/gfm/renderer/CommonmarkRenderer.kt @@ -18,6 +18,8 @@ open class CommonmarkRenderer( override val preprocessors = context.plugin().query { gfmPreprocessors } + private val isPartial = context.configuration.delayTemplateSubstitution + override fun StringBuilder.wrapGroup( node: ContentGroup, pageContext: ContentPage, @@ -87,10 +89,15 @@ open class CommonmarkRenderer( pageContext: ContentPage, sourceSetRestriction: Set? ) { - val address = locationProvider.resolve(node.address, node.sourceSets, pageContext) - buildLink(address ?: templateCommand(ResolveLinkGfmCommand(node.address))) { - buildText(node.children, pageContext, sourceSetRestriction) - } + locationProvider.resolve(node.address, node.sourceSets, pageContext)?.let { + buildLink(it) { + buildText(node.children, pageContext, sourceSetRestriction) + } + } ?: if (isPartial) { + templateCommand(ResolveLinkGfmCommand(node.address)) { + buildText(node.children, pageContext, sourceSetRestriction) + } + } else Unit } override fun StringBuilder.buildNewLine() { diff --git a/plugins/gfm/src/test/kotlin/renderers/gfm/GfmRenderingOnlyTestBase.kt b/plugins/gfm/src/test/kotlin/renderers/gfm/GfmRenderingOnlyTestBase.kt index 165aef98..1fa78423 100644 --- a/plugins/gfm/src/test/kotlin/renderers/gfm/GfmRenderingOnlyTestBase.kt +++ b/plugins/gfm/src/test/kotlin/renderers/gfm/GfmRenderingOnlyTestBase.kt @@ -1,13 +1,13 @@ package renderers.gfm import org.jetbrains.dokka.DokkaConfigurationImpl -import org.jetbrains.dokka.gfm.GfmPlugin -import org.jetbrains.dokka.gfm.location.MarkdownLocationProviderFactory -import org.jetbrains.dokka.testApi.context.MockContext import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.renderers.RootCreator import org.jetbrains.dokka.base.resolvers.external.DefaultExternalLocationProviderFactory import org.jetbrains.dokka.base.resolvers.external.javadoc.JavadocExternalLocationProviderFactory +import org.jetbrains.dokka.gfm.GfmPlugin +import org.jetbrains.dokka.gfm.location.MarkdownLocationProvider +import org.jetbrains.dokka.testApi.context.MockContext import renderers.RenderingOnlyTestBase import utils.TestOutputWriter @@ -15,11 +15,11 @@ abstract class GfmRenderingOnlyTestBase : RenderingOnlyTestBase() { val files = TestOutputWriter() override val context = MockContext( - DokkaBase().outputWriter to { _ -> files }, - DokkaBase().locationProviderFactory to ::MarkdownLocationProviderFactory, - DokkaBase().externalLocationProviderFactory to { ::JavadocExternalLocationProviderFactory }, - DokkaBase().externalLocationProviderFactory to { ::DefaultExternalLocationProviderFactory }, - GfmPlugin().gfmPreprocessors to { _ -> RootCreator }, + DokkaBase().outputWriter to { files }, + DokkaBase().locationProviderFactory to MarkdownLocationProvider::Factory, + DokkaBase().externalLocationProviderFactory to ::JavadocExternalLocationProviderFactory, + DokkaBase().externalLocationProviderFactory to ::DefaultExternalLocationProviderFactory, + GfmPlugin().gfmPreprocessors to { RootCreator }, testConfiguration = DokkaConfigurationImpl(moduleName = "root") ) diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt index b9828e9b..440bfc2f 100644 --- a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt @@ -13,7 +13,7 @@ import org.jetbrains.dokka.plugability.DokkaContext import java.util.* class JavadocLocationProvider(pageRoot: RootPageNode, dokkaContext: DokkaContext) : - DefaultLocationProvider(pageRoot, dokkaContext, ".html") { + DefaultLocationProvider(pageRoot, dokkaContext) { private val pathIndex = IdentityHashMap>().apply { fun registerPath(page: PageNode, prefix: List = emptyList()) { -- cgit