Wizard: add simple js template

This commit is contained in:
Ilya Kirillov
2019-12-17 15:26:18 +03:00
parent 5d56f3d28b
commit b29e6d0c69
10 changed files with 175 additions and 58 deletions
@@ -5,6 +5,9 @@ import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import ${serverEngine.getImport()}
#foreach($import in $imports)
import $import
#end
import kotlinx.html.*
fun HTML.index() {
@@ -15,6 +18,9 @@ fun HTML.index() {
div {
+"Hello from Ktor"
}
#foreach($element in $elements)
$element
#end
}
}
@@ -24,6 +30,9 @@ fun main() {
get("/") {
call.respondHtml(HttpStatusCode.OK, HTML::index)
}
#foreach($route in $routes)
$route
#end
}
}.start(wait = true)
}
@@ -13,7 +13,7 @@ sealed class Identificator {
}
class GeneratedIdentificator(prefix: String?) : Identificator() {
override val id = """${prefix.orEmpty()}_${RandomIdGenerator.generate()}"""
override val id = "${prefix.orEmpty()}_${RandomIdGenerator.generate()}"
}
@@ -14,7 +14,7 @@ fun <I : IrsOwner> I.withIrs(vararg irs: BuildSystemIR) = withReplacedIrs(irs =
inline fun <reified I : BuildSystemIR> IrsOwner.irsOfType(): List<I> =
irs.filterIsInstance<I>()
irs.filterIsInstance<I>()
inline fun <reified I : BuildSystemIR> IrsOwner.irsOfTypeOrNull() =
@@ -25,4 +25,4 @@ inline fun <reified T : BuildSystemIR> IrsOwner.firstIrOfType() =
irs.firstOrNull { ir -> ir is T } as T?
fun IrsOwner.freeIrs(): List<FreeIR> =
irs.filterIsInstance<FreeIR>()
irs.filterIsInstance<FreeIR>()
@@ -50,6 +50,22 @@ data class GradleCallIr(
}
}
data class GradleNewInstanceCall(
val name: String,
val parameters: List<BuildSystemIR> = emptyList()
) : GradleIR {
override fun GradlePrinter.renderGradle() {
if (dsl == GradlePrinter.GradleDsl.GROOVY) {
+"new "
}
call(name, forceBrackets = true) {
parameters.list { it.render(this) }
}
}
}
data class GradleSectionIR(
val name: String,
@@ -1,14 +1,16 @@
package org.jetbrains.kotlin.tools.projectWizard.plugins.templates
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.service.FileSystemWizardService
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.updateBuildFiles
import org.jetbrains.kotlin.tools.projectWizard.templates.*
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.TemplateInterceptor
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.fold
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.InterceptionPoint
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.TemplateInterceptionApplicationState
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.applyAll
class TemplatesPlugin(context: Context) : Plugin(context) {
val templates by property<Map<String, Template>>(
@@ -101,12 +103,37 @@ class TemplatesPlugin(context: Context) : Plugin(context) {
withAction {
updateBuildFiles { buildFile ->
@Suppress("UNCHECKED_CAST")
val interceptors = buildFile.sourcesets.mapNotNull { sourceset ->
sourceset.template?.createInterceptors(sourceset) as? List<TemplateInterceptor>
}.flatten().fold()
interceptors.applyUntilConverge(buildFile).asSuccess()
val sourcesets = buildFile.sourcesets
val applicationState = sourcesets.mapNotNull { sourceset ->
sourceset.template?.createInterceptors(sourceset)
}.flatten()
.applyAll(TemplateInterceptionApplicationState(buildFile, emptyMap()))
val templateEngine = VelocityTemplateEngine()
val templatesApplicationResult = sourcesets.map { sourceset ->
val settings = applicationState.sourcesetToSettings[sourceset.original.identificator].orEmpty()
applyFileTemplatesFromSourceset(sourceset, templateEngine, settings)
}.sequenceIgnore()
templatesApplicationResult andThen applicationState.buildFileIR.asSuccess()
}
}
}
private fun TaskRunningContext.applyFileTemplatesFromSourceset(
sourceset: SourcesetIR,
templateEngine: TemplateEngine,
interceptionPointSettings: Map<InterceptionPoint<Any>, Any>
): TaskResult<Unit> {
val template = sourceset.template ?: return UNIT_SUCCESS
val settings = with(template) { settingsAsMap(sourceset.original) }
val allSettings = settings + interceptionPointSettings.mapKeys { it.key.name }
return with(template) { getFileTemplates(sourceset) }.map { fileTemplate ->
val fileText = templateEngine.renderTemplate(fileTemplate, allSettings)
val path = sourceset.path / fileTemplate.relativePath
service<FileSystemWizardService>()!!.createFile(path, fileText)
}.sequenceIgnore()
}
}
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.DefaultRepo
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Repositories
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.InterceptionPoint
class KtorServerTemplate : Template() {
override val title: String = "Ktor-based Server"
@@ -55,6 +56,11 @@ class KtorServerTemplate : Template() {
val serverEngine by enumSetting<KtorServerEngine>("Ktor Server", GenerationPhase.PROJECT_GENERATION)
val imports = InterceptionPoint("imports", emptyList<String>())
val routes = InterceptionPoint("routes", emptyList<String>())
val elements = InterceptionPoint("elements", emptyList<String>())
override val interceptionPoints: List<InterceptionPoint<Any>> = listOf(imports, routes, elements)
override val settings: List<TemplateSetting<*, *>> = listOf(serverEngine)
}
@@ -17,7 +17,7 @@ import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.DefaultRepository
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.Interceptor
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.TemplateInterceptor
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.interceptTemplate
class SimpleJsClientTemplate : Template() {
@@ -40,13 +40,42 @@ class SimpleJsClientTemplate : Template() {
FileTemplateDescriptor("$id/client.kt.vm", sourcesPath("client.kt"))
)
override fun createInterceptors(sourceset: SourcesetIR): List<Interceptor> = buildList {
override fun createInterceptors(sourceset: SourcesetIR): List<TemplateInterceptor> = buildList {
+interceptTemplate(KtorServerTemplate()) {
applicableIf { buildFileIR ->
val tasks = buildFileIR.irsOfTypeOrNull<GradleConfigureTaskIR>() ?: return@applicableIf false
tasks.none { it.taskAccess.name.endsWith("Jar") }
}
interceptAtPoint(template.routes) { value ->
if (value.isNotEmpty()) return@interceptAtPoint value
buildList {
+value
+"""
static("/static") {
resources()
}
""".trimIndent()
}
}
interceptAtPoint(template.imports) { value ->
if (value.isNotEmpty()) return@interceptAtPoint value
buildList {
+value
+"io.ktor.http.content.resources"
+"io.ktor.http.content.static"
}
}
interceptAtPoint(template.elements) { value ->
if (value.isNotEmpty()) return@interceptAtPoint value
buildList {
+value
+"""script(src = "/static/output.js") {}"""
}
}
transformBuildFile { buildFileIR ->
val jsSourcesetName = sourceset.safeAs<SourcesetModuleIR>()?.targetName ?: return@transformBuildFile null
val jvmTarget = buildFileIR.targets.firstOrNull { target ->
@@ -64,7 +93,7 @@ class SimpleJsClientTemplate : Template() {
val from = GradleCallIr(
"from",
listOf(
GradleCallIr(
GradleNewInstanceCall(
"File",
listOf(
GradlePropertyAccessIR("$webPackTaskName.destinationDirectory"),
@@ -120,7 +149,7 @@ class SimpleJsClientTemplate : Template() {
}
companion object {
const val JS_OUTPUT_FILE_NAME = "output.js"
const val WEBPACK_TASK_CLASS = "KotlinWebpack"
private const val JS_OUTPUT_FILE_NAME = "output.js"
private const val WEBPACK_TASK_CLASS = "KotlinWebpack"
}
}
@@ -4,8 +4,6 @@ import org.jetbrains.kotlin.tools.projectWizard.Identificator
import org.jetbrains.kotlin.tools.projectWizard.SettingsOwner
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.core.service.FileSystemWizardService
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.Interceptor
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.multiplatform.TargetConfigurationIR
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
@@ -15,6 +13,8 @@ import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Sourceset
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.InterceptionPoint
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.TemplateInterceptor
import java.nio.file.Path
import kotlin.properties.ReadOnlyProperty
@@ -64,6 +64,7 @@ abstract class Template : SettingsOwner {
abstract val sourcesetTypes: Set<SourcesetType>
open val settings: List<TemplateSetting<*, *>> = emptyList()
open val interceptionPoints: List<InterceptionPoint<Any>> = emptyList()
open fun TaskRunningContext.getRequiredLibraries(sourceset: SourcesetIR): List<DependencyIR> = emptyList()
@@ -79,16 +80,11 @@ abstract class Template : SettingsOwner {
open fun TaskRunningContext.getFileTemplates(sourceset: SourcesetIR): List<FileTemplateDescriptor> = emptyList()
open fun createInterceptors(sourceset: SourcesetIR): List<Interceptor> = emptyList()
open fun createInterceptors(sourceset: SourcesetIR): List<TemplateInterceptor> = emptyList()
fun TaskRunningContext.applyToSourceset(
templateEngine: TemplateEngine,
sourceset: SourcesetIR
): TaskResult<TemplateApplicationResult> {
val fileSystemService = service<FileSystemWizardService>()!!
val allSettings = createDefaultSettings() + settingsAsMap(sourceset.original)
val librariesToAdd = getRequiredLibraries(sourceset)
val irsToAddToBuildFile = getIrsToAddToBuildFile(sourceset)
@@ -101,20 +97,15 @@ abstract class Template : SettingsOwner {
}
val result = TemplateApplicationResult(librariesToAdd, irsToAddToBuildFile, targetsUpdater)
return getFileTemplates(sourceset).map { fileTemplate ->
val fileText = templateEngine.renderTemplate(fileTemplate, allSettings)
val path = sourceset.path / fileTemplate.relativePath
fileSystemService.createFile(path, fileText)
}.sequenceIgnore() andThen result.asSuccess()
return result.asSuccess()
}
private fun TaskRunningContext.settingsAsMap(sourceset: Sourceset): Map<String, Any> =
fun TaskRunningContext.settingsAsMap(sourceset: Sourceset): Map<String, Any> =
withSettingsOf(sourceset) {
settings.associate { setting ->
setting.path to setting.reference.settingValue
}
}
} + createDefaultSettings()
private fun TaskRunningContext.createDefaultSettings() = mapOf(
@@ -250,7 +241,7 @@ fun TaskRunningContext.applyTemplateToSourceset(
): TaskResult<TemplateApplicationResult> = when (template) {
null -> TemplateApplicationResult.EMPTY.asSuccess()
else -> with(template) {
applyToSourceset(templateEngine, sourceset)
applyToSourceset(sourceset)
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors
data class InterceptionPoint<out T: Any>(val name: String, val initialValue: T)
data class InterceptionPointModifier<out T : Any>(
val point: InterceptionPoint<@UnsafeVariance T>,
val modifier: (@UnsafeVariance T) -> T
)
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors
import org.jetbrains.kotlin.tools.projectWizard.Identificator
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.BuildFileIR
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.sourcesets
import org.jetbrains.kotlin.tools.projectWizard.templates.Template
@@ -13,34 +14,60 @@ import org.jetbrains.kotlin.tools.projectWizard.transformers.TransformerFunction
interface Interceptor
typealias InterceptionPointValues = Map<InterceptionPoint<Any>, Any>
typealias SourcesetInterceptionPointValues = Map<Identificator, InterceptionPointValues>
data class TemplateInterceptionApplicationState(
val buildFileIR: BuildFileIR,
val sourcesetToSettings: SourcesetInterceptionPointValues
)
data class TemplateInterceptor(
val template: Template,
val applicabilityCheckers: List<Predicate<BuildFileIR>>,
val buildFileTransformers: List<TransformerFunction<BuildFileIR>>
val buildFileTransformers: List<TransformerFunction<BuildFileIR>>,
val interceptionPointModifiers: List<InterceptionPointModifier<Any>>
) : Interceptor {
fun applyTo(buildFileIR: BuildFileIR): BuildFileIR? {
if (applicabilityCheckers.any { checker -> !checker(buildFileIR) }) return null
fun applyTo(state: TemplateInterceptionApplicationState): TemplateInterceptionApplicationState {
if (applicabilityCheckers.any { checker -> !checker(state.buildFileIR) }) return state
var isApplied = false
var result: BuildFileIR = buildFileIR
val sourcesetsWithTemplate = state.buildFileIR.sourcesets.filter { it.template?.id == template.id }
if (sourcesetsWithTemplate.isEmpty()) return state
for (transformer in buildFileTransformers) {
val newResult = transformer(result)
if (newResult != null) {
result = newResult
isApplied = true
val transformedBuildFile = applyBuildFileTransformers(state.buildFileIR)
val mutableValues = state.sourcesetToSettings.toMutableMap()
for (sourceset in sourcesetsWithTemplate) {
mutableValues.compute(sourceset.original.identificator) { _, values ->
applyInterceptionPointModifiers(values.orEmpty())
}
}
return result.takeIf { isApplied }
return TemplateInterceptionApplicationState(transformedBuildFile, mutableValues)
}
fun applyUntilConverge(buildFileIR: BuildFileIR) =
generateSequence(buildFileIR, { buildFile ->
applyTo(buildFile)
}).last()
private fun applyInterceptionPointModifiers(values: InterceptionPointValues): InterceptionPointValues {
val mutableValues = values.toMutableMap()
for (modifier in interceptionPointModifiers) {
mutableValues.compute(modifier.point) { _, value ->
modifier.modifier(value ?: modifier.point.initialValue)
}
}
return mutableValues
}
private fun applyBuildFileTransformers(buildFile: BuildFileIR): BuildFileIR =
buildFileTransformers.fold(buildFile) { result, transformer ->
transformer(result) ?: result
}
}
class TemplateInterceptorBuilder<T : Template>(private val template: T) {
fun List<TemplateInterceptor>.applyAll(state: TemplateInterceptionApplicationState) =
fold(state) { currentState, interceptor ->
interceptor.applyTo(currentState)
}
class TemplateInterceptorBuilder<T : Template>(val template: T) {
private val buildFileTransformers = mutableListOf<TransformerFunction<BuildFileIR>>()
fun transformBuildFile(transform: TransformerFunction<BuildFileIR>) {
buildFileTransformers += transform
@@ -51,20 +78,19 @@ class TemplateInterceptorBuilder<T : Template>(private val template: T) {
applicabilityCheckers += checker
}
init {
applicableIf { buildFile ->
buildFile.sourcesets.any { it.template?.id == template.id }
}
private val interceptionPointModifiers = mutableListOf<InterceptionPointModifier<Any>>()
fun <T : Any> interceptAtPoint(point: InterceptionPoint<T>, modifier: (T) -> T) {
interceptionPointModifiers.add(InterceptionPointModifier(point, modifier))
}
fun build() = TemplateInterceptor(applicabilityCheckers, buildFileTransformers)
}
fun List<TemplateInterceptor>.fold() =
TemplateInterceptor(
flatMap { it.applicabilityCheckers },
flatMap { it.buildFileTransformers }
fun build() = TemplateInterceptor(
template,
applicabilityCheckers,
buildFileTransformers,
interceptionPointModifiers
)
}
fun <T : Template> interceptTemplate(template: T, builder: TemplateInterceptorBuilder<T>.() -> Unit) =