-
Notifications
You must be signed in to change notification settings - Fork 391
/
DefaultTemplateModelFactory.kt
209 lines (192 loc) · 8.12 KB
/
DefaultTemplateModelFactory.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package org.jetbrains.dokka.base.renderers.html.innerTemplating
import freemarker.core.Environment
import freemarker.template.*
import kotlinx.html.*
import kotlinx.html.stream.createHTML
import org.jetbrains.dokka.DokkaConfiguration
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.base.DokkaBaseConfiguration
import org.jetbrains.dokka.base.renderers.URIExtension
import org.jetbrains.dokka.base.renderers.html.TEMPLATE_REPLACEMENT
import org.jetbrains.dokka.base.renderers.html.command.consumers.ImmediateResolutionTagConsumer
import org.jetbrains.dokka.base.renderers.html.templateCommand
import org.jetbrains.dokka.base.renderers.html.templateCommandAsHtmlComment
import org.jetbrains.dokka.base.renderers.isImage
import org.jetbrains.dokka.base.resolvers.local.LocationProvider
import org.jetbrains.dokka.base.templating.PathToRootSubstitutionCommand
import org.jetbrains.dokka.base.templating.ProjectNameSubstitutionCommand
import org.jetbrains.dokka.base.templating.ReplaceVersionsCommand
import org.jetbrains.dokka.base.templating.SubstitutionCommand
import org.jetbrains.dokka.model.DisplaySourceSet
import org.jetbrains.dokka.model.withDescendants
import org.jetbrains.dokka.pages.ContentPage
import org.jetbrains.dokka.pages.PageNode
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.configuration
import java.net.URI
class DefaultTemplateModelFactory(val context: DokkaContext) : TemplateModelFactory {
private val configuration = configuration<DokkaBase, DokkaBaseConfiguration>(context)
private val isPartial = context.configuration.delayTemplateSubstitution
private fun <R> TagConsumer<R>.prepareForTemplates() =
if (context.configuration.delayTemplateSubstitution || this is ImmediateResolutionTagConsumer) this
else ImmediateResolutionTagConsumer(this, context)
data class SourceSetModel(val name: String, val platform: String, val filter: String)
override fun buildModel(
page: PageNode,
resources: List<String>,
locationProvider: LocationProvider,
content: String
): TemplateMap {
val path = locationProvider.resolve(page)
val pathToRoot = locationProvider.pathToRoot(page)
val mapper = mutableMapOf<String, Any>()
mapper["pageName"] = page.name
mapper["resources"] = PrintDirective {
val sb = StringBuilder()
if (isPartial)
sb.templateCommandAsHtmlComment(
PathToRootSubstitutionCommand(
TEMPLATE_REPLACEMENT,
default = pathToRoot
)
) { resourcesForPage(TEMPLATE_REPLACEMENT, resources) }
else
sb.resourcesForPage(pathToRoot, resources)
sb.toString()
}
mapper["content"] = PrintDirective { content }
mapper["version"] = PrintDirective {
createHTML().prepareForTemplates().templateCommand(ReplaceVersionsCommand(path.orEmpty()))
}
mapper["template_cmd"] = TemplateDirective(context.configuration, pathToRoot)
if (page is ContentPage) {
val sourceSets = page.content.withDescendants()
.flatMap { it.sourceSets }
.distinct()
.sortedBy { it.comparableKey }
.map { SourceSetModel(it.name, it.platform.key, it.sourceSetIDs.merged.toString()) }
.toList()
if (sourceSets.isNotEmpty()) {
mapper["sourceSets"] = sourceSets
}
}
return mapper
}
override fun buildSharedModel(): TemplateMap = mapOf<String, Any>(
"footerMessage" to (configuration?.footerMessage?.takeIf { it.isNotEmpty() }
?: DokkaBaseConfiguration.defaultFooterMessage)
)
private val DisplaySourceSet.comparableKey
get() = sourceSetIDs.merged.let { it.scopeId + it.sourceSetName }
private val String.isAbsolute: Boolean
get() = URI(this).isAbsolute
private fun Appendable.resourcesForPage(pathToRoot: String, resources: List<String>): Unit =
resources.forEach {
append(with(createHTML()) {
when {
it.URIExtension == "css" ->
link(
rel = LinkRel.stylesheet,
href = if (it.isAbsolute) it else "$pathToRoot$it"
)
it.URIExtension == "js" ->
script(
type = ScriptType.textJavaScript,
src = if (it.isAbsolute) it else "$pathToRoot$it"
) {
if (it == "scripts/main.js" || it.endsWith("_deferred.js"))
defer = true
else
async = true
}
it.isImage() -> link(href = if (it.isAbsolute) it else "$pathToRoot$it")
else -> null
}
} ?: it)
}
}
private class PrintDirective(val generateData: () -> String) : TemplateDirectiveModel {
override fun execute(
env: Environment,
params: MutableMap<Any?, Any?>?,
loopVars: Array<TemplateModel>?,
body: TemplateDirectiveBody?
) {
if (params?.isNotEmpty() == true) throw TemplateModelException(
"Parameters are not allowed"
)
if (loopVars?.isNotEmpty() == true) throw TemplateModelException(
"Loop variables are not allowed"
)
env.out.write(generateData())
}
}
private class TemplateDirective(val configuration: DokkaConfiguration, val pathToRoot: String) : TemplateDirectiveModel {
override fun execute(
env: Environment,
params: MutableMap<Any?, Any?>?,
loopVars: Array<TemplateModel>?,
body: TemplateDirectiveBody?
) {
val commandName = params?.get(PARAM_NAME) ?: throw TemplateModelException(
"The required $PARAM_NAME parameter is missing."
)
val replacement = (params[PARAM_REPLACEMENT] as? SimpleScalar)?.asString ?: TEMPLATE_REPLACEMENT
when ((commandName as? SimpleScalar)?.asString) {
"pathToRoot" -> {
body ?: throw TemplateModelException(
"No directive body for $commandName command."
)
executeSubstituteCommand(
PathToRootSubstitutionCommand(
replacement, pathToRoot
),
"pathToRoot",
pathToRoot,
Context(env, body)
)
}
"projectName" -> {
body ?: throw TemplateModelException(
"No directive body $commandName command."
)
executeSubstituteCommand(
ProjectNameSubstitutionCommand(
replacement, configuration.moduleName
),
"projectName",
configuration.moduleName,
Context(env, body)
)
}
else -> throw TemplateModelException(
"The parameter $PARAM_NAME $commandName is unknown"
)
}
}
private data class Context(val env: Environment, val body: TemplateDirectiveBody)
private fun executeSubstituteCommand(
command: SubstitutionCommand,
name: String,
value: String,
ctx: Context
) {
if (configuration.delayTemplateSubstitution)
ctx.env.out.templateCommandAsHtmlComment(command) {
renderWithLocalVar(name, command.pattern, ctx)
}
else {
renderWithLocalVar(name, value, ctx)
}
}
private fun renderWithLocalVar(name: String, value: String, ctx: Context) =
with(ctx) {
env.setVariable(name, SimpleScalar(value))
body.render(env.out)
env.setVariable(name, null)
}
companion object {
const val PARAM_NAME = "name"
const val PARAM_REPLACEMENT = "replacement"
}
}