From b29e6d0c69606a3700c6a3af32f659b2ed92c34c Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Tue, 17 Dec 2019 15:26:18 +0300 Subject: [PATCH] Wizard: add simple js template --- .../templates/ktorServer/server.kt.vm | 9 +++ .../tools/projectWizard/SettingOwnerWithId.kt | 2 +- .../projectWizard/ir/buildsystem/IrsOwner.kt | 4 +- .../ir/buildsystem/gradle/GradleIR.kt | 16 ++++ .../plugins/templates/TemplatesPlugin.kt | 41 ++++++++-- .../templates/KtorServerTemplate.kt | 6 ++ .../templates/SimpleJsClientTemplate.kt | 39 ++++++++-- .../tools/projectWizard/templates/Template.kt | 25 ++---- .../interceptors/InterceptionPoint.kt | 13 ++++ .../transformers/interceptors/Interceptor.kt | 78 ++++++++++++------- 10 files changed, 175 insertions(+), 58 deletions(-) create mode 100644 libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/transformers/interceptors/InterceptionPoint.kt diff --git a/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/ktorServer/server.kt.vm b/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/ktorServer/server.kt.vm index a1f95fe073c..51ef81211f4 100644 --- a/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/ktorServer/server.kt.vm +++ b/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/ktorServer/server.kt.vm @@ -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) } \ No newline at end of file diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/SettingOwnerWithId.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/SettingOwnerWithId.kt index 9c0804c78fe..90ef1fc9993 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/SettingOwnerWithId.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/SettingOwnerWithId.kt @@ -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()}" } diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/IrsOwner.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/IrsOwner.kt index 6c3d5e8c7d4..3ddb6120736 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/IrsOwner.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/IrsOwner.kt @@ -14,7 +14,7 @@ fun I.withIrs(vararg irs: BuildSystemIR) = withReplacedIrs(irs = inline fun IrsOwner.irsOfType(): List = - irs.filterIsInstance() + irs.filterIsInstance() inline fun IrsOwner.irsOfTypeOrNull() = @@ -25,4 +25,4 @@ inline fun IrsOwner.firstIrOfType() = irs.firstOrNull { ir -> ir is T } as T? fun IrsOwner.freeIrs(): List = - irs.filterIsInstance() \ No newline at end of file + irs.filterIsInstance() diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/gradle/GradleIR.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/gradle/GradleIR.kt index f12e6d82552..6614cb7cf8c 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/gradle/GradleIR.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/ir/buildsystem/gradle/GradleIR.kt @@ -50,6 +50,22 @@ data class GradleCallIr( } } +data class GradleNewInstanceCall( + val name: String, + val parameters: List = 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, diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/plugins/templates/TemplatesPlugin.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/plugins/templates/TemplatesPlugin.kt index 2108f2b875d..c3b8dd3538e 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/plugins/templates/TemplatesPlugin.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/plugins/templates/TemplatesPlugin.kt @@ -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>( @@ -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 - }.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, Any> + ): TaskResult { + 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()!!.createFile(path, fileText) + }.sequenceIgnore() + } } \ No newline at end of file diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/KtorServerTemplate.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/KtorServerTemplate.kt index 52698768722..b3f229f34d4 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/KtorServerTemplate.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/KtorServerTemplate.kt @@ -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("Ktor Server", GenerationPhase.PROJECT_GENERATION) + val imports = InterceptionPoint("imports", emptyList()) + val routes = InterceptionPoint("routes", emptyList()) + val elements = InterceptionPoint("elements", emptyList()) + + override val interceptionPoints: List> = listOf(imports, routes, elements) override val settings: List> = listOf(serverEngine) } diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/SimpleJsClientTemplate.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/SimpleJsClientTemplate.kt index 4398ffb6a0c..bd8fb7b38ee 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/SimpleJsClientTemplate.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/SimpleJsClientTemplate.kt @@ -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 = buildList { + override fun createInterceptors(sourceset: SourcesetIR): List = buildList { +interceptTemplate(KtorServerTemplate()) { applicableIf { buildFileIR -> val tasks = buildFileIR.irsOfTypeOrNull() ?: 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()?.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" } } \ No newline at end of file diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/Template.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/Template.kt index 623f55e735b..b6cde62a351 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/Template.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/Template.kt @@ -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 open val settings: List> = emptyList() + open val interceptionPoints: List> = emptyList() open fun TaskRunningContext.getRequiredLibraries(sourceset: SourcesetIR): List = emptyList() @@ -79,16 +80,11 @@ abstract class Template : SettingsOwner { open fun TaskRunningContext.getFileTemplates(sourceset: SourcesetIR): List = emptyList() - open fun createInterceptors(sourceset: SourcesetIR): List = emptyList() + open fun createInterceptors(sourceset: SourcesetIR): List = emptyList() fun TaskRunningContext.applyToSourceset( - templateEngine: TemplateEngine, sourceset: SourcesetIR ): TaskResult { - val fileSystemService = service()!! - - 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 = + fun TaskRunningContext.settingsAsMap(sourceset: Sourceset): Map = 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 = when (template) { null -> TemplateApplicationResult.EMPTY.asSuccess() else -> with(template) { - applyToSourceset(templateEngine, sourceset) + applyToSourceset(sourceset) } } diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/transformers/interceptors/InterceptionPoint.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/transformers/interceptors/InterceptionPoint.kt new file mode 100644 index 00000000000..acb407a234e --- /dev/null +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/transformers/interceptors/InterceptionPoint.kt @@ -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(val name: String, val initialValue: T) + +data class InterceptionPointModifier( + val point: InterceptionPoint<@UnsafeVariance T>, + val modifier: (@UnsafeVariance T) -> T +) \ No newline at end of file diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/transformers/interceptors/Interceptor.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/transformers/interceptors/Interceptor.kt index e1f2f1cad3d..9801da9876e 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/transformers/interceptors/Interceptor.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/transformers/interceptors/Interceptor.kt @@ -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, Any> +typealias SourcesetInterceptionPointValues = Map + +data class TemplateInterceptionApplicationState( + val buildFileIR: BuildFileIR, + val sourcesetToSettings: SourcesetInterceptionPointValues +) + data class TemplateInterceptor( + val template: Template, val applicabilityCheckers: List>, - val buildFileTransformers: List> + val buildFileTransformers: List>, + val interceptionPointModifiers: List> ) : 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(private val template: T) { +fun List.applyAll(state: TemplateInterceptionApplicationState) = + fold(state) { currentState, interceptor -> + interceptor.applyTo(currentState) + } + +class TemplateInterceptorBuilder(val template: T) { private val buildFileTransformers = mutableListOf>() fun transformBuildFile(transform: TransformerFunction) { buildFileTransformers += transform @@ -51,20 +78,19 @@ class TemplateInterceptorBuilder(private val template: T) { applicabilityCheckers += checker } - init { - applicableIf { buildFile -> - buildFile.sourcesets.any { it.template?.id == template.id } - } + private val interceptionPointModifiers = mutableListOf>() + fun interceptAtPoint(point: InterceptionPoint, modifier: (T) -> T) { + interceptionPointModifiers.add(InterceptionPointModifier(point, modifier)) } - fun build() = TemplateInterceptor(applicabilityCheckers, buildFileTransformers) -} -fun List.fold() = - TemplateInterceptor( - flatMap { it.applicabilityCheckers }, - flatMap { it.buildFileTransformers } + fun build() = TemplateInterceptor( + template, + applicabilityCheckers, + buildFileTransformers, + interceptionPointModifiers ) +} fun interceptTemplate(template: T, builder: TemplateInterceptorBuilder.() -> Unit) =