blob: 67e6828540b2fec292b4f364625b7d86cb4a2769 [file] [log] [blame]
Simon Ogorodnik1b722f72017-11-30 20:34:25 +03001package org.jetbrains.dokka.Formats
Simon Ogorodnikf3db3f32017-11-30 19:04:11 +03002
Simon Ogorodnik7acc52b2017-12-02 00:50:08 +03003import com.google.inject.Binder
Simon Ogorodnik1b722f72017-11-30 20:34:25 +03004import com.google.inject.Inject
Simon Ogorodnik21465d82017-12-06 18:54:54 +03005import com.google.inject.name.Named
6import kotlinx.html.*
7import kotlinx.html.Entities.nbsp
Simon Ogorodnik1b722f72017-11-30 20:34:25 +03008import kotlinx.html.stream.appendHTML
Simon Ogorodnikf3db3f32017-11-30 19:04:11 +03009import org.jetbrains.dokka.*
Simon Ogorodnik21465d82017-12-06 18:54:54 +030010import org.jetbrains.dokka.LanguageService.RenderMode.FULL
11import org.jetbrains.dokka.LanguageService.RenderMode.SUMMARY
12import org.jetbrains.dokka.NodeKind.Companion.classLike
Simon Ogorodnik8ebfdda2017-12-07 00:13:40 +030013import org.jetbrains.dokka.NodeKind.Companion.memberLike
Simon Ogorodnik7acc52b2017-12-02 00:50:08 +030014import org.jetbrains.dokka.Utilities.bind
Simon Ogorodnik21a5b3e2017-12-11 16:08:58 +030015import org.jetbrains.dokka.Utilities.lazyBind
Simon Ogorodnik92db0442017-12-07 19:26:29 +030016import org.jetbrains.dokka.Utilities.toOptional
Simon Ogorodnik7acc52b2017-12-02 00:50:08 +030017import org.jetbrains.dokka.Utilities.toType
Simon Ogorodnik21465d82017-12-06 18:54:54 +030018import org.jetbrains.kotlin.preprocessor.mkdirsOrFail
Simon Ogorodnikda153a82017-12-11 21:42:23 +030019import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +030020import java.io.BufferedWriter
Simon Ogorodnik1b722f72017-11-30 20:34:25 +030021import java.io.File
Simon Ogorodnik8ebfdda2017-12-07 00:13:40 +030022import java.net.URI
23import java.net.URLEncoder
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +030024import kotlin.reflect.KClass
Simon Ogorodnikf3db3f32017-11-30 19:04:11 +030025
26
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +030027abstract class JavaLayoutHtmlFormatDescriptorBase : FormatDescriptor, DefaultAnalysisComponent {
Simon Ogorodnik7acc52b2017-12-02 00:50:08 +030028
29 override fun configureOutput(binder: Binder): Unit = with(binder) {
30 bind<Generator>() toType generatorServiceClass
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +030031 bind<LanguageService>() toType languageServiceClass
32 bind<JavaLayoutHtmlTemplateService>() toType templateServiceClass
Simon Ogorodnik92db0442017-12-07 19:26:29 +030033 bind<JavaLayoutHtmlUriProvider>() toType generatorServiceClass
Simon Ogorodnik21a5b3e2017-12-11 16:08:58 +030034 lazyBind<JavaLayoutHtmlFormatOutlineFactoryService>() toOptional outlineFactoryClass
Simon Ogorodnik7acc52b2017-12-02 00:50:08 +030035 }
36
Simon Ogorodnik7acc52b2017-12-02 00:50:08 +030037 val generatorServiceClass = JavaLayoutHtmlFormatGenerator::class
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +030038 abstract val languageServiceClass: KClass<out LanguageService>
39 abstract val templateServiceClass: KClass<out JavaLayoutHtmlTemplateService>
40 abstract val outlineFactoryClass: KClass<out JavaLayoutHtmlFormatOutlineFactoryService>?
Simon Ogorodnikf3db3f32017-11-30 19:04:11 +030041}
42
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +030043class JavaLayoutHtmlFormatDescriptor : JavaLayoutHtmlFormatDescriptorBase(), DefaultAnalysisComponentServices by KotlinAsKotlin {
44 override val languageServiceClass = KotlinLanguageService::class
45 override val templateServiceClass = JavaLayoutHtmlTemplateService.Default::class
46 override val outlineFactoryClass = null
47}
48
49
50interface JavaLayoutHtmlTemplateService {
51 fun composePage(
52 nodes: List<DocumentationNode>,
53 tagConsumer: TagConsumer<Appendable>,
54 headContent: HEAD.() -> Unit,
55 bodyContent: BODY.() -> Unit
56 )
57
58 class Default : JavaLayoutHtmlTemplateService {
59 override fun composePage(
60 nodes: List<DocumentationNode>,
61 tagConsumer: TagConsumer<Appendable>,
62 headContent: HEAD.() -> Unit,
63 bodyContent: BODY.() -> Unit
64 ) {
65 tagConsumer.html {
66 head(headContent)
67 body(block = bodyContent)
68 }
69 }
70 }
71}
72
73interface JavaLayoutHtmlFormatOutlineFactoryService {
74 fun generateOutlines(outputProvider: (URI) -> Appendable, nodes: Iterable<DocumentationNode>)
75}
76
77class JavaLayoutHtmlFormatOutputBuilder(
78 val output: Appendable,
79 val languageService: LanguageService,
Simon Ogorodnik92db0442017-12-07 19:26:29 +030080 val uriProvider: JavaLayoutHtmlUriProvider,
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +030081 val templateService: JavaLayoutHtmlTemplateService,
82 val uri: URI
83) {
Simon Ogorodnikf3db3f32017-11-30 19:04:11 +030084
Simon Ogorodnik21465d82017-12-06 18:54:54 +030085 val htmlConsumer = output.appendHTML()
Simon Ogorodnikf3db3f32017-11-30 19:04:11 +030086
Simon Ogorodnik92db0442017-12-07 19:26:29 +030087 val contentToHtmlBuilder = ContentToHtmlBuilder(uriProvider, uri)
Simon Ogorodnikf3db3f32017-11-30 19:04:11 +030088
Simon Ogorodnik21465d82017-12-06 18:54:54 +030089 private fun FlowContent.summaryNodeGroup(nodes: Iterable<DocumentationNode>, header: String, headerAsRow: Boolean = false, row: TBODY.(DocumentationNode) -> Unit) {
90 if (nodes.none()) return
Simon Ogorodnik6362f272017-12-06 20:18:47 +030091 if (!headerAsRow) {
92 h2 { +header }
Simon Ogorodnik6362f272017-12-06 20:18:47 +030093 }
Simon Ogorodnik21465d82017-12-06 18:54:54 +030094 table {
95 if (headerAsRow) thead { tr { td { h3 { +header } } } }
96 tbody {
97 nodes.forEach { node ->
98 row(node)
Simon Ogorodnik1b722f72017-11-30 20:34:25 +030099 }
100 }
101 }
102 }
103
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300104 fun FlowContent.metaMarkup(content: ContentNode) = with(contentToHtmlBuilder) {
105 appendContent(content)
106 }
107
108 fun FlowContent.metaMarkup(content: List<ContentNode>) = with(contentToHtmlBuilder) {
109 appendContent(content)
110 }
111
112 private fun TBODY.formatClassLikeRow(node: DocumentationNode) = tr {
Simon Ogorodnik92db0442017-12-07 19:26:29 +0300113 td { a(href = uriProvider.linkTo(node, uri)) { +node.simpleName() } }
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300114 td { metaMarkup(node.summary) }
115 }
116
117 private fun TBODY.formatFunctionSummaryRow(node: DocumentationNode) = tr {
118 td {
119 for (modifier in node.details(NodeKind.Modifier)) {
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300120 renderedSignature(modifier, SUMMARY)
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300121 }
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300122 renderedSignature(node.detail(NodeKind.Type), SUMMARY)
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300123 }
124 td {
125 div {
Simon Ogorodnik92db0442017-12-07 19:26:29 +0300126 a(href = uriProvider.linkTo(node, uri)) { +node.name }
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300127 }
128
129 metaMarkup(node.summary)
Simon Ogorodnik1b722f72017-11-30 20:34:25 +0300130 }
131 }
132
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300133 private fun FlowContent.renderedSignature(node: DocumentationNode, mode: LanguageService.RenderMode = SUMMARY) {
134 metaMarkup(languageService.render(node, mode))
135 }
136
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300137 private fun FlowContent.fullFunctionDocs(node: DocumentationNode) {
138 div {
Simon Ogorodnik9b1ccf72017-12-11 21:43:03 +0300139 id = node.signature()
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300140 h3 { +node.name }
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300141 pre { renderedSignature(node, FULL) }
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300142 metaMarkup(node.content)
143 for ((name, sections) in node.content.sections.groupBy { it.tag }) {
144 table {
145 thead { tr { td { h3 { +name } } } }
146 tbody {
147 sections.forEach {
148 tr {
149 td { it.subjectName?.let { +it } }
150 td {
151 metaMarkup(it.children)
152 }
153 }
154 }
155 }
156 }
157 }
158 }
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300159 }
160
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +0300161 fun appendPackage(node: DocumentationNode) = templateService.composePage(
162 listOf(node),
163 htmlConsumer,
164 headContent = {
165
166 },
167 bodyContent = {
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300168 h1 { +node.name }
169 metaMarkup(node.content)
170 summaryNodeGroup(node.members(NodeKind.Class), "Classes") { formatClassLikeRow(it) }
171 summaryNodeGroup(node.members(NodeKind.Exception), "Exceptions") { formatClassLikeRow(it) }
172 summaryNodeGroup(node.members(NodeKind.TypeAlias), "Type-aliases") { formatClassLikeRow(it) }
173 summaryNodeGroup(node.members(NodeKind.AnnotationClass), "Annotations") { formatClassLikeRow(it) }
174 summaryNodeGroup(node.members(NodeKind.Enum), "Enums") { formatClassLikeRow(it) }
175
176 summaryNodeGroup(node.members(NodeKind.Function), "Top-level functions summary") { formatFunctionSummaryRow(it) }
177
178
179 h2 { +"Top-level functions" }
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300180 for (function in node.members(NodeKind.Function)) {
181 fullFunctionDocs(function)
182 }
183 }
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +0300184 )
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300185
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +0300186
187 fun appendClassLike(node: DocumentationNode) = templateService.composePage(
188 listOf(node),
189 htmlConsumer,
190 headContent = {
191
192 },
193 bodyContent = {
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300194 h1 { +node.name }
195 pre { renderedSignature(node, FULL) }
196 metaMarkup(node.content)
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300197
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300198 h2 { +"Summary" }
Simon Ogorodnik6362f272017-12-06 20:18:47 +0300199 val functionsToDisplay = node.members(NodeKind.Function) + node.members(NodeKind.CompanionObjectFunction)
Simon Ogorodnik6362f272017-12-06 20:18:47 +0300200 summaryNodeGroup(functionsToDisplay, "Functions", headerAsRow = true) { formatFunctionSummaryRow(it) }
201
202 h2 { +"Functions" }
Simon Ogorodnik6362f272017-12-06 20:18:47 +0300203 for (function in functionsToDisplay) {
204 fullFunctionDocs(function)
205 }
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300206 }
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +0300207 )
Simon Ogorodnik1b722f72017-11-30 20:34:25 +0300208}
209
Simon Ogorodnik92db0442017-12-07 19:26:29 +0300210class ContentToHtmlBuilder(val uriProvider: JavaLayoutHtmlUriProvider, val uri: URI) {
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300211 fun FlowContent.appendContent(content: List<ContentNode>): Unit = content.forEach { appendContent(it) }
Simon Ogorodnik1b722f72017-11-30 20:34:25 +0300212
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300213 private fun FlowContent.hN(level: Int, classes: String? = null, block: CommonAttributeGroupFacadeFlowHeadingPhrasingContent.() -> Unit) {
214 when (level) {
215 1 -> h1(classes, block)
216 2 -> h2(classes, block)
217 3 -> h3(classes, block)
218 4 -> h4(classes, block)
219 5 -> h5(classes, block)
220 6 -> h6(classes, block)
221 }
222 }
223
224 fun FlowContent.appendContent(content: ContentNode) {
225 when (content) {
226 is ContentText -> +content.text
227 is ContentSymbol -> span("symbol") { +content.text }
228 is ContentKeyword -> span("keyword") { +content.text }
229 is ContentIdentifier -> span("identifier") {
230 content.signature?.let { id = it }
231 +content.text
232 }
233
234 is ContentHeading -> hN(level = content.level) { appendContent(content.children) }
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300235
236 is ContentEntity -> +content.text
237
238 is ContentStrong -> strong { appendContent(content.children) }
239 is ContentStrikethrough -> del { appendContent(content.children) }
240 is ContentEmphasis -> em { appendContent(content.children) }
241
242 is ContentOrderedList -> ol { appendContent(content.children) }
243 is ContentUnorderedList -> ul { appendContent(content.children) }
244 is ContentListItem -> consumer.li {
245 (content.children.singleOrNull() as? ContentParagraph)
246 ?.let { paragraph -> appendContent(paragraph.children) }
247 ?: appendContent(content.children)
248 }
249
250
251 is ContentCode -> pre { code { appendContent(content.children) } }
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300252 is ContentBlockSampleCode -> pre { code {} }
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300253 is ContentBlockCode -> pre { code {} }
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300254
255
256 is ContentNonBreakingSpace -> +nbsp
257 is ContentSoftLineBreak, is ContentIndentedSoftLineBreak -> {
258 }
259
260 is ContentParagraph -> p { appendContent(content.children) }
261
262 is ContentNodeLink -> {
Simon Ogorodnik92db0442017-12-07 19:26:29 +0300263 a(href = uriProvider.linkTo(content.node!!, uri)) { appendContent(content.children) }
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300264 }
265 is ContentExternalLink -> {
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300266 a(href = content.href) { appendContent(content.children) }
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300267 }
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300268
269 is ContentBlock -> appendContent(content.children)
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300270 }
271 }
272}
273
Simon Ogorodnik92db0442017-12-07 19:26:29 +0300274
275interface JavaLayoutHtmlUriProvider {
Simon Ogorodnikfe71c9e2017-12-07 20:00:19 +0300276 fun tryGetContainerUri(node: DocumentationNode): URI?
277 fun tryGetMainUri(node: DocumentationNode): URI?
278 fun containerUri(node: DocumentationNode): URI = tryGetContainerUri(node) ?: error("Unsupported ${node.kind}")
279 fun mainUri(node: DocumentationNode): URI = tryGetMainUri(node) ?: error("Unsupported ${node.kind}")
Simon Ogorodnik92db0442017-12-07 19:26:29 +0300280
281 fun linkTo(to: DocumentationNode, from: URI): String {
Simon Ogorodnikfe71c9e2017-12-07 20:00:19 +0300282 return mainUri(to).relativeTo(from).toString()
283 }
284
285 fun mainUriOrWarn(node: DocumentationNode): URI? = tryGetMainUri(node) ?: (null).also {
286 AssertionError("Not implemented mainUri for ${node.kind}").printStackTrace()
Simon Ogorodnik92db0442017-12-07 19:26:29 +0300287 }
288}
289
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +0300290class JavaLayoutHtmlFormatGenerator @Inject constructor(
291 @Named("outputDir") val root: File,
292 val languageService: LanguageService,
Simon Ogorodnikda153a82017-12-11 21:42:23 +0300293 val templateService: JavaLayoutHtmlTemplateService,
294 val logger: DokkaLogger
Simon Ogorodnik92db0442017-12-07 19:26:29 +0300295) : Generator, JavaLayoutHtmlUriProvider {
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300296
Simon Ogorodnik21a5b3e2017-12-11 16:08:58 +0300297 @set:Inject(optional = true) var outlineFactoryService: JavaLayoutHtmlFormatOutlineFactoryService? = null
298
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +0300299 fun createOutputBuilderForNode(node: DocumentationNode, output: Appendable)
Simon Ogorodnikfe71c9e2017-12-07 20:00:19 +0300300 = JavaLayoutHtmlFormatOutputBuilder(output, languageService, this, templateService, mainUri(node))
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300301
Simon Ogorodnikfe71c9e2017-12-07 20:00:19 +0300302 override fun tryGetContainerUri(node: DocumentationNode): URI? {
Simon Ogorodnik8ebfdda2017-12-07 00:13:40 +0300303 return when (node.kind) {
Simon Ogorodnik92db0442017-12-07 19:26:29 +0300304 NodeKind.Module -> URI("/").resolve(node.name + "/")
Simon Ogorodnikfe71c9e2017-12-07 20:00:19 +0300305 NodeKind.Package -> tryGetContainerUri(node.owner!!)?.resolve(node.name.replace('.', '/') + '/')
306 in classLike -> tryGetContainerUri(node.owner!!)?.resolve("${node.name}.html")
307 else -> null
Simon Ogorodnik8ebfdda2017-12-07 00:13:40 +0300308 }
309 }
310
Simon Ogorodnikfe71c9e2017-12-07 20:00:19 +0300311 override fun tryGetMainUri(node: DocumentationNode): URI? {
Simon Ogorodnik8ebfdda2017-12-07 00:13:40 +0300312 return when (node.kind) {
Simon Ogorodnikfe71c9e2017-12-07 20:00:19 +0300313 NodeKind.Package -> tryGetContainerUri(node)?.resolve("package-summary.html")
Simon Ogorodnikc08417e2017-12-07 22:12:02 +0300314 in classLike -> tryGetContainerUri(node)?.resolve("#")
Simon Ogorodnikda153a82017-12-11 21:42:23 +0300315 in memberLike -> tryGetMainUri(node.owner!!)?.resolveInPage(node)
316 NodeKind.TypeParameter -> node.path.firstNotNullResult(this::tryGetContainerUri)?.also { logger.warn("Not implemented mainUri for $node") }
Simon Ogorodnikfe71c9e2017-12-07 20:00:19 +0300317 NodeKind.AllTypes -> tryGetContainerUri(node.owner!!)?.resolve("allclasses.html")
318 else -> null
Simon Ogorodnik8ebfdda2017-12-07 00:13:40 +0300319 }
320 }
321
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300322 fun buildClass(node: DocumentationNode, parentDir: File) {
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300323 val fileForClass = parentDir.resolve(node.simpleName() + ".html")
324 fileForClass.bufferedWriter().use {
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +0300325 createOutputBuilderForNode(node, it).appendClassLike(node)
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300326 }
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300327 }
328
329 fun buildPackage(node: DocumentationNode, parentDir: File) {
330 assert(node.kind == NodeKind.Package)
331 val members = node.members
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300332 val directoryForPackage = parentDir.resolve(node.name.replace('.', File.separatorChar))
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300333 directoryForPackage.mkdirsOrFail()
334
335 directoryForPackage.resolve("package-summary.html").bufferedWriter().use {
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +0300336 createOutputBuilderForNode(node, it).appendPackage(node)
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300337 }
338
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300339 members.filter { it.kind in classLike }.forEach {
340 buildClass(it, directoryForPackage)
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300341 }
342 }
343
344
345 override fun buildPages(nodes: Iterable<DocumentationNode>) {
346 val module = nodes.single()
347
Simon Ogorodnik6dfe5822017-12-06 20:05:31 +0300348 val moduleRoot = root.resolve(module.name)
349 module.members.filter { it.kind == NodeKind.Package }.forEach { buildPackage(it, moduleRoot) }
Simon Ogorodnikf3db3f32017-11-30 19:04:11 +0300350 }
351
352 override fun buildOutlines(nodes: Iterable<DocumentationNode>) {
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +0300353 val uriToWriter = mutableMapOf<URI, BufferedWriter>()
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300354
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +0300355 fun provideOutput(uri: URI): BufferedWriter {
356 val normalized = uri.normalize()
357 uriToWriter[normalized]?.let { return it }
358 val file = root.resolve(normalized.path.removePrefix("/"))
359 val writer = file.bufferedWriter()
360 uriToWriter[normalized] = writer
361 return writer
362 }
363
Simon Ogorodnik21a5b3e2017-12-11 16:08:58 +0300364 outlineFactoryService?.generateOutlines(::provideOutput, nodes)
Simon Ogorodnik1aa31e82017-12-07 14:58:45 +0300365
366 uriToWriter.values.forEach { it.close() }
Simon Ogorodnikf3db3f32017-11-30 19:04:11 +0300367 }
368
Simon Ogorodnik1b722f72017-11-30 20:34:25 +0300369 override fun buildSupportFiles() {}
Simon Ogorodnikf3db3f32017-11-30 19:04:11 +0300370
371 override fun buildPackageList(nodes: Iterable<DocumentationNode>) {
Simon Ogorodnik1b722f72017-11-30 20:34:25 +0300372
Simon Ogorodnikf3db3f32017-11-30 19:04:11 +0300373 }
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300374}
375
Simon Ogorodnik8ebfdda2017-12-07 00:13:40 +0300376fun DocumentationNode.signature() = detail(NodeKind.Signature).name
377fun DocumentationNode.signatureUrlEncoded() = URLEncoder.encode(detail(NodeKind.Signature).name, "UTF-8")
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300378
Simon Ogorodnik21465d82017-12-06 18:54:54 +0300379
Simon Ogorodnikbf22e822017-12-11 21:41:27 +0300380fun URI.relativeTo(uri: URI): URI {
Simon Ogorodnik8ebfdda2017-12-07 00:13:40 +0300381 // Normalize paths to remove . and .. segments
Simon Ogorodnikbf22e822017-12-11 21:41:27 +0300382 val base = uri.normalize()
383 val child = this.normalize()
Simon Ogorodnik8ebfdda2017-12-07 00:13:40 +0300384
385 // Split paths into segments
386 var bParts = base.path.split('/').dropLastWhile { it.isEmpty() }
387 val cParts = child.path.split('/').dropLastWhile { it.isEmpty() }
388
389 // Discard trailing segment of base path
390 if (bParts.isNotEmpty() && !base.path.endsWith("/")) {
391 bParts = bParts.dropLast(1)
392 }
393
Simon Ogorodnikbf22e822017-12-11 21:41:27 +0300394 // Compute common prefix
395 val commonPartsSize = bParts.zip(cParts).count { (basePart, childPart) -> basePart == childPart }
Simon Ogorodnik8ebfdda2017-12-07 00:13:40 +0300396
Simon Ogorodnikbf22e822017-12-11 21:41:27 +0300397 return URI.create(buildString {
398 bParts.drop(commonPartsSize).joinTo(this, separator = "") { "../" }
399 cParts.drop(commonPartsSize).joinTo(this, separator = "/")
400 child.rawQuery?.let {
401 append("?")
402 append(it)
Simon Ogorodnik8ebfdda2017-12-07 00:13:40 +0300403 }
Simon Ogorodnikbf22e822017-12-11 21:41:27 +0300404 child.rawFragment?.let {
405 append("#")
406 append(it)
407 }
408 })
409}
Simon Ogorodnik8ebfdda2017-12-07 00:13:40 +0300410
Simon Ogorodnikda153a82017-12-11 21:42:23 +0300411fun URI.resolveInPage(node: DocumentationNode): URI = resolve("#${node.signatureUrlEncoded()}")