Skip to content

Commit

Permalink
feature: finish cqcode serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
sgpublic committed Nov 18, 2023
1 parent aeef470 commit d318f0f
Show file tree
Hide file tree
Showing 44 changed files with 1,238 additions and 444 deletions.
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ plugins {
alias(mystere.plugins.ktorfit) apply false
alias(mystere.plugins.ksp) apply false
alias(mystere.plugins.buildkonfig) apply false
}
alias(mystere.plugins.sqldelight) apply false
}
13 changes: 13 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
repositories {
google()
mavenCentral()
gradlePluginPortal()
}

plugins {
`kotlin-dsl`
}

dependencies {

}
71 changes: 71 additions & 0 deletions buildSrc/src/main/kotlin/_VersionGen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import org.gradle.api.Project
import java.io.InputStream
import java.math.BigInteger
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.*


val Project.MYSTERE_LIB: String get() {
return findProperty("mystere.lib.version")!!.toString()
}
val Project.MYSTERE_APP: String get() {
return findProperty("mystere.app.version")!!.toString()
}
/**
* git commit id,若当前不为 git 仓库,则返回 TIME_MD5,可用于版本名后缀
* @see TIME_MD5
*/
val GIT_HEAD: String get() {
return withRuntime("git rev-parse --short HEAD") {
inputStream.useFirstLineIfNotBlack() ?: TIME_MD5
}
}

/**
* 按照 yyMMdd 的格式,根据当前日期返回一个整数,可用于版本号
*/
val DATED_VERSION: Int @Suppress("SimpleDateFormat") get() {
return Integer.parseInt(SimpleDateFormat("yyMMdd").format(Date()))
}

/**
* 按照 yyMMdd 的格式,根据最近一条 git commit 日期返回一个整数,若当前不为 git 仓库,则返回 DATED_VERSION,可用于版本号
* @see DATED_VERSION
*/
val COMMIT_DATE_VERSION: Int get() {
return withRuntime("git log -n 1 --pretty=format:%cd --date=format:%y%m%d") {
inputStream.useFirstLineIfNotBlack()?.toInt() ?: DATED_VERSION
}
}

/**
* 返回当前日期的 MD5,截取其中十位,与 git commit id 位数相等,可用于版本名后缀
*/
val TIME_MD5: String get() {
val digest = MessageDigest.getInstance("MD5")
.digest(System.currentTimeMillis().toString().toByteArray())
val pre = BigInteger(1, digest)
return pre.toString(16).padStart(32, '0').substring(8, 18)
}



inline fun <T> withRuntime(command: String, crossinline block: Process.() -> T): T {
val process = Runtime.getRuntime().exec(command)
return block.invoke(process)
}

fun InputStream.useFirstLineIf(block: (String) -> Boolean): String? {
val firstLine = reader().readLines()
.takeIf { it.isNotEmpty() }
?.get(0)
return firstLine?.takeIf { block.invoke(it) }
}

fun InputStream.useFirstLineIfNotBlack(): String? {
return useFirstLineIf {
it.isNotBlank()
}
}

2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#Gradle
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
org.gradle.jvmargs=-Xmx4096M -Dkotlin.daemon.jvm.options\="-Xmx4096M"

#Kotlin
kotlin.code.style=official
Expand Down
11 changes: 10 additions & 1 deletion gradle/mystere.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ ktor = "2.3.5"
ktorfit = "1.9.1"
ksp = "1.9.20-1.0.14"
buildkonfig = "0.14.0"
sqldelight = "2.0.0"

kotlinx-coroutines = "1.7.3"
kotlinx-serialization-json = "1.6.0"
kotlinx-serialization-message-pack = "0.0.2"
kotlinx-io= "0.3.0"
kotlinx-datetime= "0.4.1"

kotlin-logging = "5.1.0"
slf4j = "2.0.9"
Expand All @@ -29,13 +31,16 @@ logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
kotlinx-io-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-io-core", version.ref = "kotlinx-io" }
kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinx-datetime" }

ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" }
ktor-client-winhttp = { group = "io.ktor", name = "ktor-client-winhttp", version.ref = "ktor" }
ktor-plugin-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" }
ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" }
ktor-client-auth = { group = "io.ktor", name = "ktor-client-auth", version.ref = "ktor" }
ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" }
ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" }
ktor-plugin-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" }
ktor-plugin-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }

kotlinx-serialization-message-pack = { group = "com.wunderbee.kompack", name = "kompack-kotlin-serialization", version.ref = "ktor" }
Expand All @@ -46,6 +51,9 @@ yamlkt = { group = "net.mamoe.yamlkt", name = "yamlkt", version.ref = "yamlkt" }
ktorfit-lib-light = { group = "de.jensklingenberg.ktorfit", name = "ktorfit-lib-light", version.ref = "ktorfit" }
ktorfit-ksp = { group = "de.jensklingenberg.ktorfit", name = "ktorfit-ksp", version.ref = "ktorfit" }

sqldelight-driver-sqlite-native = { group = "app.cash.sqldelight", name = "native-driver", version.ref = "sqldelight" }
sqldelight-driver-sqlite-jvm = { group = "app.cash.sqldelight", name = "sqlite-driver", version.ref = "sqldelight" }

# :mystere
clikt = { group = "com.github.ajalt.clikt", name = "clikt", version.ref = "clikt" }

Expand All @@ -60,6 +68,7 @@ ktor = { id = "io.ktor.plugin", version.ref = "ktor" }
ktorfit = { id = "de.jensklingenberg.ktorfit", version.ref = "ktorfit" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
buildkonfig = { id = "com.codingfeline.buildkonfig", version.ref = "buildkonfig" }
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }

[bundles]

4 changes: 3 additions & 1 deletion kotlinx-serialization-cqcode/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import com.codingfeline.buildkonfig.compiler.FieldSpec

plugins {
alias(mystere.plugins.kotlin.multiplatform)
alias(mystere.plugins.kotlin.plugin.serialization)
Expand Down Expand Up @@ -92,6 +94,6 @@ buildkonfig {
packageName = findProperty("mystere.lib.serialization.cqcode.pkgName")!!.toString()

defaultConfigs {

buildConfigField(FieldSpec.Type.STRING, "VERSION_NAME", MYSTERE_LIB)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import kotlinx.serialization.modules.SerializersModule
internal val CQCodeJson = Json {
ignoreUnknownKeys = true
explicitNulls = true
useAlternativeNames = true
useArrayPolymorphism = false
}

sealed class CQCode(
Expand All @@ -26,113 +28,21 @@ sealed class CQCode(
}

fun decodeFromJson(element: JsonArray): CQCodeMessage {
val result = ArrayDeque<CQCodeMessageItem>()
for (item in element) {
result.addLast(decodeSingleItemFromJson(item.jsonObject))
}
return CQCodeMessage(result)
return CQCodeJson.decodeFromJsonElement<CQCodeMessage>(element)
}
fun decodeFromString(string: String): CQCodeMessage {
try {
if (string.startsWith("[{\"") && string.endsWith("\"}]")) {
return decodeFromJson(
CQCodeJson.decodeFromString(JsonArray.serializer(), string)
)
} else {
val result = ArrayDeque<CQCodeMessageItem>()

var index = 0
val chars = string.toCharArray()
while (index < chars.size) {
when (chars[index]) {
'[' -> {
val startIndex = index
try {
while (chars[index] != ']') {
index += 1
}
} catch (e: IndexOutOfBoundsException) {
throw SerializationException("Not a valid CQCode: $string")
}
index += 1
result.addLast(decodeSingleItemFromString(
string.substring(startIndex, index)
))
}
else -> {
val startIndex = index
try {
while (chars[index] != '[') {
index += 1
}
} catch (e: IndexOutOfBoundsException) {
throw SerializationException("Not a valid CQCode: $string")
}
result.addLast(CQCodeMessageItem.Text(
string.substring(startIndex, index)
))
index += 1
}
}
}
return CQCodeMessage(result)
}
} catch (e: Exception) {
throw SerializationException("Failed to decode content: $string", e)
}
}

fun decodeSingleItemFromJson(element: JsonObject): CQCodeMessageItem {
try {
val type = element["type"]?.jsonPrimitive!!.content.asCQCodeMessageItemType
return type.decodeFromJsonElement(CQCodeJson, element["data"]!!.jsonObject)
} catch (e: Exception) {
throw SerializationException("Failed to decode element: $element", e)
}
}
fun decodeSingleItemFromString(string: String): CQCodeMessageItem {
try {
if (string.startsWith("{\"") && string.endsWith("\"}")) {
return decodeSingleItemFromJson(
CQCodeJson.decodeFromString(JsonObject.serializer(), string)
)
} else if (string.contains("\\[(.*?)]".toRegex())) {
if (!string.startsWith("[CQ:") || !string.endsWith("]")) {
throw SerializationException("Not a valid CQCode item: $string")
}
val type = string.substring(
4, if (string.contains(",")) {
string.indexOf(",")
} else {
string.length - 4
})
.takeIf { it != "text" }
?.lowercase()
?: throw SerializationException("Not a valid CQCode: $string")
val args = with(string.substring(type.length + 5, string.length - 1)) {
HashMap<String, String>().also {
it["type"] = type
for (item in split(",")) {
if (!item.contains("=")) {
continue
}
val keyValue = item.split("=")
it[keyValue[0]] = keyValue[1]
}
}
}
return type.asCQCodeMessageItemType.deserialize(CQCodeMessageItemDecoder(
string, args, serializersModule
))
return CQCodeJson.decodeFromString(CQCodeMessage.serializer(), string)
} else {
return CQCodeMessageItem.Text(string)
return CQCodeMessage.serializer()
.deserialize(CQCodeMessageDecoder(string, serializersModule))
}
} catch (e: Exception) {
throw SerializationException("Failed to decode content: $string", e)
}
}


@Deprecated(
message = "Use encodeToString instead.",
replaceWith = ReplaceWith("encodeToString(string)"),
Expand All @@ -142,31 +52,12 @@ sealed class CQCode(
return encodeToString(value as CQCodeMessage)
}
fun encodeToJson(value: CQCodeMessage): JsonArray {
return buildJsonArray {
for (item in value.chain) {
add(encodeSingleItemToJson(item))
}
}
return CQCodeJson.encodeToJsonElement(value).jsonArray
}
fun encodeToString(value: CQCodeMessage): String {
return StringBuilder().also {
for (item in value.chain) {
it.append(encodeSingleItemToString(item))
}
}.toString()
}
fun encodeSingleItemToJson(element: CQCodeMessageItem): JsonObject {
return CQCodeJson.encodeToJsonElement(element).jsonObject
}
fun <T: CQCodeMessageItem> encodeSingleItemToString(value: T): String {
when (value) {
is CQCodeMessageItem.Text -> return value.text
else -> {
val encoder = CQCodeMessageItemEncoder(serializersModule)
value.type.serialize(encoder, value)
return encoder.encodeFinalResult()
}
}
val encoder = CQCodeMessageEncoder(serializersModule)
encoder.encodeCQCodeMessage(value)
return encoder.encodeFinalResult()
}

companion object: CQCode(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package io.github.mystere.serialization.cqcode
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
Expand Down Expand Up @@ -34,15 +34,12 @@ data class CQCodeMessage internal constructor(
}

object CQCodeMessageSerializer: KSerializer<CQCodeMessage> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor(
"io.github.mystere.serializer.cqcode.CQCodeMessage",
) {
}
override val descriptor: SerialDescriptor = listSerialDescriptor<CQCodeMessageItem>()

override fun deserialize(decoder: Decoder): CQCodeMessage {
try {
if (decoder is JsonDecoder) {
when (val element = decoder.decodeJsonElement()) {
when (decoder) {
is JsonDecoder -> when (val element = decoder.decodeJsonElement()) {
is JsonPrimitive -> {
if (element.isString) {
return CQCode.decodeFromString(element.content)
Expand All @@ -53,6 +50,7 @@ object CQCodeMessageSerializer: KSerializer<CQCodeMessage> {
}
else -> { }
}
is CQCodeMessageDecoder -> return decoder.decodeCQCodeMessage()
}
throw SerializationException("Unsupported decoder type: ${decoder::class}")
} catch (e: Exception) {
Expand All @@ -66,10 +64,14 @@ object CQCodeMessageSerializer: KSerializer<CQCodeMessage> {
is JsonEncoder -> {
encoder.encodeJsonElement(buildJsonArray {
for (item in value.chain) {
add(CQCodeJson.encodeToJsonElement(item))
add(buildJsonObject {
put("type", JsonPrimitive(item._type.name))
put("data", item._type.encodeToJsonElement(CQCodeJson, item))
})
}
})
}
is CQCodeMessageEncoder -> encoder.encodeCQCodeMessage(value)
else -> throw SerializationException("Unsupported encoder type: ${encoder::class}")
}
} catch (e: Exception) {
Expand Down
Loading

0 comments on commit d318f0f

Please sign in to comment.