Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle api links #32

Draft
wants to merge 5 commits into
base: parser
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions documentation/docs/static-page/links.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ title: Links
## Links

Our side supports standard markdown links:
- TODO Using standard [urls](https://pl.wikipedia.org/wiki/Uniform_Resource_Locator)
- TODO To [other (html) pages](/docs/static-page/samples/plain_html_file.html) (or [md based files](/docs/static-page/samples/plain_md_file.md)) in our documentation that using paths relative to root od documentation e.g. `/docs/static-page/sample/plain_html_file.html` for this project
- TODO To [other (html) pages](samples/plain_html_file.html) (or [md based files](samples/plain_md_file.md)) in our documentation that using paths relative to this file e.g. using `samples/plain_html_file.html`
- TODO To API using `[fully.quallifty.Name]`
- Using standard [urls](https://pl.wikipedia.org/wiki/Uniform_Resource_Locator)
- To [other (html) pages](/docs/static-page/samples/plain_html_file.html) (or [md based files](/docs/static-page/samples/plain_md_file.md)) in our documentation that using paths relative to root od documentation e.g. `/docs/static-page/sample/plain_html_file.html` for this project
- To [other (html) pages](samples/plain_html_file.html) (or [md based files](samples/plain_md_file.md)) in our documentation that using paths relative to this file e.g. using `samples/plain_html_file.html`
- To API (using `[fully.quallifty.Name]`)
- [package](com.virtuslab.dokka.site)
- [class](com.virtuslab.dokka.site#StaticSiteContext)
- [val](com.virtuslab.dokka.site::apiPageDRI)
- [prop](com.virtuslab.dokka.site#AContentPage::content)
- TODO [top level fun](com.virtuslab.dokka.site::loadTemplateFile(java.io.File))
- TODO [member fun](com.virtuslab.dokka.site#TemplateFile::resolveMarkdown(com.virtuslab.dokka.site.RenderingContext))
- TODO [member fun (no params)](com.virtuslab.dokka.site#StaticSiteContext::resolveLinkToApi)

112 changes: 92 additions & 20 deletions src/main/kotlin/com/virtuslab/dokka/site/StaticSiteContext.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.virtuslab.dokka.site

import org.jetbrains.dokka.base.parsers.MarkdownParser
import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter
import org.jetbrains.dokka.links.Callable
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.links.JavaClassReference
import org.jetbrains.dokka.model.doc.DocTag
import org.jetbrains.dokka.model.doc.Text
import org.jetbrains.dokka.model.properties.PropertyContainer
Expand All @@ -12,8 +15,11 @@ import org.jetbrains.dokka.pages.DCI
import org.jetbrains.dokka.pages.PageNode
import org.jetbrains.dokka.plugability.DokkaContext
import java.io.File
import java.net.MalformedURLException
import java.net.URL
import java.nio.file.Path

class StaticSiteContext(val root: File, cxt: DokkaContext){
class StaticSiteContext(val root: File, cxt: DokkaContext) {
val docsFile = File(root, "docs")

fun indexPage(): BaseStaticSiteProcessor.StaticPageNode? {
Expand Down Expand Up @@ -63,22 +69,18 @@ class StaticSiteContext(val root: File, cxt: DokkaContext){
null
}

private fun parseMarkdown(page: PreResolvedPage, dri: DRI, allDRIs: Map<String, DRI>): ContentNode {
private fun parseMarkdown(
page: PreResolvedPage,
dri: DRI,
allDRIs: Map<Path, DRI>,
proceededFilePath: String
): ContentNode {
val nodes = if (page.hasMarkdown) {
val parser = ExtendableMarkdownParser(page.code) { link ->
val driKey = if (link.startsWith("/")) {
// handle root related links
link.replace('/', '.').removePrefix(".")
} else {
val unSuffixedDri = dri.packageName!!.removeSuffix(".html").removeSuffix(".md")
val parentDri = unSuffixedDri.take(unSuffixedDri.indexOfLast('.'::equals)).removePrefix("_.")
"${parentDri}.${link.replace('/', '.')}"
}
allDRIs[driKey]
}
val externalDri = getExternalDriResolver(allDRIs, proceededFilePath)
val parser = MarkdownParser(externalDri)

val docTag = try {
parser.parse()
parser.parseStringToDocNode(page.code)
} catch (e: Throwable) {
val msg = "Error rendering (dri = $dri): ${e.message}"
println("ERROR: $msg") // TODO (#14): provide proper error handling
Expand All @@ -105,14 +107,20 @@ class StaticSiteContext(val root: File, cxt: DokkaContext){
PropertyContainer.empty()
)

fun loadFiles(files: List<File>, customChildren: List<PageNode> = emptyList()): List<BaseStaticSiteProcessor.StaticPageNode> {
fun loadFiles(
files: List<File>,
customChildren: List<PageNode> = emptyList()
): List<BaseStaticSiteProcessor.StaticPageNode> {
val all = files.mapNotNull { loadTemplate(it) }
fun flatten(it: BaseStaticSiteProcessor.LoadedTemplate): List<String> =
listOf(it.relativePath(root)) + it.children.flatMap { flatten(it) }

fun pathToDri(path: String) = DRI("_.$path")

val driMap = all.flatMap { flatten(it) }.map { it to pathToDri(it) }.toMap()
val driMap = all.flatMap(::flatten)
.flatMap(::createBothMdAndHtmlKeys)
.map { Path.of(it).normalize().run { this to pathToDri(this.toString()) } }
.toMap()

fun templateToPage(myTemplate: BaseStaticSiteProcessor.LoadedTemplate): BaseStaticSiteProcessor.StaticPageNode {
val dri = pathToDri(myTemplate.relativePath(root))
Expand All @@ -126,8 +134,13 @@ class StaticSiteContext(val root: File, cxt: DokkaContext){
println("ERROR: $msg") // TODO (#14): provide proper error handling
PreResolvedPage("", null, true)
}
val content = parseMarkdown(page, dri, driMap)
val children = myTemplate.children.map { templateToPage(it) }

val proceededFilePath = myTemplate.file.path
.removePrefix("documentation")
.dropLastWhile { x -> x != File.separatorChar }
.removeSuffix(File.separator)
val content = parseMarkdown(page, dri, driMap, proceededFilePath)
val children = myTemplate.children.map(::templateToPage)
return BaseStaticSiteProcessor.StaticPageNode(
myTemplate.templateFile.title(),
children + customChildren,
Expand All @@ -137,7 +150,66 @@ class StaticSiteContext(val root: File, cxt: DokkaContext){
content
)
}
return all.map { templateToPage(it) }
return all.map(::templateToPage)
}
}

private fun createBothMdAndHtmlKeys(x: String) =
listOf(
if (x.endsWith(".md")) x.removeSuffix(".md").plus(".html") else x,
if (x.endsWith(".html")) x.removeSuffix(".html").plus(".md") else x
)

private fun getExternalDriResolver(allDRIs: Map<Path, DRI>, proceededFilePath: String): (String) -> DRI? = { it ->
try {
URL(it)
null
} catch (e: MalformedURLException) {
val resolvePath = { x: String ->
if (it.startsWith('/')) docsFile.resolve(x)
else File(proceededFilePath).resolve(x)
}
val path = Path.of(it).normalize().toString()
.let { s -> resolvePath(s) }
.toString()
.removePrefix(File.separator)
.replace(File.separatorChar, '.')
.let(Path::of)

allDRIs[path] ?: it.replace("\\s".toRegex(), "").resolveLinkToApi()
}
}

private fun String.resolveLinkToApi() = when {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will work only partially for Kotlin since DRIs are a bir more complicated. @Kordyjan @kamildoleglo could you help here? Maybe we can reuse dokka code?

We should also create extension point to that will alow users to provide its own mapping String -> DRI?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I'd personally discourage creating DRIs by hand if possible. Reusing the IntelliJ resolveKDocLink method feels more correct here. It will certainly limit the types of links possible (eg. no linking to overloaded methods, because the KDoc specification does not allow it for now), but will be more fail-proof.

If you have to create the DRIs manually though, the JavaClassReference looks wrong for starters (not really sure what issue it resolves in Dokka TBH, I'll try to find out). Moreover, toplevel methods (classes, etc.) should have a null packageName, not a blank one. Also looks like it's going to fail for elements with generic attributes, because you're not creating TypeReferences anywhere and for extension methods (and properties), because you're not setting the receiver anywhere

'#' in this -> {
val (packageName, classNameAndRest) = split('#')
when {
"::" in classNameAndRest -> {
val (className, callableAndParams) = classNameAndRest.split("::")
makeApiDRI(callableAndParams, packageName, className)
}
else -> DRI(packageName = packageName, classNames = classNameAndRest)
}
}
"::" in this -> {
val (packageName, callableAndParams) = split("::")
makeApiDRI(callableAndParams, packageName)
}
else -> DRI(packageName = this)
}

private fun makeApiDRI(
callableAndParams: String,
packageName: String,
className: String? = null
): DRI {
val callableName = callableAndParams.takeWhile { it != '(' }
val params = callableAndParams.dropWhile { it != '(' }
.removePrefix("(")
.removeSuffix(")")
.split(',')
.filter(String::isNotBlank)
.map(::JavaClassReference)
val callable = Callable(name = callableName, params = params)
return DRI(packageName = packageName, classNames = className, callable = callable)
}
}
Loading