Skip to content

Commit

Permalink
Merge pull request #273 from simple-robot/dev/intents-appender
Browse files Browse the repository at this point in the history
增加用于Intents的DSL API
  • Loading branch information
ForteScarlet authored Feb 10, 2025
2 parents 152599e + ed97a54 commit bd36762
Show file tree
Hide file tree
Showing 10 changed files with 330 additions and 71 deletions.
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/P.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ object P {
override val homepage: String get() = HOMEPAGE


const val VERSION = "4.1.3"
const val VERSION = "4.1.4"
const val NEXT_VERSION = "4.1.4"

override val snapshotVersion = "$NEXT_VERSION-SNAPSHOT"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024. ForteScarlet.
* Copyright (c) 2024-2025. ForteScarlet.
*
* This file is part of simbot-component-qq-guild.
*
Expand Down Expand Up @@ -28,6 +28,7 @@ import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.symbol.Modifier
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.jvm.jvmInline
import com.squareup.kotlinpoet.jvm.jvmName
import com.squareup.kotlinpoet.jvm.jvmStatic
import com.squareup.kotlinpoet.ksp.toClassName
Expand All @@ -44,6 +45,10 @@ private const val EVENT_INTENTS_PACKAGE = "love.forte.simbot.qguild.event"
private const val EVENT_INTENTS_CLASS_NAME = "EventIntents"
private val EventIntentsClassName = ClassName(EVENT_INTENTS_PACKAGE, EVENT_INTENTS_CLASS_NAME)

private const val INTENTS_APPENDER_PACKAGE = "love.forte.simbot.qguild.event"
private const val INTENTS_APPENDER_CLASS_NAME = "IntentsAppender"
private val IntentsAppenderClassName = ClassName(INTENTS_APPENDER_PACKAGE, INTENTS_APPENDER_CLASS_NAME)

private const val INTENTS_CONST_NAME = "INTENTS"

private const val INTENTS_PACKAGE = "love.forte.simbot.qguild.event"
Expand All @@ -64,6 +69,15 @@ private const val EVENT_NAME_BASED_MARKER_PKG = "love.forte.simbot.qguild.intern
internal class EventIntentsAggregationProcessor(
val environment: SymbolProcessorEnvironment
) : SymbolProcessor {
data class NamesToType(
val names: Set<String>,
val declaration: KSClassDeclaration,
val firstUpper: String,
val firstLower: String,
val snackUpper: String,
val snackLower: String,
)

private val processed = AtomicBoolean(false)

override fun process(resolver: Resolver): List<KSAnnotated> {
Expand All @@ -78,32 +92,69 @@ internal class EventIntentsAggregationProcessor(
.getSealedSubclasses()
.toList()

// val allSubObjects = resolver.getAllFiles()
// .filterIsInstance<KSClassDeclaration>()
// .flatMap { dec ->
// sequence {
// yield(dec)
// yieldAll(dec.getSealedSubclasses())
// }
// }
// .filter { declaration ->
// declaration.classKind == ClassKind.OBJECT &&
// declaration.asStarProjectedType()
// .isAssignableFrom(eventIntentsDeclaration.asStarProjectedType())
// }
// .toList()

environment.logger.info("Found sub object: $allSubObjects")

val aggregationBuilder = TypeSpec.objectBuilder(AGGREGATION_OBJ_NAME)

generateAll(aggregationBuilder, allSubObjects)
generateGetByName(aggregationBuilder, allSubObjects)

// name -> type
val nameToTypes = allSubObjects.map { declaration ->
val nameBasedAnnotation = declaration.annotations
.firstOrNull {
(it.annotationType.resolve().declaration as? KSClassDeclaration)?.let { annoDecl ->
annoDecl.simpleName.asString() == EVENT_NAME_BASED_MARKER_NAME
&& annoDecl.packageName.asString() == EVENT_NAME_BASED_MARKER_PKG
} == true
}

fun baseName(): String = declaration.simpleName.asString()

fun findFromAnnotation(name: String): String? =
nameBasedAnnotation?.arguments?.firstOrNull {
it.name?.asString() == name
}?.value as? String?

val firstUpper = findFromAnnotation("firstUpper")
?.takeUnless { it.isBlank() }
?: baseName()
val firstLower = findFromAnnotation("firstLower")
?.takeUnless { it.isBlank() }
?: baseName().replaceFirstChar(Char::lowercaseChar)
val snackUpper = findFromAnnotation("snackUpper")
?.takeUnless { it.isBlank() }
?: baseName().toSnack(true)
val snackLower = findFromAnnotation("snackLower")
?.takeUnless { it.isBlank() }
?: baseName().toSnack(false)


val set = setOf(
firstUpper,
firstLower,
snackUpper,
snackLower,
)

NamesToType(
set,
declaration,
firstUpper,
firstLower,
snackUpper,
snackLower
)
}

generateGetByName(aggregationBuilder, nameToTypes)

val intentsAppenderOpTypeSpec = generateIntentsAppenderOp(nameToTypes)

val fileBuilder = FileSpec.builder(OUTPUT_PACKAGE, AGGREGATION_FILE_NAME)
fileBuilder.addType(aggregationBuilder.build())
fileBuilder.addType(intentsAppenderOpTypeSpec)
fileBuilder.addFileComment(
"本文件内容为自动生成,生成于 %L",
"\n本文件内容为自动生成,生成于 %L\n",
OffsetDateTime.now(ZoneOffset.ofHours(8)).toString()
)

Expand Down Expand Up @@ -196,53 +247,12 @@ internal class EventIntentsAggregationProcessor(
builder.addFunction(allIntentsFunc.build())
}


/**
* 生成根据名称获取结果的 `getByName(name: String)`,
* 名称支持驼峰、全大写和全小写。
*/
private fun generateGetByName(builder: TypeSpec.Builder, list: List<KSClassDeclaration>) {
data class NamesToType(val names: Set<String>, val declaration: KSClassDeclaration)

val nameToTypes = list.map { declaration ->
val nameBasedAnnotation = declaration.annotations
.firstOrNull {
(it.annotationType.resolve().declaration as? KSClassDeclaration)?.let { annoDecl ->
annoDecl.simpleName.asString() == EVENT_NAME_BASED_MARKER_NAME
&& annoDecl.packageName.asString() == EVENT_NAME_BASED_MARKER_PKG
} ?: false
}

fun baseName(): String = declaration.simpleName.asString()

fun findFromAnnotation(name: String): String? =
nameBasedAnnotation?.arguments?.firstOrNull {
it.name?.asString() == name
}?.value as? String?

val firstUpper = findFromAnnotation("firstUpper")
?.takeUnless { it.isBlank() }
?: baseName()
val firstLower = findFromAnnotation("firstLower")
?.takeUnless { it.isBlank() }
?: baseName().replaceFirstChar(Char::lowercaseChar)
val snackUpper = findFromAnnotation("snackUpper")
?.takeUnless { it.isBlank() }
?: baseName().toSnack(true)
val snackLower = findFromAnnotation("snackLower")
?.takeUnless { it.isBlank() }
?: baseName().toSnack(false)


val set = setOf(
firstUpper,
firstLower,
snackUpper,
snackLower,
)

NamesToType(set, declaration)
}

private fun generateGetByName(builder: TypeSpec.Builder, nameToTypes: List<NamesToType>) {
val doc = CodeBlock.builder().apply {
addStatement("使用简单的字符串名称来获取一个对应的 [%T] 子类型的 intents 值,", EventIntentsClassName)
addStatement("字符串名称与这个类型的简单类型相关:类名的名称,以及对应的snack(下滑线)格式。")
Expand Down Expand Up @@ -295,6 +305,45 @@ internal class EventIntentsAggregationProcessor(
}


/**
* 生成 `IntentsAppenderOp`
*
* ```kotlin
* @JvmInline
* value class IntentsAppenderOp (private val appender: IntentsAppender) {
* fun guilds() { appender.appendIntents(EventIntents.Guilds.intents) }
* fun groupAndC2C() { ... }
* // ...
* }
* ```
*/
private fun generateIntentsAppenderOp(nameToTypes: List<NamesToType>): TypeSpec {
val builder = TypeSpec.classBuilder("IntentsAppenderOp")
.addModifiers(KModifier.PUBLIC, KModifier.VALUE)
.jvmInline()
.primaryConstructor(
FunSpec.constructorBuilder().apply {
addParameter("appender", IntentsAppenderClassName)
}.build()
)
.addProperty(
PropertySpec.builder("appender", IntentsAppenderClassName, KModifier.PRIVATE)
.initializer("appender")
.build()
)

nameToTypes.forEach { nameToType ->
builder.addFunction(FunSpec.builder(nameToType.firstLower).apply {
addModifiers(KModifier.PUBLIC)
addCode(
"appender.appendIntents(%T.intents)",
nameToType.declaration.asStarProjectedType().toClassName()
)
}.build())
}

return builder.build()
}
}

private fun String.toSnack(allUpper: Boolean): String = buildString(length) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3640,6 +3640,44 @@ public final class love/forte/simbot/qguild/event/Intents$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public abstract interface class love/forte/simbot/qguild/event/IntentsAppender {
public abstract fun appendIntents-NLurJl8 (I)V
}

public final class love/forte/simbot/qguild/event/IntentsAppenderKt {
public static final fun intents (Llove/forte/simbot/qguild/event/IntentsAppender;Lkotlin/jvm/functions/Function1;)V
}

public final class love/forte/simbot/qguild/event/IntentsAppenderOp {
public static final fun audioAction-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)V
public static final fun audioOrLiveChannelMember-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)V
public static final synthetic fun box-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)Llove/forte/simbot/qguild/event/IntentsAppenderOp;
public static fun constructor-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)Llove/forte/simbot/qguild/event/IntentsAppender;
public static final fun directMessage-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)V
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Llove/forte/simbot/qguild/event/IntentsAppender;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Llove/forte/simbot/qguild/event/IntentsAppender;Llove/forte/simbot/qguild/event/IntentsAppender;)Z
public static final fun forumsEvent-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)V
public static final fun groupAndC2CEvent-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)V
public static final fun guildMembers-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)V
public static final fun guildMessageReactions-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)V
public static final fun guildMessages-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)V
public static final fun guilds-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)V
public fun hashCode ()I
public static fun hashCode-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)I
public static final fun interaction-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)V
public static final fun messageAudit-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)V
public static final fun openForumsEvent-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)V
public static final fun publicGuildMessages-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)V
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Llove/forte/simbot/qguild/event/IntentsAppender;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Llove/forte/simbot/qguild/event/IntentsAppender;
}

public final class love/forte/simbot/qguild/event/IntentsUtil {
public static final fun Intents (Lkotlin/jvm/functions/Function1;)I
}

public final class love/forte/simbot/qguild/event/MessageAuditPass : love/forte/simbot/qguild/event/MessageAuditedDispatch {
public static final field Companion Llove/forte/simbot/qguild/event/MessageAuditPass$Companion;
public fun <init> (Ljava/lang/String;JLlove/forte/simbot/qguild/model/MessageAudited;)V
Expand Down Expand Up @@ -4694,6 +4732,13 @@ public final class love/forte/simbot/qguild/event/SignalKt {
public static synthetic fun resolveDispatchSerializer$default (Lkotlinx/serialization/json/JsonObject;ZILjava/lang/Object;)Lkotlinx/serialization/KSerializer;
}

public final class love/forte/simbot/qguild/event/SimpleIntentsAppender : love/forte/simbot/qguild/event/IntentsAppender {
public fun <init> ()V
public fun appendIntents-NLurJl8 (I)V
public final fun getIntents-DNrqdk0 ()I
public final fun setIntents-NLurJl8 (I)V
}

public final class love/forte/simbot/qguild/message/ArkBuilder {
public fun <init> (Ljava/lang/String;)V
public final fun build ()Llove/forte/simbot/qguild/model/Message$Ark;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2024. ForteScarlet.
* Copyright (c) 2022-2025. ForteScarlet.
*
* This file is part of simbot-component-qq-guild.
*
Expand All @@ -20,7 +20,6 @@ package love.forte.simbot.qguild.event
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import love.forte.simbot.qguild.PrivateDomainOnly
import love.forte.simbot.qguild.event.EventIntents.*
import love.forte.simbot.qguild.event.EventIntents.Companion.READY_TYPE
import love.forte.simbot.qguild.event.EventIntents.Companion.RESUMED_TYPE
import love.forte.simbot.qguild.internal.EventNameBasedMarker
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2025. ForteScarlet.
*
* This file is part of simbot-component-qq-guild.
*
* simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild.
* If not, see <https://www.gnu.org/licenses/>.
*/

package love.forte.simbot.qguild.event

/**
* 一个 [Intents] 追加器。
* 用于方便配置类等类型对外提供Kotlin中更便捷的DSL API使用的接口类型。
*
* 例如:
*
* ```kotlin
* intents {
* guilds()
* groupAndC2C()
* }
* ```
*
* 相关操作由KSP生成 inline API,性能无损且随着 [EventIntents] 的变化自动更新,可靠又便捷。
*
* 借助工厂API [IntentsAppender] 也可以用来通过 DSL API 构建一个 [Intents] 值。
*
* @since 4.1.4
*/
public interface IntentsAppender {
public fun appendIntents(intents: Intents)
}

public inline fun IntentsAppender.intents(block: IntentsAppenderOp.() -> Unit) {
IntentsAppenderOp(this).block()
}


Loading

0 comments on commit bd36762

Please sign in to comment.