diff --git a/gallery-processor/.gitignore b/gallery-processor/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/gallery-processor/build.gradle.kts b/gallery-processor/build.gradle.kts new file mode 100644 index 00000000..0e0342fb --- /dev/null +++ b/gallery-processor/build.gradle.kts @@ -0,0 +1,22 @@ +import com.konyaco.fluent.plugin.build.BuildConfig + +plugins { + alias(libs.plugins.kotlin.multiplatform) +} + +group = BuildConfig.group +version = "gallery-processor" + +kotlin { + jvm() + sourceSets { + val jvmMain by getting { + dependencies { + implementation(libs.squareup.kotlinpoet) + implementation("com.google.devtools.ksp:symbol-processing-api:${libs.versions.ksp.get()}") + implementation("com.google.devtools.ksp:symbol-processing:${libs.versions.ksp.get()}") + implementation(kotlin("compiler")) + } + } + } +} \ No newline at end of file diff --git a/gallery-processor/src/jvmMain/kotlin/com/konyaco/fluent/gallery/processor/SampleCodeProcessor.kt b/gallery-processor/src/jvmMain/kotlin/com/konyaco/fluent/gallery/processor/SampleCodeProcessor.kt new file mode 100644 index 00000000..efed6612 --- /dev/null +++ b/gallery-processor/src/jvmMain/kotlin/com/konyaco/fluent/gallery/processor/SampleCodeProcessor.kt @@ -0,0 +1,80 @@ +package com.konyaco.fluent.gallery.processor + +import com.google.devtools.ksp.processing.* +import com.google.devtools.ksp.symbol.* +import com.google.devtools.ksp.symbol.impl.kotlin.KSFunctionDeclarationImpl +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.PropertySpec +import java.io.OutputStreamWriter +import java.nio.charset.StandardCharsets + +class SampleCodeProcessor(private val logger: KSPLogger, private val codeGenerator: CodeGenerator) : SymbolProcessor { + + private val annotationPackage = "com.konyaco.fluent.gallery.annotation" + private val sampleAnnotation = "Sample" + + private val sampleCodeFunctions = mutableMapOf>() + + private val visitor = FindFunctionVisitor { + if (it.annotations.any { annotation -> + annotation.shortName.asString() == sampleAnnotation && + annotation.annotationType.resolve().declaration.packageName.asString() == annotationPackage + } + ) { + val list = sampleCodeFunctions[it.packageName.asString()] ?: mutableListOf().apply { + sampleCodeFunctions[it.packageName.asString()] = this + } + list.add(it) + } + } + + override fun process(resolver: Resolver): List { + resolver.getAllFiles().forEach { it.accept(visitor, Unit) } + return emptyList() + } + + override fun finish() { + super.finish() + val fileName = "_SampleCodeString" + sampleCodeFunctions.forEach { (packageName, functions) -> + if (functions.isNotEmpty()) { + val sourceFile = FileSpec.builder(packageName, fileName) + val sourceFileList = mutableListOf() + functions.forEach { func -> + func.containingFile?.let { sourceFileList.add(it) } + if (func is KSFunctionDeclarationImpl) { + val funcName = func.simpleName.asString() + sourceFile.addProperty( + PropertySpec.builder( + "sourceCodeOf${funcName.first().uppercase()}${funcName.substring(1)}", + String::class + ) + .addModifiers(KModifier.INTERNAL) + .getter( + FunSpec.getterBuilder() + .addStatement("return %S", func.ktFunction.text) + .build() + ) + .build() + ) + } + } + val file = codeGenerator.createNewFile( + Dependencies(true, *(sourceFileList).toTypedArray()), + packageName, + fileName + ) + OutputStreamWriter(file, StandardCharsets.UTF_8).use(sourceFile.build()::writeTo) + + } + } + } +} + +class SampleCodeProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + return SampleCodeProcessor(environment.logger, environment.codeGenerator) + } +} \ No newline at end of file diff --git a/gallery-processor/src/jvmMain/kotlin/com/konyaco/fluent/gallery/processor/Visitor.kt b/gallery-processor/src/jvmMain/kotlin/com/konyaco/fluent/gallery/processor/Visitor.kt new file mode 100644 index 00000000..29275714 --- /dev/null +++ b/gallery-processor/src/jvmMain/kotlin/com/konyaco/fluent/gallery/processor/Visitor.kt @@ -0,0 +1,22 @@ +package com.konyaco.fluent.gallery.processor + +import com.google.devtools.ksp.getDeclaredFunctions +import com.google.devtools.ksp.symbol.* + +internal class FindFunctionVisitor( + private val onNode:(node: KSFunctionDeclaration) -> Unit +) : KSVisitorVoid() { + + override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) { + super.visitClassDeclaration(classDeclaration, data) + classDeclaration.getDeclaredFunctions().forEach { it.accept(this, Unit) } + } + + override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) { + onNode(function) + } + + override fun visitFile(file: KSFile, data: Unit) { + file.declarations.forEach { it.accept(this, Unit) } + } +} \ No newline at end of file diff --git a/gallery-processor/src/jvmMain/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/gallery-processor/src/jvmMain/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 00000000..844d0e42 --- /dev/null +++ b/gallery-processor/src/jvmMain/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +com.konyaco.fluent.gallery.processor.SampleCodeProcessorProvider \ No newline at end of file diff --git a/gallery/build.gradle.kts b/gallery/build.gradle.kts index 3a4c8bb1..d25d7e90 100644 --- a/gallery/build.gradle.kts +++ b/gallery/build.gradle.kts @@ -6,6 +6,7 @@ plugins { alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.compose) alias(libs.plugins.android.application) + id("com.google.devtools.ksp") version libs.versions.ksp.get() } kotlin { @@ -19,6 +20,7 @@ kotlin { implementation(project(":fluent-icons-extended")) implementation(compose("org.jetbrains.compose.ui:ui-util")) } + kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") } val commonTest by getting { dependencies { @@ -90,4 +92,17 @@ compose.desktop { packageVersion = "1.0.0" } } +} + +dependencies { + val processor = project(":gallery-processor") + add("kspCommonMainMetadata", processor) +} + +// workaround for KSP only in Common Main. +// https://github.com/google/ksp/issues/567 +tasks.withType>().all { + if (name != "kspCommonMainKotlinMetadata") { + dependsOn("kspCommonMainKotlinMetadata") + } } \ No newline at end of file diff --git a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/annotation/Sample.kt b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/annotation/Sample.kt new file mode 100644 index 00000000..14868117 --- /dev/null +++ b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/annotation/Sample.kt @@ -0,0 +1,4 @@ +package com.konyaco.fluent.gallery.annotation + +@Target(AnnotationTarget.FUNCTION) +annotation class Sample() diff --git a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/HomeScreen.kt b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/HomeScreen.kt index 355c3aba..d2f3f811 100644 --- a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/HomeScreen.kt +++ b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/HomeScreen.kt @@ -18,16 +18,19 @@ import com.konyaco.fluent.LocalContentColor import com.konyaco.fluent.background.Layer import com.konyaco.fluent.component.* import com.konyaco.fluent.gallery.LocalStore +import com.konyaco.fluent.gallery.annotation.Sample import com.konyaco.fluent.icons.Icons import com.konyaco.fluent.icons.regular.* - @Composable fun HomeScreen() { var displayDialog by remember { mutableStateOf(false) } val density = LocalDensity.current var scale by remember(density) { mutableStateOf(density.density) } val store = LocalStore.current + var showSourceCode by remember { + mutableStateOf(false) + } Layer( modifier = Modifier.padding(top = 16.dp).fillMaxSize() @@ -179,8 +182,27 @@ fun HomeScreen() { } } ) + Button( + onClick = { + showSourceCode = true + } + ) { + Text("Show source code of Buttons") + } } + Dialog( + visible = showSourceCode, + title = "Source Code of Buttons", + cancelButtonText = "Cancel", + confirmButtonText = "Confirm", + onConfirm = { showSourceCode = false }, + onCancel = { showSourceCode = false }, + content = { + Text(text = sourceCodeOfButtons, modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) + } + ) + Dialog( title = "This is an example dialog", visible = displayDialog, @@ -323,6 +345,7 @@ private fun Controls() { RadioButton(selectedRadio == 1, onClick = { selectedRadio = 1 }, label = "With Label") } +@Sample @Composable private fun Buttons() { var text by remember { mutableStateOf("Hello World") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 84599c0a..c2f37dae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] kotlin = "1.9.0" +ksp = "1.9.0-1.0.13" compose = "1.5.0" androidGradlePlugin = "7.4.2" androidBuildTools = "27.2.0-alpha16" diff --git a/settings.gradle.kts b/settings.gradle.kts index ad93f0b5..f8ded0b9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,3 +13,4 @@ includeBuild("build-plugin") include("fluent", "fluent-icons-core", "fluent-icons-extended") include("fluent-icons-generator") include("gallery") +include("gallery-processor")