diff --git a/build.gradle.kts b/build.gradle.kts index 979ac03..b40a82f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,7 @@ repositories { dependencies { implementation(libs.kaml) implementation(libs.kotlin.poet) + implementation(libs.swift.poet) testImplementation(kotlin("test")) testImplementation(libs.junit) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f0c1859..81f0b9e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ kotlin = "2.0.0" junit = "junit:junit:4.13.2" kaml = "com.charleskorn.kaml:kaml:0.60.0" kotlin-poet = "com.squareup:kotlinpoet:1.18.1" +swift-poet = "io.outfoxx:swiftpoet:1.6.5" [plugins] spotless = { id = "com.diffplug.spotless", version = "6.25.0" } serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/src/main/kotlin/com/tfandkusu/ga913yaml/KotlinGenerator.kt b/src/main/kotlin/com/tfandkusu/ga913yaml/KotlinGenerator.kt index 2cdb164..1e73714 100644 --- a/src/main/kotlin/com/tfandkusu/ga913yaml/KotlinGenerator.kt +++ b/src/main/kotlin/com/tfandkusu/ga913yaml/KotlinGenerator.kt @@ -156,6 +156,7 @@ object KotlinGenerator { private fun generateActionScreenClass(screen: Screen): TypeSpec = TypeSpec .objectBuilder(screen.className) + .addKdoc(screen.description) .apply { screen.actions.forEach { action -> addType( diff --git a/src/main/kotlin/com/tfandkusu/ga913yaml/Main.kt b/src/main/kotlin/com/tfandkusu/ga913yaml/Main.kt index 1e3a371..d7c0cf0 100644 --- a/src/main/kotlin/com/tfandkusu/ga913yaml/Main.kt +++ b/src/main/kotlin/com/tfandkusu/ga913yaml/Main.kt @@ -15,6 +15,7 @@ fun main(args: Array) { } "make" -> { val screens = YamlParser.parse() + SwiftGenerator.generate(screens) KotlinGenerator.generate(screens) } else -> println("Unknown command") diff --git a/src/main/kotlin/com/tfandkusu/ga913yaml/SwiftGenerator.kt b/src/main/kotlin/com/tfandkusu/ga913yaml/SwiftGenerator.kt new file mode 100644 index 0000000..8679996 --- /dev/null +++ b/src/main/kotlin/com/tfandkusu/ga913yaml/SwiftGenerator.kt @@ -0,0 +1,203 @@ +package com.tfandkusu.ga913yaml + +import com.tfandkusu.ga913yaml.model.Action +import com.tfandkusu.ga913yaml.model.ParameterType +import com.tfandkusu.ga913yaml.model.Screen +import io.outfoxx.swiftpoet.ANY +import io.outfoxx.swiftpoet.BOOL +import io.outfoxx.swiftpoet.DICTIONARY +import io.outfoxx.swiftpoet.DOUBLE +import io.outfoxx.swiftpoet.DeclaredTypeName +import io.outfoxx.swiftpoet.FLOAT +import io.outfoxx.swiftpoet.FileSpec +import io.outfoxx.swiftpoet.FunctionSpec +import io.outfoxx.swiftpoet.INT +import io.outfoxx.swiftpoet.INT64 +import io.outfoxx.swiftpoet.PropertySpec +import io.outfoxx.swiftpoet.STRING +import io.outfoxx.swiftpoet.TypeSpec +import io.outfoxx.swiftpoet.parameterizedBy +import java.io.File +import java.nio.file.Files +import java.nio.file.Paths + +object SwiftGenerator { + private const val ROOT_STRUCT = "AnalyticsEvent" + private const val DIRECTORY = "ga913-ios/Landmarks/Analytics" + private val SCREEN_PROTOCOL = DeclaredTypeName.typeName(".AnalyticsEventScreen") + private val ACTION_PROTOCOL = DeclaredTypeName.typeName(".AnalyticsEventAction") + private const val EVENT_NAME_PROPERTY = "eventName" + private const val EVENT_PARAMETERS_PROPERTY = "eventParameters" + private const val IS_CONVERSION_EVENT_PROPERTY = "isConversionEvent" + + fun generate(screens: List) { + val fileSpec = + FileSpec + .builder(ROOT_STRUCT) + .addComment("https://github.com/tfandkusu/ga913-yaml/ による自動生成コードです。編集しないでください。") + .addType( + generateAnalyticsEventScreenProtocol(), + ).addType( + generateAnalyticsEventActionProtocol(), + ).addType( + TypeSpec + .structBuilder(ROOT_STRUCT) + .addDoc("Analytics イベント構造体群") + .addType( + generateScreenStruct(screens), + ).addType( + generateActionStruct(screens), + ).build(), + ).build() + Files.createDirectories(Paths.get(DIRECTORY)) + File("$DIRECTORY/$ROOT_STRUCT.swift").writeText(fileSpec.toString()) + } + + private fun generateAnalyticsEventScreenProtocol(): TypeSpec = + TypeSpec + .protocolBuilder(SCREEN_PROTOCOL) + .addDoc("画面遷移イベントのプロトコル") + .addProperty( + PropertySpec + .abstractBuilder(EVENT_NAME_PROPERTY, STRING) + .abstractGetter() + .addDoc("Analytics イベント名") + .build(), + ).addProperty( + PropertySpec + .abstractBuilder(IS_CONVERSION_EVENT_PROPERTY, BOOL) + .abstractGetter() + .addDoc("コンバージョンイベントフラグ") + .build(), + ).build() + + private fun generateAnalyticsEventActionProtocol(): TypeSpec = + TypeSpec + .protocolBuilder(ACTION_PROTOCOL) + .addDoc("画面内操作イベントのプロトコル") + .addProperty( + PropertySpec + .abstractBuilder(EVENT_NAME_PROPERTY, STRING) + .abstractGetter() + .addDoc("Analytics イベント名") + .build(), + ).addProperty( + PropertySpec + .abstractBuilder(EVENT_PARAMETERS_PROPERTY, DICTIONARY.parameterizedBy(STRING, ANY)) + .abstractGetter() + .addDoc("Analytics イベントパラメータ") + .build(), + ).addProperty( + PropertySpec + .abstractBuilder(IS_CONVERSION_EVENT_PROPERTY, BOOL) + .abstractGetter() + .addDoc("コンバージョンイベントフラグ") + .build(), + ).build() + + private fun generateScreenStruct(screens: List): TypeSpec = + TypeSpec + .structBuilder("Screen") + .addDoc("画面遷移イベント構造体群") + .apply { + screens.forEach { screen -> + addType( + generateScreenStruct(screen), + ) + } + }.build() + + private fun generateScreenStruct(screen: Screen): TypeSpec = + TypeSpec + .structBuilder(screen.className) + .addDoc(screen.description) + .addSuperType(SCREEN_PROTOCOL) + .addProperty( + PropertySpec + .builder(EVENT_NAME_PROPERTY, STRING) + .initializer("%S", screen.eventName) + .build(), + ).addProperty( + PropertySpec + .builder(IS_CONVERSION_EVENT_PROPERTY, BOOL) + .initializer("%L", screen.isConversionEvent) + .build(), + ).build() + + private fun generateActionStruct(screens: List): TypeSpec = + TypeSpec + .structBuilder("Action") + .addDoc("画面内操作イベント構造体群") + .apply { + screens.forEach { screen -> + addType( + generateActionScreenStruct(screen), + ) + } + }.build() + + private fun generateActionScreenStruct(screen: Screen): TypeSpec = + TypeSpec + .structBuilder(screen.className) + .addDoc(screen.description) + .apply { + screen.actions.forEach { action -> + addType(generateActionStruct(screenEventName = screen.eventName, action = action)) + } + }.build() + + private fun generateActionStruct( + screenEventName: String, + action: Action, + ): TypeSpec = + TypeSpec + .structBuilder(action.className) + .addDoc(action.description) + .addSuperType(ACTION_PROTOCOL) + .addFunction( + FunctionSpec + .constructorBuilder() + .apply { + action.parameters.forEach { parameter -> + addParameter(parameter.propertyName, toDeclaredTypeNames(parameter.type)) + } + if (action.parameters.isEmpty()) { + addStatement("eventParameters = [:]") + } else { + addStatement("eventParameters = [") + action.parameters.forEach { parameter -> + addStatement( + " %S: %L,", + parameter.eventParameterKey, + parameter.propertyName, + ) + } + addStatement("]") + } + }.build(), + ).addProperty( + PropertySpec + .builder(EVENT_NAME_PROPERTY, STRING) + .initializer("%S", screenEventName + action.eventName) + .build(), + ).addProperty( + PropertySpec + .builder(EVENT_PARAMETERS_PROPERTY, DICTIONARY.parameterizedBy(STRING, ANY)) + .build(), + ).addProperty( + PropertySpec + .builder(IS_CONVERSION_EVENT_PROPERTY, BOOL) + .initializer("%L", action.isConversionEvent) + .build(), + ).build() + + private fun toDeclaredTypeNames(parameterType: ParameterType): DeclaredTypeName = + when (parameterType) { + ParameterType.STRING -> STRING + ParameterType.INT -> INT + ParameterType.LONG -> INT64 + ParameterType.FLOAT -> FLOAT + ParameterType.DOUBLE -> DOUBLE + ParameterType.BOOLEAN -> BOOL + } +}