diff options
Diffstat (limited to 'plugins/base/src/main/kotlin/resolvers/DefaultLocationProvider.kt')
-rw-r--r-- | plugins/base/src/main/kotlin/resolvers/DefaultLocationProvider.kt | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/plugins/base/src/main/kotlin/resolvers/DefaultLocationProvider.kt b/plugins/base/src/main/kotlin/resolvers/DefaultLocationProvider.kt new file mode 100644 index 00000000..2238b0c3 --- /dev/null +++ b/plugins/base/src/main/kotlin/resolvers/DefaultLocationProvider.kt @@ -0,0 +1,116 @@ +package org.jetbrains.dokka.base.resolvers + +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.utilities.htmlEscape +import java.util.* + +private const val PAGE_WITH_CHILDREN_SUFFIX = "index" + +open class DefaultLocationProvider( + protected val pageGraphRoot: RootPageNode, + protected val dokkaContext: DokkaContext +) : LocationProvider { + protected val extension = ".html" + + protected val pagesIndex: Map<DRI, ContentPage> = pageGraphRoot.asSequence().filterIsInstance<ContentPage>() + .map { it.dri.map { dri -> dri to it } }.flatten() + .groupingBy { it.first } + .aggregate { dri, _, (_, page), first -> + if (first) page else throw AssertionError("Multiple pages associated with dri: $dri") + } + + protected val pathsIndex: Map<PageNode, List<String>> = IdentityHashMap<PageNode, List<String>>().apply { + fun registerPath(page: PageNode, prefix: List<String>) { + val newPrefix = prefix + page.pathName + put(page, newPrefix) + page.children.forEach { registerPath(it, newPrefix) } + } + put(pageGraphRoot, emptyList()) + pageGraphRoot.children.forEach { registerPath(it, emptyList()) } + } + + override fun resolve(node: PageNode, context: PageNode?, skipExtension: Boolean): String = + pathTo(node, context) + if (!skipExtension) extension else "" + + override fun resolve(dri: DRI, platforms: List<PlatformData>, context: PageNode?): String = + pagesIndex[dri]?.let { resolve(it, context) } ?: + // Not found in PageGraph, that means it's an external link + ExternalLocationProvider.getLocation(dri, + this.dokkaContext.configuration.passesConfigurations + .filter { passConfig -> + platforms.toSet() + .contains(PlatformData(passConfig.moduleName, passConfig.analysisPlatform, passConfig.targets)) + } // TODO: change targets to something better? + .flatMap { it.externalDocumentationLinks }.distinct() + ) + + override fun resolveRoot(node: PageNode): String = + pathTo(pageGraphRoot, node).removeSuffix(PAGE_WITH_CHILDREN_SUFFIX) + + override fun ancestors(node: PageNode): List<PageNode> = + generateSequence(node) { it.parent() }.toList() + + protected open fun pathTo(node: PageNode, context: PageNode?): String { + fun pathFor(page: PageNode) = pathsIndex[page] ?: throw AssertionError( + "${page::class.simpleName}(${page.name}) does not belong to current page graph so it is impossible to compute its path" + ) + + val contextNode = + if (context?.children?.isEmpty() == true && context.parent() != null) context.parent() else context + val nodePath = pathFor(node) + val contextPath = contextNode?.let { pathFor(it) }.orEmpty() + + val commonPathElements = nodePath.asSequence().zip(contextPath.asSequence()) + .takeWhile { (a, b) -> a == b }.count() + + return (List(contextPath.size - commonPathElements) { ".." } + nodePath.drop(commonPathElements) + + if (node.children.isNotEmpty()) listOf(PAGE_WITH_CHILDREN_SUFFIX) else emptyList()).joinToString("/") + } + + private fun PageNode.parent() = pageGraphRoot.parentMap[this] +} + +fun DRI.toJavadocLocation(jdkVersion: Int): String { // TODO: classes without packages? + val packageLink = packageName?.replace(".", "/") + if (classNames == null) { + return "$packageLink/package-summary.html".htmlEscape() + } + val classLink = if (packageLink == null) "$classNames.html" else "$packageLink/$classNames.html" + val callableChecked = callable ?: return classLink.htmlEscape() + + val callableLink = "$classLink#${callableChecked.name}" + when { + jdkVersion < 8 -> "(${callableChecked.params.joinToString(", ")})" + jdkVersion < 10 -> "-${callableChecked.params.joinToString("-")}-" + else -> "(${callableChecked.params.joinToString(",")})" + } + + return callableLink.htmlEscape() +} + +fun DRI.toDokkaLocation(extension: String): String { // TODO: classes without packages? + val classNamesChecked = classNames ?: return "$packageName/index$extension" + + val classLink = if (packageName == null) { + "" + } else { + "$packageName/" + } + classNamesChecked.split('.').joinToString("/", transform = ::identifierToFilename) + + val callableChecked = callable ?: return "$classLink/index$extension" + + return "$classLink/${identifierToFilename(callableChecked.name)}$extension" +} + +private val reservedFilenames = setOf("index", "con", "aux", "lst", "prn", "nul", "eof", "inp", "out") + +private fun identifierToFilename(name: String): String { + if (name.isEmpty()) return "--root--" + val escaped = name.replace('<', '-').replace('>', '-') + val lowercase = escaped.replace("[A-Z]".toRegex()) { matchResult -> "-" + matchResult.value.toLowerCase() } + return if (lowercase in reservedFilenames) "--$lowercase--" else lowercase +} + +private val PageNode.pathName: String + get() = if (this is PackagePageNode) name else identifierToFilename(name) |