diff options
Diffstat (limited to 'core/src/main/kotlin/utilities')
-rw-r--r-- | core/src/main/kotlin/utilities/DokkaLogging.kt | 31 | ||||
-rw-r--r-- | core/src/main/kotlin/utilities/Html.kt | 12 | ||||
-rw-r--r-- | core/src/main/kotlin/utilities/Path.kt | 5 | ||||
-rw-r--r-- | core/src/main/kotlin/utilities/ServiceLocator.kt | 97 | ||||
-rw-r--r-- | core/src/main/kotlin/utilities/Uri.kt | 40 | ||||
-rw-r--r-- | core/src/main/kotlin/utilities/nodeDebug.kt | 50 |
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 &, < with < and > with > + */ +fun String.htmlEscape(): String = replace("&", "&").replace("<", "<").replace(">", ">") + +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() +//} |