Tiem Song | e1dd512 | 2019-07-03 14:16:39 -0700 | [diff] [blame^] | 1 | package org.jetbrains.dokka.Formats |
| 2 | |
| 3 | import com.google.inject.Inject |
| 4 | import com.google.inject.name.Named |
| 5 | import org.jetbrains.dokka.* |
| 6 | import org.jetbrains.dokka.Formats.JavaLayoutHtmlFormatOutputBuilder.Page |
| 7 | import org.jetbrains.dokka.NodeKind.Companion.classLike |
| 8 | import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult |
| 9 | import java.io.BufferedWriter |
| 10 | import java.io.File |
| 11 | import java.net.URI |
| 12 | |
| 13 | class JavaLayoutHtmlFormatGenerator @Inject constructor( |
| 14 | @Named("outputDir") val root: File, |
| 15 | val packageListService: PackageListService, |
| 16 | val outputBuilderFactoryService: JavaLayoutHtmlFormatOutputBuilderFactory, |
| 17 | private val options: DocumentationOptions, |
| 18 | val logger: DokkaLogger, |
| 19 | @Named("outlineRoot") val outlineRoot: String |
| 20 | ) : Generator, JavaLayoutHtmlUriProvider { |
| 21 | |
| 22 | @set:Inject(optional = true) |
| 23 | var outlineFactoryService: JavaLayoutHtmlFormatOutlineFactoryService? = null |
| 24 | |
| 25 | fun createOutputBuilderForNode(node: DocumentationNode, output: Appendable) = outputBuilderFactoryService.createOutputBuilder(output, node) |
| 26 | |
| 27 | fun DocumentationNode.getOwnerOrReport() = owner ?: run { |
| 28 | error("Owner not found for $this") |
| 29 | } |
| 30 | |
| 31 | override fun tryGetContainerUri(node: DocumentationNode): URI? { |
| 32 | return when (node.kind) { |
| 33 | NodeKind.Module -> URI("/").resolve(node.name + "/") |
| 34 | NodeKind.Package -> tryGetContainerUri(node.getOwnerOrReport())?.resolve(node.name.replace('.', '/') + '/') |
| 35 | in NodeKind.classLike -> tryGetContainerUri(node.getOwnerOrReport())?.resolve("${node.classNodeNameWithOuterClass()}.html") |
| 36 | else -> null |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | override fun tryGetMainUri(node: DocumentationNode): URI? { |
| 41 | return when (node.kind) { |
| 42 | NodeKind.Package -> tryGetContainerUri(node)?.resolve("package-summary.html") |
| 43 | in NodeKind.classLike -> tryGetContainerUri(node)?.resolve("#") |
| 44 | in NodeKind.memberLike -> { |
| 45 | val owner = if (node.owner?.kind != NodeKind.ExternalClass) node.owner else node.owner?.owner |
| 46 | if (owner!!.kind in classLike && |
| 47 | (node.kind == NodeKind.CompanionObjectProperty || node.kind == NodeKind.CompanionObjectFunction) && |
| 48 | owner.companion != null |
| 49 | ) { |
| 50 | val signature = node.detail(NodeKind.Signature) |
| 51 | val originalFunction = owner.companion!!.members.first { it.detailOrNull(NodeKind.Signature)?.name == signature.name } |
| 52 | tryGetMainUri(owner.companion!!)?.resolveInPage(originalFunction) |
| 53 | } else { |
| 54 | tryGetMainUri(owner)?.resolveInPage(node) |
| 55 | } |
| 56 | } |
| 57 | NodeKind.TypeParameter, NodeKind.Parameter -> node.path.asReversed().drop(1).firstNotNullResult(this::tryGetMainUri)?.resolveInPage(node) |
| 58 | NodeKind.AllTypes -> outlineRootUri(node).resolve ("classes.html") |
| 59 | else -> null |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | override fun tryGetOutlineRootUri(node: DocumentationNode): URI? { |
| 64 | return when(node.kind) { |
| 65 | NodeKind.AllTypes -> tryGetContainerUri(node.getOwnerOrReport()) |
| 66 | else -> tryGetContainerUri(node) |
| 67 | }?.resolve(outlineRoot) |
| 68 | } |
| 69 | |
| 70 | fun URI.resolveInPage(node: DocumentationNode): URI = resolve("#${node.signatureForAnchor(logger).anchorEncoded()}") |
| 71 | |
| 72 | fun buildClass(node: DocumentationNode, parentDir: File) { |
| 73 | val fileForClass = parentDir.resolve(node.classNodeNameWithOuterClass() + ".html") |
| 74 | fileForClass.bufferedWriter().use { |
| 75 | createOutputBuilderForNode(node, it).generatePage(Page.ClassPage(node)) |
| 76 | } |
| 77 | for (memberClass in node.members.filter { it.kind in NodeKind.classLike }) { |
| 78 | buildClass(memberClass, parentDir) |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | fun buildPackage(node: DocumentationNode, parentDir: File) { |
| 83 | assert(node.kind == NodeKind.Package) |
| 84 | val members = node.members |
| 85 | val directoryForPackage = parentDir.resolve(node.name.replace('.', File.separatorChar)) |
| 86 | directoryForPackage.mkdirsOrFail() |
| 87 | |
| 88 | directoryForPackage.resolve("package-summary.html").bufferedWriter().use { |
| 89 | createOutputBuilderForNode(node, it).generatePage(Page.PackagePage(node)) |
| 90 | } |
| 91 | |
| 92 | members.filter { it.kind in NodeKind.classLike }.forEach { |
| 93 | buildClass(it, directoryForPackage) |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | fun buildClassIndex(node: DocumentationNode, parentDir: File) { |
| 98 | val file = parentDir.resolve("classes.html") |
| 99 | file.bufferedWriter().use { |
| 100 | createOutputBuilderForNode(node, it).generatePage(Page.ClassIndex(node)) |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | fun buildPackageIndex(module: DocumentationNode, nodes: List<DocumentationNode>, parentDir: File) { |
| 105 | val file = parentDir.resolve("packages.html") |
| 106 | file.bufferedWriter().use { |
| 107 | val uri = outlineRootUri(module).resolve("packages.html") |
| 108 | outputBuilderFactoryService.createOutputBuilder(it, uri) |
| 109 | .generatePage(Page.PackageIndex(nodes)) |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | override fun buildPages(nodes: Iterable<DocumentationNode>) { |
| 114 | val module = nodes.single() |
| 115 | |
| 116 | val moduleRoot = root.resolve(module.name) |
| 117 | val packages = module.members.filter { it.kind == NodeKind.Package } |
| 118 | packages.forEach { buildPackage(it, moduleRoot) } |
| 119 | val outlineRootFile = moduleRoot.resolve(outlineRoot) |
| 120 | if (options.generateClassIndexPage) { |
| 121 | buildClassIndex(module.members.single { it.kind == NodeKind.AllTypes }, outlineRootFile) |
| 122 | } |
| 123 | |
| 124 | if (options.generatePackageIndexPage) { |
| 125 | buildPackageIndex(module, packages, outlineRootFile) |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | override fun buildOutlines(nodes: Iterable<DocumentationNode>) { |
| 130 | val uriToWriter = mutableMapOf<URI, BufferedWriter>() |
| 131 | |
| 132 | fun provideOutput(uri: URI): BufferedWriter { |
| 133 | val normalized = uri.normalize() |
| 134 | uriToWriter[normalized]?.let { return it } |
| 135 | val file = root.resolve(normalized.path.removePrefix("/")) |
| 136 | val writer = file.bufferedWriter() |
| 137 | uriToWriter[normalized] = writer |
| 138 | return writer |
| 139 | } |
| 140 | |
| 141 | outlineFactoryService?.generateOutlines(::provideOutput, nodes) |
| 142 | |
| 143 | uriToWriter.values.forEach { it.close() } |
| 144 | } |
| 145 | |
| 146 | override fun buildSupportFiles() {} |
| 147 | |
| 148 | override fun buildPackageList(nodes: Iterable<DocumentationNode>) { |
| 149 | nodes.filter { it.kind == NodeKind.Module }.forEach { module -> |
| 150 | val moduleRoot = root.resolve(module.name) |
| 151 | val packageListFile = moduleRoot.resolve("package-list") |
| 152 | packageListFile.writeText(packageListService.formatPackageList(module as DocumentationModule)) |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | interface JavaLayoutHtmlFormatOutputBuilderFactory { |
| 158 | fun createOutputBuilder(output: Appendable, uri: URI): JavaLayoutHtmlFormatOutputBuilder |
| 159 | fun createOutputBuilder(output: Appendable, node: DocumentationNode): JavaLayoutHtmlFormatOutputBuilder |
| 160 | } |