Skip to content

Commit

Permalink
Merge pull request #5 from trafi/multi_projects_suport
Browse files Browse the repository at this point in the history
Support customization of output class name and schema metadata inclusion
  • Loading branch information
justasm authored Aug 31, 2023
2 parents 37d87ac + 0b6a2b0 commit 0f76634
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 47 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
}

group = "com.trafi"
version = "2.1"
version = "2.2"

dependencies {
implementation(libs.kotlin.stdlib)
Expand Down
6 changes: 5 additions & 1 deletion src/main/kotlin/Mammoth.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.trafi.mammoth.CodeGenerator
import com.trafi.mammoth.Schema
Expand All @@ -17,6 +18,9 @@ class Mammoth : CliktCommand() {
private val version: String by option(help = "Mammoth schema version").default("")
private val outputPath: String by option(help = "Generated schema code output path").default("")
private val outputFilename: String by option(help = "Generated schema code output file name").default("MammothEvents.kt")
private val outputClassName: String by option(help = "Generated schema code output class name").default("AnalyticsEvent")
private val includeSchemaMetadata: Boolean by option(help = "Include schema version & event id in generated event parameters")
.flag("--no-schema-metadata", default = true, defaultForHelp = "include")

override fun run() {
try {
Expand All @@ -37,7 +41,7 @@ class Mammoth : CliktCommand() {
val schema = json.decodeFromString<Schema>(schemaJsonString)

echo(if (business) "Generating code" else "Cooking kotlet")
val code = CodeGenerator.generateCode(schema)
val code = CodeGenerator.generateCode(schema, outputClassName, includeSchemaMetadata)

val file = File(outputPath).resolve(outputFilename)
echo(if (business) "Writing generated code to $file" else "Serving hot kotlet at $file")
Expand Down
84 changes: 53 additions & 31 deletions src/main/kotlin/com/trafi/mammoth/CodeGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,33 @@ object CodeGenerator {
private val rawEventClass = ClassName(packageName, "RawEvent")
private val schemaVersion = MemberName(packageName, schemaVersionPropertyName)

fun generateCode(schema: Schema): String {
val file = FileSpec.builder(packageName, "AnalyticsEvent")
fun generateCode(schema: Schema, className: String, includeSchemaMetadata: Boolean): String {
val file = FileSpec.builder(packageName, className)
.indent(" ")
.addFileComment("%L schema version %L\n", schema.projectId, schema.versionNumber)
.addFileComment("Generated with https://github.com/trafi/mammoth-kt\nDo not edit manually.")
.addProperty(
PropertySpec
.builder(
schemaVersionPropertyName,
String::class,
KModifier.PRIVATE,
KModifier.CONST
.apply {
if (includeSchemaMetadata) {
addProperty(
PropertySpec
.builder(
schemaVersionPropertyName,
String::class,
KModifier.PRIVATE,
KModifier.CONST
)
.initializer("%S", schema.versionNumber)
.build()
)
.initializer("%S", schema.versionNumber)
.build()
)
}
}
.addType(
TypeSpec.objectBuilder("AnalyticsEvent")
.apply { schema.events.forEach { addFunction(generateEventFunction(it)) } }
TypeSpec.objectBuilder(className)
.apply {
schema.events.forEach {
addFunction(generateEventFunction(it, includeSchemaMetadata))
}
}
.build()
)
.apply { schema.types.forEach { generateType(it)?.let { typeSpec -> addType(typeSpec) } } }
Expand Down Expand Up @@ -80,7 +88,10 @@ object CodeGenerator {
}
}

private fun generateEventFunction(event: Schema.Event): FunSpec {
private fun generateEventFunction(
event: Schema.Event,
includeSchemaMetadata: Boolean,
): FunSpec {
return FunSpec.builder(event.nativeFunctionName)
.addKdoc(event.description)
.returns(eventClass)
Expand All @@ -102,10 +113,10 @@ object CodeGenerator {
.build()
}.sortedBy { it.defaultValue != null })
.addStatement(
"return %T(\n⇥business = %L,\npublish = %L,\nexplicitConsumerTags = %L⇤\n)",
"return %T(\n⇥business = %L,\npublish = %L,\nexplicitConsumerTags = %L,\n)",
eventClass,
generateBusinessEvent(event),
generatePublishEvent(event),
generateBusinessEvent(event, includeSchemaMetadata),
generatePublishEvent(event, includeSchemaMetadata) ?: "null",
generateSdkTags(event) ?: "null"
)
.build()
Expand All @@ -117,13 +128,17 @@ object CodeGenerator {
}
return CodeBlock.of(
"listOf(\n⇥%L⇤\n)",
sdkTags.joinToString(separator = ",\n") { "\"${it.name}\"" }
sdkTags.joinToString(separator = ",\n", postfix = ",") { "\"${it.name}\"" }
).takeIf { sdkTags.isNotEmpty() }
}

private fun generatePublishEvent(event: Schema.Event): CodeBlock {
private fun generatePublishEvent(
event: Schema.Event,
includeSchemaMetadata: Boolean,
): CodeBlock? {
val publishName = event.publishName ?: return null
return generateRawEvent(
name = event.publishName,
name = publishName,
parameterCodeBlocks = event.publishValues.map {
CodeBlock.of(
"%S to %S",
Expand All @@ -136,11 +151,14 @@ object CodeGenerator {
it.first,
it.second
)
}).plus(event.publishMetadataParameters)
}).let { if (includeSchemaMetadata) it.plus(event.publishMetadataParameters) else it }
)
}

private fun generateBusinessEvent(event: Schema.Event): CodeBlock {
private fun generateBusinessEvent(
event: Schema.Event,
includeSchemaMetadata: Boolean,
): CodeBlock {
return generateRawEvent(
name = event.name,
parameterCodeBlocks = event.businessValues.map {
Expand All @@ -155,19 +173,23 @@ object CodeGenerator {
it.first,
it.second
)
}).plus(event.businessMetadataParameters)
}).let { if (includeSchemaMetadata) it.plus(event.businessMetadataParameters) else it }
)
}

private fun generateRawEvent(name: String, parameterCodeBlocks: List<CodeBlock>): CodeBlock {
return CodeBlock.of(
"%T(\n⇥name = %S,\nparameters = %L⇤\n)",
"%T(\n⇥name = %S,\nparameters = %L,\n)",
rawEventClass,
name,
CodeBlock.of(
"mapOf(\n⇥%L⇤\n)",
parameterCodeBlocks.joinToCode(separator = ",\n")
)
if (parameterCodeBlocks.isEmpty()) {
CodeBlock.of("mapOf()")
} else {
CodeBlock.of(
"mapOf(\n⇥%L⇤\n)",
parameterCodeBlocks.joinToCode(separator = ",\n", suffix = ",")
)
}
)
}

Expand Down Expand Up @@ -212,11 +234,11 @@ private val Schema.Event.Parameter.nativeTypeName: TypeName
else -> ClassName(packageName, typeName.normalized)
}

private val Schema.Event.publishName: String
private val Schema.Event.publishName: String?
get() {
val eventTypeValue =
values.firstOrNull { it.parameter.name == Schema.Event.Parameter.eventTypeParameterName }
?: throw IllegalArgumentException("Event does not contain valid ${Schema.Event.Parameter.eventTypeParameterName} value")
?: return null
return eventTypeValue.stringEnumValue
?: throw IllegalArgumentException("${Schema.Event.Parameter.eventTypeParameterName} must have non-null value")
}
Expand Down
87 changes: 73 additions & 14 deletions src/test/kotlin/CodeGeneratorTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ internal class CodeGeneratorTests {
""".trimIndent()
val schema = json.decodeFromString<Schema>(schemaJsonString)

val code = CodeGenerator.generateCode(schema)
val code = CodeGenerator.generateCode(
schema,
className = "AnalyticsEvent",
includeSchemaMetadata = true,
)
assertEquals(
"""
// whitelabel schema version 1
Expand All @@ -72,17 +76,17 @@ internal class CodeGeneratorTests {
parameters = mapOf(
"event_type" to "screen_open",
"schema_event_id" to "0",
"schema_version" to mammothSchemaVersion
)
"schema_version" to mammothSchemaVersion,
),
),
publish = RawEvent(
name = "screen_open",
parameters = mapOf(
"achievement_id" to "0",
"score" to mammothSchemaVersion
)
"score" to mammothSchemaVersion,
),
),
explicitConsumerTags = null
explicitConsumerTags = null,
)
}
Expand All @@ -99,7 +103,7 @@ internal class CodeGeneratorTests {
}

@Test
fun `generates function with no parameters but with explicitConsumerTags`() {
fun `generates event with explicitConsumerTags`() {
val schemaJsonString = """
{
"projectId": "whitelabel",
Expand Down Expand Up @@ -149,7 +153,11 @@ internal class CodeGeneratorTests {
""".trimIndent()
val schema = json.decodeFromString<Schema>(schemaJsonString)

val code = CodeGenerator.generateCode(schema)
val code = CodeGenerator.generateCode(
schema,
className = "AnalyticsEvent",
includeSchemaMetadata = true,
)
assertEquals(
"""
// whitelabel schema version 1
Expand All @@ -171,20 +179,20 @@ internal class CodeGeneratorTests {
parameters = mapOf(
"event_type" to "screen_open",
"schema_event_id" to "0",
"schema_version" to mammothSchemaVersion
)
"schema_version" to mammothSchemaVersion,
),
),
publish = RawEvent(
name = "screen_open",
parameters = mapOf(
"achievement_id" to "0",
"score" to mammothSchemaVersion
)
"score" to mammothSchemaVersion,
),
),
explicitConsumerTags = listOf(
"Braze",
"Batch"
)
"Batch",
),
)
}
Expand All @@ -199,4 +207,55 @@ internal class CodeGeneratorTests {
""".trimIndent(), code
)
}

@Test
fun `generates function for bare-bones schema`() {
val schemaJsonString = """
{
"projectId": "some-project",
"versionNumber": 1,
"events": [
{
"id": 0,
"name": "SomeScreenOpen",
"description": "Some screen was opened",
"values": [],
"parameters": [],
"tags": []
}
],
"types": []
}
""".trimIndent()
val schema = json.decodeFromString<Schema>(schemaJsonString)

val code = CodeGenerator.generateCode(
schema,
className = "AnalyticsEvent",
includeSchemaMetadata = false,
)
assertEquals(
"""
// some-project schema version 1
// Generated with https://github.com/trafi/mammoth-kt
// Do not edit manually.
package com.trafi.analytics
public object AnalyticsEvent {
/**
* Some screen was opened
*/
public fun someScreenOpen(): Analytics.Event = Analytics.Event(
business = RawEvent(
name = "SomeScreenOpen",
parameters = mapOf(),
),
publish = null,
explicitConsumerTags = null,
)
}
""".trimIndent(), code
)
}
}

0 comments on commit 0f76634

Please sign in to comment.