diff options
author | Błażej Kardyś <bkardys@virtuslab.com> | 2020-11-20 17:23:10 +0100 |
---|---|---|
committer | Błażej Kardyś <bkardys@virtuslab.com> | 2020-11-27 03:15:02 +0100 |
commit | 3cb4702a68139788de6e1f7b087ced345f2b71ba (patch) | |
tree | a383471c9915ae4aaff078b4f3b81bb99a4fde35 /plugins/all-modules-page/src/main | |
parent | 076a5f421c5e4621539efd814be612f43fef33f5 (diff) | |
download | dokka-3cb4702a68139788de6e1f7b087ced345f2b71ba.tar.gz dokka-3cb4702a68139788de6e1f7b087ced345f2b71ba.tar.bz2 dokka-3cb4702a68139788de6e1f7b087ced345f2b71ba.zip |
Changing how multimodule location provider works and improving gfm link substitution
Diffstat (limited to 'plugins/all-modules-page/src/main')
11 files changed, 562 insertions, 0 deletions
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<AllModulesPagePlugin>() } + + 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<TemplateProcessor>() + val templateProcessingStrategy by extensionPoint<TemplateProcessingStrategy>() + val partialLocationProviderFactory by extensionPoint<LocationProviderFactory>() + val allModulesPageCreator by extensionPoint<PageCreator>() + val allModulesPageTransformer by extensionPoint<PageTransformer>() + val externalModuleLinkResolver by extensionPoint<ExternalModuleLinkResolver>() + + val substitutor by extensionPoint<Substitutor>() + + val allModulesPageCreators by extending { + allModulesPageCreator providing ::MultimodulePageCreator + } + + val multimoduleLocationProvider by extending { + (plugin<DokkaBase>().locationProviderFactory + providing MultimoduleLocationProvider::Factory + override plugin<DokkaBase>().locationProvider) + } + + val baseLocationProviderFactory by extending { + partialLocationProviderFactory providing ::DokkaLocationProviderFactory + } + + val allModulesPageGeneration by extending { + (CoreExtensions.generation + providing ::AllModulesPageGeneration + override plugin<DokkaBase>().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<AllModulesPagePlugin>().querySingle { partialLocationProviderFactory }.getLocationProvider(root) + private val externalModuleLinkResolver = dokkaContext.plugin<AllModulesPagePlugin>().querySingle { externalModuleLinkResolver } + + override fun resolve(dri: DRI, sourceSets: Set<DisplaySourceSet>, 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<PageNode> = 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<DokkaBase>().querySingle { commentsToContentConverter } } + private val signatureProvider by lazy { context.plugin<DokkaBase>().querySingle { signatureProvider } } + + override fun invoke(): RootPageNode { + val modules = context.configuration.modules + val sourceSetData = emptySet<DokkaSourceSet>() + 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<String, Element>() + + private val substitutors = context.plugin<AllModulesPagePlugin>().query { substitutor } + private val externalModuleLinkResolver = context.plugin<AllModulesPagePlugin>().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<Command>(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<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) { + 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<DokkaBase>().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<ExternalDocumentation> { + 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<SubstitutionCommand>, 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<SubstitutionCommand>, 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<TemplateProcessingStrategy> = context.plugin<AllModulesPagePlugin>().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<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/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 |