Skip to content

Commit

Permalink
Generate code to have an API that provides module level metadata of S…
Browse files Browse the repository at this point in the history
…howkase elements
  • Loading branch information
vinaygaba committed Jun 10, 2024
1 parent 5498f79 commit 1dbf112
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ import com.airbnb.android.showkase.processor.models.getShowkaseMetadataFromPrevi
import com.airbnb.android.showkase.processor.models.getShowkaseTypographyMetadata
import com.airbnb.android.showkase.processor.writer.PaparazziShowkaseScreenshotTestWriter
import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserProperties
import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserPropertyWriter
import com.airbnb.android.showkase.processor.writer.ShowkaseModuleBrowserPropertyWriter
import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter
import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Companion.CODEGEN_AUTOGEN_CLASS_NAME
import com.airbnb.android.showkase.processor.writer.ShowkaseCodegenMetadataWriter
import com.airbnb.android.showkase.processor.writer.ShowkaseModuleCodegenMetadataWriter
import com.airbnb.android.showkase.processor.writer.ShowkaseExtensionFunctionsWriter
import com.airbnb.android.showkase.processor.writer.ShowkaseModuleMetadataWriter
import com.airbnb.android.showkase.processor.writer.ShowkaseScreenshotTestWriter
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
Expand Down Expand Up @@ -262,16 +263,17 @@ class ShowkaseProcessor @JvmOverloads constructor(
val aggregateMetadataList = componentMetadata + colorMetadata + typographyMetadata
if (aggregateMetadataList.isEmpty()) return ShowkaseBrowserProperties()

ShowkaseCodegenMetadataWriter(environment).apply {
ShowkaseModuleCodegenMetadataWriter(environment).apply {
generateShowkaseCodegenFunctions(aggregateMetadataList)
}
ShowkaseBrowserPropertyWriter(environment).apply {
ShowkaseModuleBrowserPropertyWriter(environment).apply {
return generateMetadataPropertyFiles(
componentMetadata = componentMetadata,
colorMetadata = colorMetadata,
typographyMetadata = typographyMetadata,
)
}

}

private fun Collection<ShowkaseMetadata.Component>.dedupeAndSort() = this.distinctBy {
Expand Down Expand Up @@ -354,6 +356,16 @@ class ShowkaseProcessor @JvmOverloads constructor(
val currentShowkaseBrowserProperties =
writeMetadataFile(componentMetadata, colorMetadata, typographyMetadata)

ShowkaseModuleMetadataWriter.generateModuleLevelShowkaseProvider(
environment = environment,
moduleShowkaseBrowserProperties = currentShowkaseBrowserProperties
)

ShowkaseModuleMetadataWriter.generateModuleMetadataPublicApi(
environment = environment,
moduleShowkaseBrowserProperties = currentShowkaseBrowserProperties
)

if (rootElement != null) {
// This is the module that should aggregate all the other metadata files and
// also use the showkaseMetadata set from the current round to write the final file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,80 +71,6 @@ internal class ShowkaseBrowserWriter(private val environment: XProcessingEnv) {
)
}

private fun initializeComponentCodeBlock(
withoutParameterPropertyNames: List<ShowkaseGeneratedMetadata>,
withParameterPropertyNames: List<ShowkaseGeneratedMetadata>,
): CodeBlock {
val componentListInitializerCodeBlock = if (withParameterPropertyNames.isNotEmpty()) {
SHOWKASE_BROWSER_COMPONENT_CLASS_NAME.mutableListInitializerCodeBlock()
} else {
SHOWKASE_BROWSER_COMPONENT_CLASS_NAME.listInitializerCodeBlock()
}

componentListInitializerCodeBlock.apply {
addLineBreak()
withoutParameterPropertyNames.forEachIndexed { index, metadata ->
add("%M,", MemberName(metadata.propertyPackage, metadata.propertyName))
addLineBreak()
}
doubleUnindent()
add(")")

if (withParameterPropertyNames.isNotEmpty()) {
add(".apply {")
addLineBreak()
withDoubleIndent {
withParameterPropertyNames.forEachIndexed { index, metadata ->
add(
"addAll(%M)",
MemberName(metadata.propertyPackage, metadata.propertyName)
)
if (index != withParameterPropertyNames.lastIndex) {
addLineBreak()
}
}
}
closeCurlyBraces()
}
}

return componentListInitializerCodeBlock.build()
}

private fun initializeColorCodeBlock(
colorsParameterPropertyNames: List<ShowkaseGeneratedMetadata>,
): CodeBlock {
val colorListInitializerCodeBlock =
SHOWKASE_BROWSER_COLOR_CLASS_NAME.listInitializerCodeBlock()

return colorListInitializerCodeBlock.apply {
addLineBreak()
colorsParameterPropertyNames.forEachIndexed { index, metadata ->
add("%M,", MemberName(metadata.propertyPackage, metadata.propertyName))
addLineBreak()
}
doubleUnindent()
add(")")
}.build()
}

private fun initializeTypographyCodeBlock(
typographyParameterPropertyNames: List<ShowkaseGeneratedMetadata>,
): CodeBlock {
val typographyListInitializerCodeBlock =
SHOWKASE_BROWSER_TYPOGRAPHY_CLASS_NAME.listInitializerCodeBlock()

return typographyListInitializerCodeBlock.apply {
addLineBreak()
typographyParameterPropertyNames.forEachIndexed { index, metadata ->
add("%M,", MemberName(metadata.propertyPackage, metadata.propertyName))
addLineBreak()
}
doubleUnindent()
add(")")
}.build()
}

private fun initializeShowkaseRootCodegenAnnotation(
numComponentsWithoutPreviewParameter: Int,
numComponentsWithPreviewParameter: Int,
Expand Down Expand Up @@ -203,9 +129,9 @@ internal class ShowkaseBrowserWriter(private val environment: XProcessingEnv) {

companion object {
internal const val CODEGEN_AUTOGEN_CLASS_NAME = "Codegen"
private const val COMPONENT_INTERFACE_METHOD_NAME = "getShowkaseComponents"
private const val COLOR_INTERFACE_METHOD_NAME = "getShowkaseColors"
private const val TYPOGRAPHY_INTERFACE_METHOD_NAME = "getShowkaseTypography"
internal const val COMPONENT_INTERFACE_METHOD_NAME = "getShowkaseComponents"
internal const val COLOR_INTERFACE_METHOD_NAME = "getShowkaseColors"
internal const val TYPOGRAPHY_INTERFACE_METHOD_NAME = "getShowkaseTypography"
internal const val SHOWKASE_MODELS_PACKAGE_NAME = "com.airbnb.android.showkase.models"
internal const val COMPONENT_PROPERTY_NAME = "componentList"
internal const val COLOR_PROPERTY_NAME = "colorList"
Expand All @@ -220,5 +146,79 @@ internal class ShowkaseBrowserWriter(private val environment: XProcessingEnv) {
ClassName(SHOWKASE_MODELS_PACKAGE_NAME, "ShowkaseBrowserTypography")
internal val SHOWKASE_PROVIDER_CLASS_NAME =
ClassName(SHOWKASE_MODELS_PACKAGE_NAME, "ShowkaseProvider")

internal fun initializeComponentCodeBlock(
withoutParameterPropertyNames: List<ShowkaseGeneratedMetadata>,
withParameterPropertyNames: List<ShowkaseGeneratedMetadata>,
): CodeBlock {
val componentListInitializerCodeBlock = if (withParameterPropertyNames.isNotEmpty()) {
SHOWKASE_BROWSER_COMPONENT_CLASS_NAME.mutableListInitializerCodeBlock()
} else {
SHOWKASE_BROWSER_COMPONENT_CLASS_NAME.listInitializerCodeBlock()
}

componentListInitializerCodeBlock.apply {
addLineBreak()
withoutParameterPropertyNames.forEachIndexed { index, metadata ->
add("%M,", MemberName(metadata.propertyPackage, metadata.propertyName))
addLineBreak()
}
doubleUnindent()
add(")")

if (withParameterPropertyNames.isNotEmpty()) {
add(".apply {")
addLineBreak()
withDoubleIndent {
withParameterPropertyNames.forEachIndexed { index, metadata ->
add(
"addAll(%M)",
MemberName(metadata.propertyPackage, metadata.propertyName)
)
if (index != withParameterPropertyNames.lastIndex) {
addLineBreak()
}
}
}
closeCurlyBraces()
}
}

return componentListInitializerCodeBlock.build()
}

internal fun initializeColorCodeBlock(
colorsParameterPropertyNames: List<ShowkaseGeneratedMetadata>,
): CodeBlock {
val colorListInitializerCodeBlock =
SHOWKASE_BROWSER_COLOR_CLASS_NAME.listInitializerCodeBlock()

return colorListInitializerCodeBlock.apply {
addLineBreak()
colorsParameterPropertyNames.forEachIndexed { index, metadata ->
add("%M,", MemberName(metadata.propertyPackage, metadata.propertyName))
addLineBreak()
}
doubleUnindent()
add(")")
}.build()
}

internal fun initializeTypographyCodeBlock(
typographyParameterPropertyNames: List<ShowkaseGeneratedMetadata>,
): CodeBlock {
val typographyListInitializerCodeBlock =
SHOWKASE_BROWSER_TYPOGRAPHY_CLASS_NAME.listInitializerCodeBlock()

return typographyListInitializerCodeBlock.apply {
addLineBreak()
typographyParameterPropertyNames.forEachIndexed { index, metadata ->
add("%M,", MemberName(metadata.propertyPackage, metadata.propertyName))
addLineBreak()
}
doubleUnindent()
add(")")
}.build()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ internal class ShowkaseExtensionFunctionsWriter(
companion object {
private const val SHOWKASE_ROOT_MODULE_KEY = "SHOWKASE_ROOT_MODULE"
private const val INTENT_FUNCTION_NAME = "getBrowserIntent"
private const val METADATA_FUNCTION_NAME = "getMetadata"
internal const val METADATA_FUNCTION_NAME = "getMetadata"
private const val SHOWKASE_EXTENSION_FUNCTIONS_NAME = "ShowkaseExtensionFunctions"
private const val SHOWKASE_METHODS_SUFFIX = "${SHOWKASE_EXTENSION_FUNCTIONS_NAME}Codegen"
private const val CONTEXT_PARAMETER_NAME = "context"
Expand All @@ -127,7 +127,7 @@ internal class ShowkaseExtensionFunctionsWriter(
ClassName(CONTEXT_PACKAGE_NAME, "Intent")
private val SHOWKASE_BROWSER_ACTIVITY_CLASS_NAME =
ClassName("com.airbnb.android.showkase.ui", "ShowkaseBrowserActivity")
private val SHOWKASE_ELEMENTS_METADATA_CLASS_NAME =
internal val SHOWKASE_ELEMENTS_METADATA_CLASS_NAME =
ClassName(SHOWKASE_MODELS_PACKAGE_NAME, "ShowkaseElementsMetadata")
internal val SHOWKASE_OBJECT_CLASS_NAME =
ClassName(SHOWKASE_MODELS_PACKAGE_NAME, "Showkase")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.asTypeName

class ShowkaseBrowserPropertyWriter(private val environment: XProcessingEnv) {
class ShowkaseModuleBrowserPropertyWriter(private val environment: XProcessingEnv) {
@Suppress("LongMethod")
internal fun generateMetadataPropertyFiles(
componentMetadata: Set<ShowkaseMetadata.Component>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@ import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.TypeSpec
import java.util.Locale

internal class ShowkaseCodegenMetadataWriter(private val environment: XProcessingEnv) {
internal class ShowkaseModuleCodegenMetadataWriter(private val environment: XProcessingEnv) {

internal fun generateShowkaseCodegenFunctions(
showkaseMetadataSet: Set<ShowkaseMetadata>,
) {
val moduleName = showkaseMetadataSet.first().packageName.replace(".", "_")
val generatedClassName = "ShowkaseMetadata_${moduleName.lowercase(Locale.getDefault())}"
val generatedClassName = "ShowkaseMetadata_${showkaseMetadataSet.getNormalizedPackageName()}"
val fileBuilder = FileSpec.builder(
CODEGEN_PACKAGE_NAME,
generatedClassName
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.airbnb.android.showkase.processor.writer

import androidx.room.compiler.processing.XFiler
import androidx.room.compiler.processing.XProcessingEnv
import androidx.room.compiler.processing.addOriginatingElement
import androidx.room.compiler.processing.writeTo
import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Companion.SHOWKASE_PROVIDER_CLASS_NAME
import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Companion.initializeColorCodeBlock
import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Companion.initializeComponentCodeBlock
import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Companion.initializeTypographyCodeBlock
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.LIST
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy

object ShowkaseModuleMetadataWriter {
@Suppress("LongMethod", "LongParameterList")
internal fun generateModuleLevelShowkaseProvider(
environment: XProcessingEnv,
moduleShowkaseBrowserProperties: ShowkaseBrowserProperties,
) {
if (moduleShowkaseBrowserProperties.isEmpty()) return
val packageName = moduleShowkaseBrowserProperties.getPackageName()
val showkaseComponentsListClassName =
"${MODULE_LEVEL_SHOWKASE_PROVIDER_CLASS_PREFIX}_${packageName.normalizePackageName()}"
val fileBuilder = getFileBuilder(packageName, showkaseComponentsListClassName)
val componentCodeBlock = initializeComponentCodeBlock(
moduleShowkaseBrowserProperties.componentsWithoutPreviewParameters,
moduleShowkaseBrowserProperties.componentsWithPreviewParameters
)
val colorCodeBlock = initializeColorCodeBlock(moduleShowkaseBrowserProperties.colors)
val typographyCodeBlock =
initializeTypographyCodeBlock(moduleShowkaseBrowserProperties.typography)
writeFile(
environment,
fileBuilder,
ShowkaseBrowserWriter.SHOWKASE_PROVIDER_CLASS_NAME,
showkaseComponentsListClassName,
moduleShowkaseBrowserProperties,
getShowkaseProviderInterfaceFunction(
methodName = ShowkaseBrowserWriter.COMPONENT_INTERFACE_METHOD_NAME,
returnType = LIST.parameterizedBy(ShowkaseBrowserWriter.SHOWKASE_BROWSER_COMPONENT_CLASS_NAME),
codeBlock = componentCodeBlock
),
getShowkaseProviderInterfaceFunction(
methodName = ShowkaseBrowserWriter.COLOR_INTERFACE_METHOD_NAME,
returnType = LIST.parameterizedBy(ShowkaseBrowserWriter.SHOWKASE_BROWSER_COLOR_CLASS_NAME),
codeBlock = colorCodeBlock
),
getShowkaseProviderInterfaceFunction(
methodName = ShowkaseBrowserWriter.TYPOGRAPHY_INTERFACE_METHOD_NAME,
returnType = LIST.parameterizedBy(ShowkaseBrowserWriter.SHOWKASE_BROWSER_TYPOGRAPHY_CLASS_NAME),
codeBlock = typographyCodeBlock
),
showkaseRootCodegenAnnotation = null
)
}

internal fun generateModuleMetadataPublicApi(
environment: XProcessingEnv,
moduleShowkaseBrowserProperties: ShowkaseBrowserProperties,
) {
if (moduleShowkaseBrowserProperties.isEmpty()) return
val packageName = moduleShowkaseBrowserProperties.getPackageName()
val normalizedPackageName = packageName.normalizePackageName()
val showkaseComponentsListClassName =
"${MODULE_LEVEL_SHOWKASE_PROVIDER_CLASS_PREFIX}ExtensionFunctions_${normalizedPackageName}"
val fileBuilder = getFileBuilder(packageName, showkaseComponentsListClassName)

fileBuilder
.addFileComment("This is an auto-generated file. Please do not edit/modify this file.")
.addFunction(
FunSpec.builder(MODULE_METADATA_FUNCTION_NAME).apply {
receiver(ShowkaseExtensionFunctionsWriter.SHOWKASE_OBJECT_CLASS_NAME)
returns(ShowkaseExtensionFunctionsWriter.SHOWKASE_ELEMENTS_METADATA_CLASS_NAME)
addKdoc(
"Helper function that gives you access to Showkase elements that are declared in a given module. " +
"This contains data about the composables, colors and typography that are meant to be rendered" +
"inside the Showkase browser. This is different from the Showkase.${ShowkaseExtensionFunctionsWriter.METADATA_FUNCTION_NAME}() " +
"function, which contains all the Showkase elements in a given ShowkaseRoot graph, whereas this function " +
"only contains metadata about the module it's generated in. Each module where Showkase is setup " +
"will have this function generated in it."
)
addCode(
CodeBlock.Builder()
.indent()
.addStatement(
"return (%T as %T).metadata()",
ClassName(
packageName,
"${MODULE_LEVEL_SHOWKASE_PROVIDER_CLASS_PREFIX}_${normalizedPackageName}"
),
SHOWKASE_PROVIDER_CLASS_NAME
)
.unindent()
.build()
)
moduleShowkaseBrowserProperties.zip()
.forEach { addOriginatingElement(it.element) }
}
.build()
)
.build()
.writeTo(environment.filer, mode = XFiler.Mode.Aggregating)
}

private const val MODULE_METADATA_FUNCTION_NAME = "getModuleMetadata"
private const val MODULE_LEVEL_SHOWKASE_PROVIDER_CLASS_PREFIX = "ShowkaseModuleMetadata"
}
Loading

0 comments on commit 1dbf112

Please sign in to comment.