Wizard: add simple js template
This commit is contained in:
+9
@@ -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)
|
||||
}
|
||||
+1
-1
@@ -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()}"
|
||||
}
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -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>()
|
||||
|
||||
+16
@@ -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,
|
||||
|
||||
+34
-7
@@ -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()
|
||||
}
|
||||
}
|
||||
+6
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
+34
-5
@@ -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"
|
||||
}
|
||||
}
|
||||
+8
-17
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
@@ -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
|
||||
)
|
||||
+52
-26
@@ -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) =
|
||||
|
||||
Reference in New Issue
Block a user