aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/utilities
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main/kotlin/utilities')
-rw-r--r--core/src/main/kotlin/utilities/DokkaLogging.kt31
-rw-r--r--core/src/main/kotlin/utilities/Html.kt12
-rw-r--r--core/src/main/kotlin/utilities/Path.kt5
-rw-r--r--core/src/main/kotlin/utilities/ServiceLocator.kt97
-rw-r--r--core/src/main/kotlin/utilities/Uri.kt40
-rw-r--r--core/src/main/kotlin/utilities/nodeDebug.kt50
6 files changed, 235 insertions, 0 deletions
diff --git a/core/src/main/kotlin/utilities/DokkaLogging.kt b/core/src/main/kotlin/utilities/DokkaLogging.kt
new file mode 100644
index 00000000..6eb7867b
--- /dev/null
+++ b/core/src/main/kotlin/utilities/DokkaLogging.kt
@@ -0,0 +1,31 @@
+package org.jetbrains.dokka.utilities
+
+interface DokkaLogger {
+ fun debug(message: String)
+ fun info(message: String)
+ fun progress(message: String)
+ fun warn(message: String)
+ fun error(message: String)
+}
+
+object DokkaConsoleLogger : DokkaLogger {
+ var warningCount: Int = 0
+
+ override fun debug(message: String)= println(message)
+
+ override fun progress(message: String) = println("PROGRESS: $message")
+
+ override fun info(message: String) = println(message)
+
+ override fun warn(message: String) = println("WARN: $message").also { warningCount++ }
+
+ override fun error(message: String) = println("ERROR: $message")
+
+ fun report() {
+ if (warningCount > 0) {
+ println("generation completed with $warningCount warnings")
+ } else {
+ println("generation completed successfully")
+ }
+ }
+}
diff --git a/core/src/main/kotlin/utilities/Html.kt b/core/src/main/kotlin/utilities/Html.kt
new file mode 100644
index 00000000..9dc032f6
--- /dev/null
+++ b/core/src/main/kotlin/utilities/Html.kt
@@ -0,0 +1,12 @@
+package org.jetbrains.dokka.utilities
+
+import java.net.URLEncoder
+
+
+/**
+ * Replaces symbols reserved in HTML with their respective entities.
+ * Replaces & with &amp;, < with &lt; and > with &gt;
+ */
+fun String.htmlEscape(): String = replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
+
+fun String.urlEncoded(): String = URLEncoder.encode(this, "UTF-8") \ No newline at end of file
diff --git a/core/src/main/kotlin/utilities/Path.kt b/core/src/main/kotlin/utilities/Path.kt
new file mode 100644
index 00000000..00e2aa60
--- /dev/null
+++ b/core/src/main/kotlin/utilities/Path.kt
@@ -0,0 +1,5 @@
+package org.jetbrains.dokka.utilities
+
+import java.io.File
+
+fun File.appendExtension(extension: String) = if (extension.isEmpty()) this else File(path + "." + extension)
diff --git a/core/src/main/kotlin/utilities/ServiceLocator.kt b/core/src/main/kotlin/utilities/ServiceLocator.kt
new file mode 100644
index 00000000..00c9ae9f
--- /dev/null
+++ b/core/src/main/kotlin/utilities/ServiceLocator.kt
@@ -0,0 +1,97 @@
+package org.jetbrains.dokka.utilities
+
+import java.io.File
+import java.net.URISyntaxException
+import java.net.URL
+import java.util.*
+import java.util.jar.JarFile
+import java.util.zip.ZipEntry
+
+data class ServiceDescriptor(val name: String, val category: String, val description: String?, val className: String)
+
+class ServiceLookupException(message: String) : Exception(message)
+
+object ServiceLocator {
+ fun <T : Any> lookup(clazz: Class<T>, category: String, implementationName: String): T {
+ val descriptor = lookupDescriptor(category, implementationName)
+ return lookup(clazz, descriptor)
+ }
+
+ fun <T : Any> lookup(
+ clazz: Class<T>,
+ descriptor: ServiceDescriptor
+ ): T {
+ val loadedClass = javaClass.classLoader.loadClass(descriptor.className)
+ val constructor = loadedClass.constructors.firstOrNull { it.parameterTypes.isEmpty() } ?: throw ServiceLookupException("Class ${descriptor.className} has no corresponding constructor")
+
+ val implementationRawType: Any =
+ if (constructor.parameterTypes.isEmpty()) constructor.newInstance() else constructor.newInstance(constructor)
+
+ if (!clazz.isInstance(implementationRawType)) {
+ throw ServiceLookupException("Class ${descriptor.className} is not a subtype of ${clazz.name}")
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ return implementationRawType as T
+ }
+
+ private fun lookupDescriptor(category: String, implementationName: String): ServiceDescriptor {
+ val properties = javaClass.classLoader.getResourceAsStream("dokka/$category/$implementationName.properties")?.use { stream ->
+ Properties().let { properties ->
+ properties.load(stream)
+ properties
+ }
+ } ?: throw ServiceLookupException("No implementation with name $implementationName found in category $category")
+
+ val className = properties["class"]?.toString() ?: throw ServiceLookupException("Implementation $implementationName has no class configured")
+
+ return ServiceDescriptor(implementationName, category, properties["description"]?.toString(), className)
+ }
+
+ fun URL.toFile(): File {
+ assert(protocol == "file")
+
+ return try {
+ File(toURI())
+ } catch (e: URISyntaxException) { //Try to handle broken URLs, with unescaped spaces
+ File(path)
+ }
+ }
+
+ fun allServices(category: String): List<ServiceDescriptor> {
+ val entries = this.javaClass.classLoader.getResources("dokka/$category")?.toList() ?: emptyList()
+
+ return entries.flatMap {
+ when (it.protocol) {
+ "file" -> it.toFile().listFiles()?.filter { it.extension == "properties" }?.map { lookupDescriptor(category, it.nameWithoutExtension) } ?: emptyList()
+ "jar" -> {
+ val file = JarFile(URL(it.file.substringBefore("!")).toFile())
+ try {
+ val jarPath = it.file.substringAfterLast("!").removePrefix("/").removeSuffix("/")
+ file.entries()
+ .asSequence()
+ .filter { entry -> !entry.isDirectory && entry.path == jarPath && entry.extension == "properties" }
+ .map { entry ->
+ lookupDescriptor(category, entry.fileName.substringBeforeLast("."))
+ }.toList()
+ } finally {
+ file.close()
+ }
+ }
+ else -> emptyList<ServiceDescriptor>()
+ }
+ }
+ }
+}
+
+inline fun <reified T : Any> ServiceLocator.lookup(category: String, implementationName: String): T = lookup(T::class.java, category, implementationName)
+inline fun <reified T : Any> ServiceLocator.lookup(desc: ServiceDescriptor): T = lookup(T::class.java, desc)
+
+private val ZipEntry.fileName: String
+ get() = name.substringAfterLast("/", name)
+
+private val ZipEntry.path: String
+ get() = name.substringBeforeLast("/", "").removePrefix("/")
+
+private val ZipEntry.extension: String?
+ get() = fileName.let { fn -> if ("." in fn) fn.substringAfterLast(".") else null }
diff --git a/core/src/main/kotlin/utilities/Uri.kt b/core/src/main/kotlin/utilities/Uri.kt
new file mode 100644
index 00000000..089b3cff
--- /dev/null
+++ b/core/src/main/kotlin/utilities/Uri.kt
@@ -0,0 +1,40 @@
+package org.jetbrains.dokka.utilities
+
+import java.net.URI
+
+
+fun URI.relativeTo(uri: URI): URI {
+ // Normalize paths to remove . and .. segments
+ val base = uri.normalize()
+ val child = this.normalize()
+
+ fun StringBuilder.appendRelativePath() {
+ // Split paths into segments
+ var bParts = base.path.split('/').dropLastWhile { it.isEmpty() }
+ val cParts = child.path.split('/').dropLastWhile { it.isEmpty() }
+
+ // Discard trailing segment of base path
+ if (bParts.isNotEmpty() && !base.path.endsWith("/")) {
+ bParts = bParts.dropLast(1)
+ }
+
+ // Compute common prefix
+ val commonPartsSize = bParts.zip(cParts).takeWhile { (basePart, childPart) -> basePart == childPart }.count()
+ bParts.drop(commonPartsSize).joinTo(this, separator = "") { "../" }
+ cParts.drop(commonPartsSize).joinTo(this, separator = "/")
+ }
+
+ return URI.create(buildString {
+ if (base.path != child.path) {
+ appendRelativePath()
+ }
+ child.rawQuery?.let {
+ append("?")
+ append(it)
+ }
+ child.rawFragment?.let {
+ append("#")
+ append(it)
+ }
+ })
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/utilities/nodeDebug.kt b/core/src/main/kotlin/utilities/nodeDebug.kt
new file mode 100644
index 00000000..3290202e
--- /dev/null
+++ b/core/src/main/kotlin/utilities/nodeDebug.kt
@@ -0,0 +1,50 @@
+package org.jetbrains.dokka.utilities
+
+import org.jetbrains.dokka.model.DocumentationNode
+import org.jetbrains.dokka.pages.*
+
+const val DOWN = '\u2503'
+const val BRANCH = '\u2523'
+const val LAST = '\u2517'
+
+fun DocumentationNode.pretty(prefix: String = "", isLast: Boolean = true): String {
+ val nextPrefix = prefix + (if (isLast) ' ' else DOWN) + ' '
+
+ return prefix + (if (isLast) LAST else BRANCH) + this.toString() +
+ children.dropLast(1)
+ .map { it.pretty(nextPrefix, false) }
+ .plus(children.lastOrNull()?.pretty(nextPrefix))
+ .filterNotNull()
+ .takeIf { it.isNotEmpty() }
+ ?.joinToString(prefix = "\n", separator = "")
+ .orEmpty() + if (children.isEmpty()) "\n" else ""
+}
+
+//fun Any.genericPretty(prefix: String = "", isLast: Boolean = true): String {
+// val nextPrefix = prefix + (if (isLast) ' ' else DOWN) + ' '
+//
+// return prefix + (if (isLast) LAST else BRANCH) + this.stringify() +
+// allChildren().dropLast(1)
+// .map { it.genericPretty(nextPrefix, false) }
+// .plus(allChildren().lastOrNull()?.genericPretty(nextPrefix))
+// .filterNotNull()
+// .takeIf { it.isNotEmpty() }
+// ?.joinToString(prefix = "\n", separator = "")
+// .orEmpty() + if (allChildren().isEmpty()) "\n" else ""
+//}
+private fun Any.stringify() = when(this) {
+ is ContentNode -> toString() + this.dci
+ is PageNode -> this.name + this::class.simpleName
+ else -> toString()
+}
+//private fun Any.allChildren() = when(this){
+// is PageNode -> children + content
+// is ContentBlock -> this.children
+// is ContentHeader -> this.items
+// is ContentStyle -> this.items
+// is ContentSymbol -> this.parts
+// is ContentComment -> this.parts
+// is ContentGroup -> this.children
+// is ContentList -> this.items
+// else -> emptyList()
+//}