From ef83281e97ffd4998a88f5c8b68fb906d60f81f6 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 15 May 2020 00:57:50 +0800 Subject: [PATCH 1/6] Redesign CommandDescriptor --- .../mamoe/mirai/console/command/Command.kt | 62 +++------------ .../console/command/CommandDescriptor.kt | 46 +++++++---- .../mirai/console/command/CommandManager.kt | 77 +++++++++---------- .../mirai/console/command/CommandParam.kt | 3 + .../console/command/CommandParserContext.kt | 7 +- 5 files changed, 89 insertions(+), 106 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt index c36120749..fd650dff1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt @@ -11,12 +11,8 @@ package net.mamoe.mirai.console.command -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.plugins.PluginBase +import net.mamoe.mirai.console.command.CommandDescriptor.SubCommandDescriptor import net.mamoe.mirai.message.data.Message -import java.lang.reflect.Member import kotlin.reflect.KProperty internal const val FOR_BINARY_COMPATIBILITY = "for binary compatibility" @@ -42,14 +38,18 @@ interface Command { */ } +internal fun Command.matchChild(args: List): SubCommandDescriptor { + +} + /** - * 指令实际参数列表. 参数顺序与 [Command.descriptor] 的 [CommandDescriptor.params] 相同. + * 解析完成的指令实际参数列表. 参数顺序与 [Command.descriptor] 的 [CommandDescriptor.params] 相同. */ class CommandArgs private constructor( @JvmField internal val values: List, - private val fromCommand: Command + private val fromCommand: SubCommandDescriptor ) : List by values { /** * 获取第一个类型为 [R] 的参数 @@ -71,7 +71,7 @@ class CommandArgs private constructor( * @throws NoSuchElementException 找不到这个名称的参数时抛出 */ operator fun get(name: String?): Any { - val index = fromCommand.descriptor.params.indexOfFirst { it.name == name } + val index = fromCommand.params.indexOfFirst { it.name == name } if (index == -1) { throw NoSuchElementException("Cannot find argument named $name") } @@ -93,12 +93,12 @@ class CommandArgs private constructor( inline operator fun getValue(thisRef: Any?, property: KProperty<*>): R = getReified() companion object { - fun parseFrom(command: Command, sender: CommandSender, rawArgs: List): CommandArgs { - val params = command.descriptor.params + fun parseFrom(command: SubCommandDescriptor, sender: CommandSender, rawArgs: List): CommandArgs { + val params = command.params require(rawArgs.size >= params.size) { "No enough rawArgs: required ${params.size}, found only ${rawArgs.size}" } - command.descriptor.params.asSequence().zip(rawArgs.asSequence()).map { (commandParam, any) -> + command.params.asSequence().zip(rawArgs.asSequence()).map { (commandParam, any) -> command.parserFor(commandParam)?.parse(any, sender) ?: error("Could not find a parser for param named ${commandParam.name}, typed ${commandParam.type.qualifiedName}") }.toList().let { bakedArgs -> @@ -106,44 +106,4 @@ class CommandArgs private constructor( } } } -} - -inline val Command.fullName get() = descriptor.fullName -inline val Command.usage get() = descriptor.usage -inline val Command.params get() = descriptor.params -inline val Command.description get() = descriptor.description -inline val Command.context get() = descriptor.context -inline val Command.aliases get() = descriptor.aliases -inline val Command.permission get() = descriptor.permission -inline val Command.allNames get() = descriptor.allNames - -abstract class PluginCommand( - final override val owner: PluginBase, - descriptor: CommandDescriptor -) : AbstractCommand(descriptor) - -internal abstract class ConsoleCommand( - descriptor: CommandDescriptor -) : AbstractCommand(descriptor) { - final override val owner: MiraiConsole get() = MiraiConsole -} - -sealed class AbstractCommand( - final override val descriptor: CommandDescriptor -) : Command - - -/** - * For Java - */ -@Suppress("unused") -abstract class BlockingCommand( - owner: PluginBase, - descriptor: CommandDescriptor -) : PluginCommand(owner, descriptor) { - final override suspend fun CommandSender.onCommand(args: CommandArgs): Boolean { - return withContext(Dispatchers.IO) { onCommandBlocking(this@onCommand, args) } - } - - abstract fun onCommandBlocking(sender: CommandSender, args: CommandArgs): Boolean } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandDescriptor.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandDescriptor.kt index 43a0b6d84..0a538400b 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandDescriptor.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandDescriptor.kt @@ -13,7 +13,7 @@ import net.mamoe.mirai.message.data.SingleMessage */ class CommandDescriptor( /** - * 子指令列表 + * 子指令列表. 第一个元素为默认值. */ val subCommands: List, /** @@ -25,8 +25,8 @@ class CommandDescriptor( ) { /** 子指令描述 */ inner class SubCommandDescriptor( - /** 为空字符串时代表默认 */ - val name: String, + /** 子指令名, 如 "/mute group add" 中的 "group add". 当表示默认指令时为父指令名. */ + val subName: String, /** 用法说明 */ val usage: String, /** 指令参数列表, 有顺序. */ @@ -40,8 +40,20 @@ class CommandDescriptor( * @see CommandPermission.or 要求其中一个权限 * @see CommandPermission.and 同时要求两个权限 */ - val permission: CommandPermission = CommandPermission.Default - ) + val permission: CommandPermission = CommandPermission.Default, + /** 指令执行器 */ + val onCommand: suspend (sender: CommandSender, args: CommandArgs) -> Boolean + ) { + init { + require(!(subName.startsWith(' ') || subName.endsWith(' '))) { "subName mustn't start or end with a caret" } + require(subName.isValidSubName()) { "subName mustn't contain any of $ILLEGAL_SUB_NAME_CHARS" } + } + + @JvmField + internal val bakedSubNames: Array> = + listOf(subName, *aliases).map { it.bakeSubName() }.toTypedArray() + internal inline val parent: CommandDescriptor get() = this@CommandDescriptor + } /** * 指令参数解析器环境. @@ -49,6 +61,13 @@ class CommandDescriptor( val context: CommandParserContext = CommandParserContext.Builtins + overrideContext } +internal val CommandDescriptor.base: CommandDescriptor.SubCommandDescriptor get() = subCommands[0] + + +internal val ILLEGAL_SUB_NAME_CHARS = "\\/!@#$%^&*()_+-={}[];':\",.<>?`~".toCharArray() +internal fun String.isValidSubName(): Boolean = ILLEGAL_SUB_NAME_CHARS.none { it in this } +internal fun String.bakeSubName(): Array = split(' ').filterNot { it.isBlank() }.toTypedArray() + /** * 检查指令参数数量是否足够, 类型是否匹配. @@ -213,15 +232,16 @@ inline class ParamBlock internal constructor(@PublishedApi internal val list: Mu /// internal -internal fun Any.flattenCommandComponents(): Sequence = when (this) { - is Array<*> -> this.asSequence().flatMap { - it?.flattenCommandComponents() ?: throw java.lang.IllegalArgumentException("unexpected null value") +internal fun Any.flattenCommandComponents(): List { + val list = ArrayList() + when (this) { + is String -> list.addAll(split(' ').filterNot { it.isBlank() }) + is PlainText -> list.addAll(content.flattenCommandComponents()) + is SingleMessage -> list.add(this.toString()) + is MessageChain -> this.asSequence().forEach { list.addAll(it.flattenCommandComponents()) } + else -> throw IllegalArgumentException("Illegal component: $this") } - is String -> splitToSequence(' ').filterNot { it.isBlank() } - is PlainText -> content.flattenCommandComponents() - is SingleMessage -> sequenceOf(this.toString()) - is MessageChain -> this.asSequence().flatMap { it.flattenCommandComponents() } - else -> throw IllegalArgumentException("Illegal component: $this") + return list } internal fun Any.checkFullName(errorHint: String): Array { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index c8b05be87..ec319625a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -7,7 +7,7 @@ import kotlinx.atomicfu.locks.withLock import net.mamoe.mirai.console.plugins.PluginBase import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain -import java.util.* +import net.mamoe.mirai.utils.MiraiExperimentalAPI import java.util.concurrent.locks.ReentrantLock sealed class CommandOwner @@ -30,16 +30,13 @@ fun CommandOwner.unregisterAllCommands() { } /** - * 注册一个指令. 若此指令已经注册或有已经注册的指令与 [allNames] 重名, 返回 `false` + * 注册一个指令. 若此指令已经注册或有已经注册的指令与 [SubCommandDescriptor] 重名, 返回 `false` */ fun Command.register(): Boolean = InternalCommandManager.modifyLock.withLock { if (findDuplicate() != null) { return false } InternalCommandManager.registeredCommands.add(this@register) - for (name in this.allNames) { - InternalCommandManager.nameToCommandMap[name] = this@register - } return true } @@ -47,25 +44,24 @@ fun Command.register(): Boolean = InternalCommandManager.modifyLock.withLock { * 查找是否有重名的指令. 返回重名的指令. */ fun Command.findDuplicate(): Command? { - return InternalCommandManager.nameToCommandMap.entries.firstOrNull { (names, _) -> - this.allNames.any { it.contentEquals(names) } - }?.value + return InternalCommandManager.registeredCommands.firstOrNull { + it.descriptor.base.bakedSubNames intersects this.descriptor.base.bakedSubNames + } +} + +private infix fun Array.intersects(other: Array): Boolean { + val max = this.size.coerceAtMost(other.size) + for (i in 0 until max) { + if (this[i] == other[i]) return true + } + return false } /** * 取消注册这个指令. 若指令未注册, 返回 `false` */ fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock { - if (!InternalCommandManager.registeredCommands.contains(this)) { - return false - } - InternalCommandManager.registeredCommands.remove(this) - for (name in this.allNames) { - InternalCommandManager.nameToCommandMap.entries.removeIf { - it.key.contentEquals(this.fullName) - } - } - return true + return InternalCommandManager.registeredCommands.remove(this) } /** @@ -73,8 +69,11 @@ fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock { * @param args 接受 [String] 或 [Message] * @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配 */ +@MiraiExperimentalAPI suspend fun CommandSender.executeCommand(vararg args: Any): Boolean { - return args.flattenCommandComponents().toList().executeCommand(this) + val command = InternalCommandManager.matchCommand(args[0].toString()) ?: return false + + return args.flattenCommandComponents().executeCommand(this) } /** @@ -82,15 +81,15 @@ suspend fun CommandSender.executeCommand(vararg args: Any): Boolean { * @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配 */ suspend fun MessageChain.executeAsCommand(sender: CommandSender): Boolean { - return this.flattenCommandComponents().toList().executeCommand(sender) + return this.flattenCommandComponents().executeCommand(sender) } /** * 检查指令参数并直接执行一个指令. */ -suspend inline fun CommandSender.execute(command: Command, args: CommandArgs): Boolean = with(command) { - checkArgs(args) - return this@execute.onCommand(args) +suspend inline fun CommandSender.execute(command: CommandDescriptor.SubCommandDescriptor, args: CommandArgs): Boolean { + command.checkArgs(args) + return command.onCommand(this@execute, args) } /** @@ -99,19 +98,19 @@ suspend inline fun CommandSender.execute(command: Command, args: CommandArgs): B suspend inline fun Command.execute(sender: CommandSender, args: CommandArgs): Boolean = sender.execute(this, args) /** - * 解析并执行一个指令. - * @param args 接受 [String] 或 [Message] + * 核心执行指令 */ -suspend fun CommandSender.execute(vararg args: Any): Boolean = args.toList().executeCommand(this) - - -internal suspend fun List.executeCommand(sender: CommandSender): Boolean { - val command = InternalCommandManager.matchCommand(this) ?: return false - return command.run { - sender.onCommand( +internal suspend fun List.executeCommand(origin: String, sender: CommandSender): Boolean { + if (this.isEmpty()) return false + val command = InternalCommandManager.matchCommand(origin) ?: return false + TODO() + /* + command.descriptor.subCommands.forEach { sub -> + }.run { + sender.onDefault( CommandArgs.parseFrom(command, sender, this@executeCommand.drop(command.fullName.size)) ) - } + }*/ } internal infix fun Array.matchesBeginning(list: List): Boolean { @@ -125,18 +124,16 @@ internal object InternalCommandManager { @JvmField internal val registeredCommands: MutableList = mutableListOf() - @JvmField - internal val nameToCommandMap: TreeMap, Command> = TreeMap(Comparator.comparingInt { it.size }) - @JvmField internal val modifyLock = ReentrantLock() internal var _commandPrefix: String = "/" - internal fun matchCommand(splitted: List): Command? { - nameToCommandMap.entries.forEach { - if (it.key matchesBeginning splitted) return it.value + internal fun matchCommand(name: String): Command? { + return registeredCommands.firstOrNull { command -> + command.descriptor.base.bakedSubNames.any { + name.startsWith(it[0]) && (name.length <= it[0].length || name[it[0].length] == ' ') // 判断跟随空格 + } } - return null } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParam.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParam.kt index b29876c87..2cee303a6 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParam.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParam.kt @@ -41,4 +41,7 @@ fun CommandParam.parserFrom(command: Command): CommandArgParser? fun CommandParam.parserFrom(descriptor: CommandDescriptor): CommandArgParser? = descriptor.parserFor(this) +fun CommandParam.parserFrom(descriptor: CommandDescriptor.SubCommandDescriptor): CommandArgParser? = + descriptor.parserFor(this) + fun CommandParam.parserFrom(context: CommandParserContext): CommandArgParser? = context.parserFor(this) \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParserContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParserContext.kt index 71ecae55e..c02e017a7 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParserContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParserContext.kt @@ -61,10 +61,13 @@ fun CommandParserContext.parserFor(param: CommandParam): CommandArg param.overrideParser ?: this[param.type] fun CommandDescriptor.parserFor(param: CommandParam): CommandArgParser? = - param.overrideParser ?: this.context.parserFor(param) + this.context.parserFor(param) + +fun CommandDescriptor.SubCommandDescriptor.parserFor(param: CommandParam): CommandArgParser? = + this.parent.parserFor(param) fun Command.parserFor(param: CommandParam): CommandArgParser? = - param.overrideParser ?: this.descriptor.parserFor(param) + this.descriptor.parserFor(param) /** * 合并两个 [CommandParserContext], [replacer] 将会替换 [this] 中重复的 parser. From a43fb27181c3d753eb3f5efe7ed605b50ae39c9c Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 15 May 2020 00:58:31 +0800 Subject: [PATCH 2/6] Fix build --- .../kotlin/net/mamoe/mirai/console/command/Command.kt | 4 ---- .../net/mamoe/mirai/console/command/CommandManager.kt | 10 +++++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt index fd650dff1..ad1d3e7e5 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt @@ -38,10 +38,6 @@ interface Command { */ } -internal fun Command.matchChild(args: List): SubCommandDescriptor { - -} - /** * 解析完成的指令实际参数列表. 参数顺序与 [Command.descriptor] 的 [CommandDescriptor.params] 相同. diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index ec319625a..5437efa82 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -73,7 +73,8 @@ fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock { suspend fun CommandSender.executeCommand(vararg args: Any): Boolean { val command = InternalCommandManager.matchCommand(args[0].toString()) ?: return false - return args.flattenCommandComponents().executeCommand(this) + TODO() + //return args.flattenCommandComponents().executeCommand(this) } /** @@ -81,7 +82,8 @@ suspend fun CommandSender.executeCommand(vararg args: Any): Boolean { * @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配 */ suspend fun MessageChain.executeAsCommand(sender: CommandSender): Boolean { - return this.flattenCommandComponents().executeCommand(sender) + TODO() +/// return this.flattenCommandComponents().executeCommand(sender) } /** @@ -95,7 +97,9 @@ suspend inline fun CommandSender.execute(command: CommandDescriptor.SubCommandDe /** * 检查指令参数并直接执行一个指令. */ -suspend inline fun Command.execute(sender: CommandSender, args: CommandArgs): Boolean = sender.execute(this, args) +suspend inline fun Command.execute(sender: CommandSender, args: CommandArgs): Boolean = + TODO() +//sender.execute(this, args) /** * 核心执行指令 From 71a83c8509841fb49d1aeeba74cadc318619d927 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 15 May 2020 01:00:29 +0800 Subject: [PATCH 3/6] Fix build --- .../java/net/mamoe/mirai/console/command/TestCommands.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/backend/mirai-console/src/test/java/net/mamoe/mirai/console/command/TestCommands.kt b/backend/mirai-console/src/test/java/net/mamoe/mirai/console/command/TestCommands.kt index 717b32979..5ff9aca1e 100644 --- a/backend/mirai-console/src/test/java/net/mamoe/mirai/console/command/TestCommands.kt +++ b/backend/mirai-console/src/test/java/net/mamoe/mirai/console/command/TestCommands.kt @@ -11,20 +11,18 @@ package net.mamoe.mirai.console.command -import kotlinx.coroutines.runBlocking import net.mamoe.mirai.Bot import net.mamoe.mirai.console.plugins.PluginBase import net.mamoe.mirai.message.data.* import org.junit.jupiter.api.Test import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue val plugin: PluginBase = object : PluginBase() { } +/* internal object TestCommand : PluginCommand( plugin, CommandDescriptor("test") { @@ -36,7 +34,7 @@ internal object TestCommand : PluginCommand( sendMessage(s) return true } -} +}*/ internal class TestCommands { @Test @@ -50,6 +48,7 @@ internal class TestCommands { ) } + /* @Test fun testRegister() { assertTrue(TestCommand.register()) @@ -81,7 +80,7 @@ internal class TestCommands { execute("test", "arg") }.contentToString() ) - } + }*/ } From fdf12d024fa99196db156a0c44bf0bc0ffe5be10 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 15 May 2020 11:45:53 +0800 Subject: [PATCH 4/6] Use YamlKt instead of Gson, fastjson --- backend/mirai-console/build.gradle.kts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts index 312cf19ec..124050c15 100644 --- a/backend/mirai-console/build.gradle.kts +++ b/backend/mirai-console/build.gradle.kts @@ -32,9 +32,8 @@ dependencies { compileAndRuntime("net.mamoe:mirai-core:${Versions.Mirai.core}") compileAndRuntime(kotlin("stdlib")) - api("com.google.code.gson:gson:2.8.6") - api(group = "org.yaml", name = "snakeyaml", version = "1.25") - api(group = "com.moandjiezana.toml", name = "toml4j", version = "0.7.2") + api("moe.him188.yamlkt:yamlkt:0.2.0") + api("org.jsoup:jsoup:1.12.1") api("org.jetbrains:annotations:19.0.0") From ef17e23a0377f74784c421546d00327541b2f815 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 15 May 2020 14:44:39 +0800 Subject: [PATCH 5/6] Settings infrastructure: Primitive Value codegen, use-site codegen, value impl codegen; Settings serializers; Values; Settings property delegation. --- backend/codegen/README.MD | 87 +++ backend/codegen/build.gradle.kts | 41 ++ .../codegen/SettingValueUseSiteCodegen.kt | 76 ++ .../mirai/console/codegen/ValueImplCodegen.kt | 102 +++ .../mirai/console/codegen/ValuesCodegen.kt | 199 ++++++ backend/mirai-console/build.gradle.kts | 2 +- .../console/plugins/ConfigSectionFactory.java | 9 - .../mirai/console/center/CuiPluginCenter.kt | 58 +- .../mirai/console/plugins/ConfigSection.kt | 617 ----------------- .../mamoe/mirai/console/plugins/PluginBase.kt | 18 - .../mamoe/mirai/console/setting/Setting.kt | 123 ++++ .../net/mamoe/mirai/console/setting/Value.kt | 16 + .../mamoe/mirai/console/setting/_Setting.kt | 55 ++ .../net/mamoe/mirai/console/setting/_Value.kt | 101 +++ .../mamoe/mirai/console/setting/_ValueImpl.kt | 652 ++++++++++++++++++ .../mamoe/mirai/console/utils/BotHelper.kt | 26 +- settings.gradle | 3 + 17 files changed, 1488 insertions(+), 697 deletions(-) create mode 100644 backend/codegen/README.MD create mode 100644 backend/codegen/build.gradle.kts create mode 100644 backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/SettingValueUseSiteCodegen.kt create mode 100644 backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueImplCodegen.kt create mode 100644 backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt delete mode 100644 backend/mirai-console/src/main/java/net/mamoe/mirai/console/plugins/ConfigSectionFactory.java delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/ConfigSection.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Setting.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Value.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_ValueImpl.kt diff --git a/backend/codegen/README.MD b/backend/codegen/README.MD new file mode 100644 index 000000000..6d5db89bc --- /dev/null +++ b/backend/codegen/README.MD @@ -0,0 +1,87 @@ +# Mirai Console +你可以在全平台运行Mirai高效率机器人框架 +### Mirai Console提供了6个版本以满足各种需要 +#### 所有版本的Mirai Console API相同 插件系统相同 + +| 名字 | 介绍 | +|:------------------------|:------------------------------| +| Mirai-Console-Pure | 最纯净版, CLI环境, 通过标准输入与标准输出 交互 | +| Mirai-Console-Terminal | (UNIX)Terminal环境 提供简洁的富文本控制台 | +| Mirai-Console-Android | 安卓APP (TODO) | +| Mirai-Console-Graphical | JavaFX的图形化界面 (.jar/.exe/.dmg) | +| Mirai-Console-WebPanel | Web Panel操作(TODO) | +| Mirai-Console-Ios | IOS APP (TODO) | + + +### 如何选择版本 +1: Mirai-Console-Pure 兼容性最高, 在其他都表现不佳的时候请使用
+2: 以系统区分 +```kotlin + return when(operatingSystem){ + WINDOWS -> listOf("Graphical","WebPanel","Pure") + MAC_OS -> listOf("Graphical","Terminal","WebPanel","Pure") + LINUX -> listOf("Terminal","Pure") + ANDROID -> listOf("Android","Pure","WebPanel") + IOS -> listOf("Ios") + else -> listOf("Pure") + } +``` +3: 以策略区分 +```kotlin + return when(task){ + 体验 -> listOf("Graphical","Terminal","WebPanel","Android","Pure") + 测试插件 -> listOf("Pure") + 调试插件 -> byOperatingSystem() + 稳定挂机 -> listOf("Terminal","Pure") + else -> listOf("Pure") + } +``` + + +#### More Importantly, Mirai Console support Plugins, tells the bot what to do +#### Mirai Console 支持插件系统, 你可以自己开发或使用公开的插件来逻辑化机器人, 如群管 +
+ +#### download 下载 +#### how to get/write plugins 如何获取/写插件 +
+
+ +### how to use(如何使用) +#### how to run Mirai Console +
    +
  • download mirai-console.jar
  • +
  • open command line/terminal
  • +
  • create a folder and put mirai-console.jar in
  • +
  • cd that folder
  • +
  • "java -jar mirai-console.jar"
  • +
+ +
    +
  • 下载mirai-console.jar
  • +
  • 打开终端
  • +
  • 在任何地方创建一个文件夹, 并放入mirai-console.jar
  • +
  • 在终端中打开该文件夹"cd"
  • +
  • 输入"java -jar mirai-console.jar"
  • +
+ +#### how to add plugins +
    +
  • After first time of running mirai console
  • +
  • /plugins/folder will be created next to mirai-console.jar
  • +
  • put plugin(.jar) into /plugins/
  • +
  • restart mirai console
  • +
  • checking logger and check if the plugin is loaded successfully
  • +
  • if the plugin has it own Config file, it normally appears in /plugins/{pluginName}/
  • +
+ +
    +
  • 在首次运行mirai console后
  • +
  • mirai-console.jar 的同级会出现/plugins/文件夹
  • +
  • 将插件(.jar)放入/plugins/文件夹
  • +
  • 重启mirai console
  • +
  • 在开启后检查日志, 是否成功加载
  • +
  • 如该插件有配置文件, 配置文件一般会创建在/plugins/插件名字/ 文件夹下
  • +
+ + diff --git a/backend/codegen/build.gradle.kts b/backend/codegen/build.gradle.kts new file mode 100644 index 000000000..574abfe7f --- /dev/null +++ b/backend/codegen/build.gradle.kts @@ -0,0 +1,41 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id("kotlin") + kotlin("plugin.serialization") + id("java") +} + +kotlin { + sourceSets { + all { + languageSettings.useExperimentalAnnotation("kotlin.Experimental") + languageSettings.useExperimentalAnnotation("kotlin.OptIn") + languageSettings.progressiveMode = true + languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI") + languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes") + languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference") + languageSettings.useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts") + } + } +} + +dependencies { + api(kotlin("stdlib")) +} + +val compileKotlin: KotlinCompile by tasks +compileKotlin.kotlinOptions { + jvmTarget = "1.8" +} +val compileTestKotlin: KotlinCompile by tasks +compileTestKotlin.kotlinOptions { + jvmTarget = "1.8" +} +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} +tasks.withType(JavaCompile::class.java) { + options.encoding = "UTF8" +} \ No newline at end of file diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/SettingValueUseSiteCodegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/SettingValueUseSiteCodegen.kt new file mode 100644 index 000000000..99269419a --- /dev/null +++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/SettingValueUseSiteCodegen.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.codegen + +import java.io.File + + +fun main() { + println(File("").absolutePath) // default project base dir + + File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Setting.kt").apply { + createNewFile() + }.writeText(buildString { + appendln(COPYRIGHT) + appendln() + appendln(PACKAGE) + appendln() + // appendln(IMPORTS) + // appendln() + // appendln() + appendln(DO_NOT_MODIFY) + appendln() + appendln() + appendln(genAllValueUseSite()) + }) +} + +private val DO_NOT_MODIFY = """ +/** + * !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.SettingValueUseSiteCodegen.kt + * !!! DO NOT MODIFY THIS FILE MANUALLY + */ +""".trimIndent() + +private val PACKAGE = """ +package net.mamoe.mirai.console.setting +""".trimIndent() + +private val IMPORTS = """ +import kotlinx.serialization.builtins.* +""".trimIndent() + +fun genAllValueUseSite(): String = buildString { + // PRIMITIVE + for (number in NUMBERS + OTHER_PRIMITIVES) { + appendln(genValueUseSite(number, number)) + } + + // PRIMITIVE ARRAYS + for (number in NUMBERS + OTHER_PRIMITIVES.filterNot { it == "String" }) { + appendln(genValueUseSite("${number}Array", "${number}Array")) + } + + // TYPED ARRAYS + for (number in NUMBERS + OTHER_PRIMITIVES) { + appendln(genValueUseSite("Array<${number}>", "Typed${number}Array")) + } + + // PRIMITIVE LISTS + for (number in NUMBERS + OTHER_PRIMITIVES) { + appendln(genValueUseSite("List<${number}>", "${number}List")) + } +} + +fun genValueUseSite(kotlinTypeName: String, miraiValueName: String): String = + """ + fun Setting.value(default: $kotlinTypeName): ${miraiValueName}Value = valueImpl(default) + """.trimIndent() + diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueImplCodegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueImplCodegen.kt new file mode 100644 index 000000000..7d40123f1 --- /dev/null +++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueImplCodegen.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.codegen + +import java.io.File + + +fun main() { + println(File("").absolutePath) // default project base dir + + File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_ValueImpl.kt").apply { + createNewFile() + }.writeText(buildString { + appendln(COPYRIGHT) + appendln() + appendln(PACKAGE) + appendln() + appendln(IMPORTS) + appendln() + appendln() + appendln(DO_NOT_MODIFY) + appendln() + appendln() + appendln(genAllValueImpl()) + }) +} + +private val DO_NOT_MODIFY = """ +/** + * !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.ValueImplCodegen.kt + * !!! DO NOT MODIFY THIS FILE MANUALLY + */ +""".trimIndent() + +private val PACKAGE = """ +package net.mamoe.mirai.console.setting +""".trimIndent() + +private val IMPORTS = """ +import kotlinx.serialization.builtins.* +""".trimIndent() + +fun genAllValueImpl(): String = buildString { + // PRIMITIVE + for (number in NUMBERS + OTHER_PRIMITIVES) { + appendln(genValueImpl(number, number, "$number.serializer()", false)) + } + + // PRIMITIVE ARRAYS + for (number in NUMBERS + OTHER_PRIMITIVES.filterNot { it == "String" }) { + appendln(genValueImpl("${number}Array", "${number}Array", "${number}ArraySerializer()", true)) + } + + // TYPED ARRAYS + for (number in NUMBERS + OTHER_PRIMITIVES) { + appendln(genValueImpl("Array<${number}>", "Typed${number}Array", "ArraySerializer(${number}.serializer())", true)) + } + + // PRIMITIVE LISTS + for (number in NUMBERS + OTHER_PRIMITIVES) { + appendln(genValueImpl("List<${number}>", "${number}List", "ListSerializer(${number}.serializer())", false)) + } +} + +fun genValueImpl(kotlinTypeName: String, miraiValueName: String, serializer: String, isArray: Boolean): String = + """ + internal fun Setting.valueImpl(default: ${kotlinTypeName}): ${miraiValueName}Value { + return object : ${miraiValueName}Value() { + private var internalValue: $kotlinTypeName = default + override var value: $kotlinTypeName + get() = internalValue + set(new) { + ${ + if (isArray) """ + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + """.trim() + else """ + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + """.trim() + } + } + override val serializer = ${serializer}.bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } + } + """.trimIndent() + "\n" + diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt new file mode 100644 index 000000000..4d75b33d2 --- /dev/null +++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt @@ -0,0 +1,199 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ +@file:Suppress("ClassName") + +package net.mamoe.mirai.console.codegen + +import org.intellij.lang.annotations.Language +import java.io.File + +fun main() { + println(File("").absolutePath) // default project base dir + + File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Value.kt").apply { + createNewFile() + }.writeText(genPublicApi()) +} + +internal const val COPYRIGHT = """ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ +""" + +internal val NUMBERS = listOf( + "Int", + "Short", + "Byte", + "Long", + "Float", + "Double" +) + +internal val UNSIGNED_NUMBERS = listOf( + "UInt", + "UShort", + "UByte", + "ULong" +) + +internal val OTHER_PRIMITIVES = listOf( + "Boolean", + "Char", + "String" +) + +fun genPublicApi() = buildString { + fun appendln(@Language("kt") code: String){ + this.appendln(code.trimIndent()) + } + + appendln(COPYRIGHT.trim()) + appendln() + appendln( + """ + package net.mamoe.mirai.console.setting + + import kotlinx.serialization.KSerializer + import kotlin.properties.ReadWriteProperty + import kotlin.reflect.KProperty + """ + ) + appendln() + appendln( + """ + /** + * !!! These primitive types are auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.codegen.ValuesCodegen.kt + * !!! for better performance + * !!! DO NOT MODIFY THIS FILE MANUALLY + */ + """ + ) + appendln() + + appendln( + """ +sealed class Value : ReadWriteProperty { + abstract var value: T + + abstract val serializer: KSerializer + override fun getValue(thisRef: Setting, property: KProperty<*>): T = value + override fun setValue(thisRef: Setting, property: KProperty<*>, value: T) { + this.value = value + } +} + """ + ) + appendln() + + // PRIMITIVES + + appendln( + """ + sealed class PrimitiveValue : Value() + + sealed class NumberValue : Value() + """ + ) + + for (number in NUMBERS) { + val template = """ + abstract class ${number}Value internal constructor() : NumberValue<${number}>() + """ + + appendln(template) + } + + appendln() + + for (number in OTHER_PRIMITIVES) { + val template = """ + abstract class ${number}Value internal constructor() : PrimitiveValue<${number}>() + """ + + appendln(template) + } + + appendln() + + // ARRAYS + + appendln( + """ + // T can be primitive array or typed Array + sealed class ArrayValue : Value() + """ + ) + + // PRIMITIVE ARRAYS + appendln( + """ + sealed class PrimitiveArrayValue : ArrayValue() + """ + ) + appendln() + + for (number in (NUMBERS + OTHER_PRIMITIVES).filterNot { it == "String" }) { + val template = """ + abstract class ${number}ArrayValue internal constructor() : PrimitiveArrayValue<${number}Array>(), Iterable<${number}> { + override fun iterator(): Iterator<${number}> = this.value.iterator() + } + """ + + appendln(template) + } + + appendln() + + // TYPED ARRAYS + + appendln( + """ + sealed class TypedPrimitiveArrayValue : ArrayValue>() , Iterable{ + override fun iterator() = this.value.iterator() + } + """ + ) + appendln() + + for (number in (NUMBERS + OTHER_PRIMITIVES)) { + val template = """ + abstract class Typed${number}ArrayValue internal constructor() : TypedPrimitiveArrayValue<${number}>() + """ + + appendln(template) + } + + appendln() + + // TYPED LISTS + + appendln( + """ + sealed class ListValue : Value>(), Iterable{ + override fun iterator() = this.value.iterator() + } + """ + ) + + for (number in (NUMBERS + OTHER_PRIMITIVES)) { + val template = """ + abstract class ${number}ListValue internal constructor() : ListValue<${number}>() + """ + + appendln(template) + } + + appendln() +} \ No newline at end of file diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts index 124050c15..60572b5fd 100644 --- a/backend/mirai-console/build.gradle.kts +++ b/backend/mirai-console/build.gradle.kts @@ -32,7 +32,7 @@ dependencies { compileAndRuntime("net.mamoe:mirai-core:${Versions.Mirai.core}") compileAndRuntime(kotlin("stdlib")) - api("moe.him188.yamlkt:yamlkt:0.2.0") + api("net.mamoe.yamlkt:yamlkt:0.2.0") api("org.jsoup:jsoup:1.12.1") diff --git a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/plugins/ConfigSectionFactory.java b/backend/mirai-console/src/main/java/net/mamoe/mirai/console/plugins/ConfigSectionFactory.java deleted file mode 100644 index 0f1877e06..000000000 --- a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/plugins/ConfigSectionFactory.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.mamoe.mirai.console.plugins; - -public class ConfigSectionFactory { - - public static ConfigSection create(){ - return ConfigSection.Companion.create(); - } - -} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/CuiPluginCenter.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/CuiPluginCenter.kt index faced21a0..60b041c17 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/CuiPluginCenter.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/CuiPluginCenter.kt @@ -1,12 +1,10 @@ package net.mamoe.mirai.console.center -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import com.google.gson.JsonParser import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import io.ktor.client.request.get import io.ktor.util.KtorExperimentalAPI +import kotlinx.serialization.json.* import net.mamoe.mirai.console.utils.retryCatching import java.io.File @@ -26,20 +24,20 @@ internal object CuiPluginCenter : PluginCenter { if (plugins == null) { refresh() } - if (it >= plugins!!.size()) { + if (it >= plugins!!.size) { return@forEach } val info = plugins!![it] - with(info.asJsonObject) { - map[this.get("name").asString] = PluginCenter.PluginInsight( - this.get("name")?.asString ?: "", - this.get("version")?.asString ?: "", - this.get("core")?.asString ?: "", - this.get("console")?.asString ?: "", - this.get("author")?.asString ?: "", - this.get("description")?.asString ?: "", - this.get("tags")?.asJsonArray?.map { it.asString } ?: arrayListOf(), - this.get("commands")?.asJsonArray?.map { it.asString } ?: arrayListOf() + with(info.jsonObject) { + map[this["name"]!!.toString()] = PluginCenter.PluginInsight( + this["name"]?.primitive?.content ?: "", + this["version"]?.primitive?.content ?: "", + this["core"]?.primitive?.content ?: "", + this["console"]?.primitive?.content ?: "", + this["author"]?.primitive?.content ?: "", + this["description"]?.primitive?.content ?: "", + this["tags"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf(), + this["commands"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf() ) } } @@ -62,18 +60,18 @@ internal object CuiPluginCenter : PluginCenter { return result.asJson().run { PluginCenter.PluginInfo( - this.get("name")?.asString ?: "", - this.get("version")?.asString ?: "", - this.get("core")?.asString ?: "", - this.get("console")?.asString ?: "", - this.get("tags")?.asJsonArray?.map { it.asString } ?: arrayListOf(), - this.get("author")?.asString ?: "", - this.get("contact")?.asString ?: "", - this.get("description")?.asString ?: "", - this.get("usage")?.asString ?: "", - this.get("vsc")?.asString ?: "", - this.get("commands")?.asJsonArray?.map { it.asString } ?: arrayListOf(), - this.get("changeLog")?.asJsonArray?.map { it.asString } ?: arrayListOf() + this["name"]?.primitive?.content ?: "", + this["version"]?.primitive?.content ?: "", + this["core"]?.primitive?.content ?: "", + this["console"]?.primitive?.content ?: "", + this["tags"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf(), + this["author"]?.primitive?.content ?: "", + this["contact"]?.primitive?.content ?: "", + this["description"]?.primitive?.content ?: "", + this["usage"]?.primitive?.content ?: "", + this["vsc"]?.primitive?.content ?: "", + this["commands"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf(), + this["changeLog"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf() ) } @@ -82,10 +80,10 @@ internal object CuiPluginCenter : PluginCenter { override suspend fun refresh() { val results = Http.get("https://miraiapi.jasonczc.cn/getPluginList").asJson() - if (!(results.has("success") && results["success"].asBoolean)) { + if (!(results.containsKey("success") && results["success"]?.boolean == true)) { error("Failed to fetch plugin list from Cui Cloud") } - plugins = results.get("result").asJsonArray//先不解析 + plugins = results["result"]?.jsonArray//先不解析 } override suspend fun T.downloadPlugin(name: String, progressListener: T.(Float) -> Unit): File { @@ -118,8 +116,10 @@ internal object CuiPluginCenter : PluginCenter { get() = "崔云" + private val json = Json(JsonConfiguration.Stable) + private fun String.asJson(): JsonObject { - return JsonParser.parseString(this).asJsonObject + return json.parseJson(this).jsonObject } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/ConfigSection.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/ConfigSection.kt deleted file mode 100644 index ba8225913..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/ConfigSection.kt +++ /dev/null @@ -1,617 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -@file:Suppress("MemberVisibilityCanBePrivate") - -package net.mamoe.mirai.console.plugins - -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import com.moandjiezana.toml.Toml -import com.moandjiezana.toml.TomlWriter -import kotlinx.serialization.Serializable -import kotlinx.serialization.UnstableDefault -import net.mamoe.mirai.console.encodeToString -import net.mamoe.mirai.utils.MiraiInternalAPI -import org.yaml.snakeyaml.Yaml -import java.io.File -import java.io.InputStream -import java.util.* -import java.util.concurrent.ConcurrentHashMap -import kotlin.NoSuchElementException -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KClass -import kotlin.reflect.KProperty -import kotlin.reflect.full.isSubclassOf - - -/** - * 标注一个即将在将来版本删除的 API - * - * 目前 [Config] 的读写设计语义不明, list 和 map 读取效率低, 属性委托写入语义不明 - * 将在未来重写并变为 `ReadOnlyConfig` 和 `ReadWriteConfig`. - * 并计划添加图形端配置绑定, 和带注释的序列化, 自动保存的配置文件, 与指令绑定的属性. - */ -@RequiresOptIn("将在未来进行不兼容的修改", RequiresOptIn.Level.WARNING) -annotation class ToBeRemoved - -/** - * 可读可写配置文件. - * - * @suppress 注意: 配置文件正在进行不兼容的修改 - */ -interface Config { - @ToBeRemoved - fun getConfigSection(key: String): ConfigSection - - fun getString(key: String): String - fun getInt(key: String): Int - fun getFloat(key: String): Float - fun getDouble(key: String): Double - fun getLong(key: String): Long - fun getBoolean(key: String): Boolean - - @ToBeRemoved - fun getList(key: String): List<*> - - - @ToBeRemoved - fun getStringList(key: String): List - - @ToBeRemoved - fun getIntList(key: String): List - - @ToBeRemoved - fun getFloatList(key: String): List - - @ToBeRemoved - fun getDoubleList(key: String): List - - @ToBeRemoved - fun getLongList(key: String): List - - @ToBeRemoved - fun getConfigSectionList(key: String): List - - operator fun set(key: String, value: Any) - operator fun get(key: String): Any? - operator fun contains(key: String): Boolean - - @ToBeRemoved - fun exist(key: String): Boolean - - /** - * 设置 key = value (如果value不存在则valueInitializer会被调用) - * 之后返回当前key对应的值 - * */ - @ToBeRemoved - fun setIfAbsent(key: String, value: T) - - @ToBeRemoved - fun setIfAbsent(key: String, valueInitializer: Config.() -> T) - - @ToBeRemoved - fun asMap(): Map - - @ToBeRemoved - fun save() - - companion object { - @ToBeRemoved - fun load(fileName: String): Config { - return load( - File( - fileName.replace( - "//", - "/" - ) - ) - ) - } - - /** - * create a read-write config - * */ - @ToBeRemoved - fun load(file: File): Config { - if (!file.exists()) { - file.createNewFile() - } - return when (file.extension.toLowerCase()) { - "json" -> JsonConfig(file) - "yml" -> YamlConfig(file) - "yaml" -> YamlConfig(file) - "mirai" -> YamlConfig(file) - "ini" -> TomlConfig(file) - "toml" -> TomlConfig(file) - "properties" -> TomlConfig(file) - "property" -> TomlConfig(file) - "data" -> TomlConfig(file) - else -> error("Unsupported file config type ${file.extension.toLowerCase()}") - } - } - - /** - * create a read-only config - */ - @ToBeRemoved - fun load(content: String, type: String): Config { - return when (type.toLowerCase()) { - "json" -> JsonConfig(content) - "yml" -> YamlConfig(content) - "yaml" -> YamlConfig(content) - "mirai" -> YamlConfig(content) - "ini" -> TomlConfig(content) - "toml" -> TomlConfig(content) - "properties" -> TomlConfig(content) - "property" -> TomlConfig(content) - "data" -> TomlConfig(content) - else -> error("Unsupported file config type $content") - } - } - - /** - * create a read-only config - */ - @ToBeRemoved - fun load(inputStream: InputStream, type: String): Config { - return load(inputStream.readBytes().encodeToString(), type) - } - - } -} - - -@ToBeRemoved -fun File.loadAsConfig(): Config { - return Config.load(this) -} - -/* 最简单的代理 */ -@ToBeRemoved -inline operator fun Config.getValue(thisRef: Any?, property: KProperty<*>): T { - return smartCast(property) -} - -@ToBeRemoved -inline operator fun Config.setValue(thisRef: Any?, property: KProperty<*>, value: T) { - this[property.name] = value -} - -/* 带有默认值的代理 */ -@Suppress("unused") -@ToBeRemoved -inline fun Config.withDefault( - crossinline defaultValue: () -> T -): ReadWriteProperty { - return object : ReadWriteProperty { - override fun getValue(thisRef: Any, property: KProperty<*>): T { - if (this@withDefault.exist(property.name)) {//unsafe - return this@withDefault.smartCast(property) - } - return defaultValue() - } - - override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { - this@withDefault[property.name] = value - } - } -} - -/* 带有默认值且如果为空会写入的代理 */ -@Suppress("unused") -@ToBeRemoved -inline fun Config.withDefaultWrite( - noinline defaultValue: () -> T -): WithDefaultWriteLoader { - return WithDefaultWriteLoader( - T::class, - this, - defaultValue, - false - ) -} - -/* 带有默认值且如果为空会写入保存的代理 */ -@ToBeRemoved -inline fun Config.withDefaultWriteSave( - noinline defaultValue: () -> T -): WithDefaultWriteLoader { - return WithDefaultWriteLoader(T::class, this, defaultValue, true) -} - -@ToBeRemoved -class WithDefaultWriteLoader( - private val _class: KClass, - private val config: Config, - private val defaultValue: () -> T, - private val save: Boolean -) { - operator fun provideDelegate( - thisRef: Any, - prop: KProperty<*> - ): ReadWriteProperty { - val defaultValue by lazy { defaultValue.invoke() } - if (!config.contains(prop.name)) { - config[prop.name] = defaultValue - if (save) { - config.save() - } - } - return object : ReadWriteProperty { - override fun getValue(thisRef: Any, property: KProperty<*>): T { - if (config.exist(property.name)) {//unsafe - return config.smartCastInternal(property.name, _class) - } - return defaultValue - } - - override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { - config[property.name] = value - } - } - } -} - -@PublishedApi -internal inline fun Config.smartCast(property: KProperty<*>): T { - return smartCastInternal(property.name, T::class) -} - -@OptIn(ToBeRemoved::class) -@PublishedApi -@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST") -internal fun Config.smartCastInternal(propertyName: String, _class: KClass): T { - return when (_class) { - String::class -> this.getString(propertyName) - Int::class -> this.getInt(propertyName) - Float::class -> this.getFloat(propertyName) - Double::class -> this.getDouble(propertyName) - Long::class -> this.getLong(propertyName) - Boolean::class -> this.getBoolean(propertyName) - else -> when { - _class.isSubclassOf(ConfigSection::class) -> this.getConfigSection(propertyName) - _class == List::class || _class == MutableList::class -> { - val list = this.getList(propertyName) - return if (list.isEmpty()) { - list - } else { - when (list[0]!!::class) { - String::class -> getStringList(propertyName) - Int::class -> getIntList(propertyName) - Float::class -> getFloatList(propertyName) - Double::class -> getDoubleList(propertyName) - Long::class -> getLongList(propertyName) - //不去支持getConfigSectionList(propertyName) - // LinkedHashMap::class -> getConfigSectionList(propertyName)//faster approach - else -> { - //if(list[0]!! is ConfigSection || list[0]!! is Map<*,*>){ - // getConfigSectionList(propertyName) - //}else { - error("unsupported type" + list[0]!!::class) - //} - } - } - } as T - } - else -> { - error("unsupported type") - } - } - } as T -} - - -@ToBeRemoved -interface ConfigSection : Config, MutableMap { - companion object { - fun create(): ConfigSection { - return ConfigSectionImpl() - } - - fun new(): ConfigSection { - return this.create() - } - } - - override fun getConfigSection(key: String): ConfigSection { - val content = get(key) ?: throw NoSuchElementException(key) - if (content is ConfigSection) { - return content - } - @Suppress("UNCHECKED_CAST") - return ConfigSectionDelegation( - Collections.synchronizedMap( - (get(key) ?: throw NoSuchElementException(key)) as LinkedHashMap - ) - ) - } - - override fun getString(key: String): String { - return (get(key) ?: throw NoSuchElementException(key)).toString() - } - - override fun getInt(key: String): Int { - return (get(key) ?: throw NoSuchElementException(key)).toString().toInt() - } - - override fun getFloat(key: String): Float { - return (get(key) ?: throw NoSuchElementException(key)).toString().toFloat() - } - - override fun getBoolean(key: String): Boolean { - return (get(key) ?: throw NoSuchElementException(key)).toString().toBoolean() - } - - override fun getDouble(key: String): Double { - return (get(key) ?: throw NoSuchElementException(key)).toString().toDouble() - } - - override fun getLong(key: String): Long { - return (get(key) ?: throw NoSuchElementException(key)).toString().toLong() - } - - override fun getList(key: String): List<*> { - return ((get(key) ?: throw NoSuchElementException(key)) as List<*>) - } - - override fun getStringList(key: String): List { - return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).filterNotNull().map { it.toString() } - } - - override fun getIntList(key: String): List { - return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map { it.toString().toInt() } - } - - override fun getFloatList(key: String): List { - return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map { it.toString().toFloat() } - } - - override fun getDoubleList(key: String): List { - return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map { it.toString().toDouble() } - } - - override fun getLongList(key: String): List { - return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map { it.toString().toLong() } - } - - override fun getConfigSectionList(key: String): List { - return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map { - if (it is ConfigSection) { - it - } else { - @Suppress("UNCHECKED_CAST") - ConfigSectionDelegation( - Collections.synchronizedMap( - it as MutableMap - ) - ) - } - } - } - - override fun exist(key: String): Boolean { - return get(key) != null - } - - override fun setIfAbsent(key: String, value: T) { - putIfAbsent(key, value) - } - - override fun setIfAbsent(key: String, valueInitializer: Config.() -> T) { - if (this.exist(key)) { - put(key, valueInitializer.invoke(this)) - } - } -} - -@OptIn(ToBeRemoved::class) -internal inline fun ConfigSection.smartGet(key: String): T { - return this.smartCastInternal(key, T::class) -} - -@Serializable -@ToBeRemoved -open class ConfigSectionImpl : ConcurrentHashMap(), - - ConfigSection { - override fun set(key: String, value: Any) { - super.put(key, value) - } - - override operator fun get(key: String): Any? { - return super.get(key) - } - - @Suppress("RedundantOverride") - override fun contains(key: String): Boolean { - return super.contains(key) - } - - override fun exist(key: String): Boolean { - return containsKey(key) - } - - override fun asMap(): Map { - return this - } - - override fun save() { - - } -} - -@ToBeRemoved -open class ConfigSectionDelegation( - private val delegate: MutableMap -) : ConfigSection, MutableMap by delegate { - override fun set(key: String, value: Any) { - delegate[key] = value - } - - override fun contains(key: String): Boolean { - return delegate.containsKey(key) - } - - override fun asMap(): Map { - return delegate - } - - override fun save() { - - } -} - - -@ToBeRemoved -interface FileConfig : Config { - fun deserialize(content: String): ConfigSection - - fun serialize(config: ConfigSection): String -} - - -@MiraiInternalAPI -@ToBeRemoved -abstract class FileConfigImpl internal constructor( - private val rawContent: String -) : FileConfig, - ConfigSection { - - internal var file: File? = null - - - @Suppress("unused") - constructor(file: File) : this(file.readText()) { - this.file = file - } - - - private val content by lazy { - deserialize(rawContent) - } - - - override val size: Int get() = content.size - override val entries: MutableSet> get() = content.entries - override val keys: MutableSet get() = content.keys - override val values: MutableCollection get() = content.values - override fun containsKey(key: String): Boolean = content.containsKey(key) - override fun containsValue(value: Any): Boolean = content.containsValue(value) - override fun put(key: String, value: Any): Any? = content.put(key, value) - override fun isEmpty(): Boolean = content.isEmpty() - override fun putAll(from: Map) = content.putAll(from) - override fun clear() = content.clear() - override fun remove(key: String): Any? = content.remove(key) - - override fun save() { - if (isReadOnly) { - error("Config is readonly") - } - if (!((file?.exists())!!)) { - file?.createNewFile() - } - file?.writeText(serialize(content)) - } - - val isReadOnly: Boolean get() = file == null - - override fun contains(key: String): Boolean { - return content.contains(key) - } - - override fun get(key: String): Any? { - return content[key] - } - - override fun set(key: String, value: Any) { - content[key] = value - } - - override fun asMap(): Map { - return content.asMap() - } - -} - -@ToBeRemoved -@OptIn(MiraiInternalAPI::class) -class JsonConfig internal constructor( - content: String -) : FileConfigImpl(content) { - constructor(file: File) : this(file.readText()) { - this.file = file - } - - @UnstableDefault - override fun deserialize(content: String): ConfigSection { - if (content.isEmpty() || content.isBlank() || content == "{}") { - return ConfigSectionImpl() - } - val gson = Gson() - val typeRef = object : TypeToken>() {}.type - return ConfigSectionDelegation( - gson.fromJson(content, typeRef) - ) - } - - @UnstableDefault - override fun serialize(config: ConfigSection): String { - val gson = Gson() - return gson.toJson(config.toMap()) - } -} - -@ToBeRemoved -@OptIn(MiraiInternalAPI::class) -class YamlConfig internal constructor(content: String) : FileConfigImpl(content) { - constructor(file: File) : this(file.readText()) { - this.file = file - } - - override fun deserialize(content: String): ConfigSection { - if (content.isEmpty() || content.isBlank()) { - return ConfigSectionImpl() - } - return ConfigSectionDelegation( - Collections.synchronizedMap( - Yaml().load(content) as LinkedHashMap - ) - ) - } - - override fun serialize(config: ConfigSection): String { - return Yaml().dumpAsMap(config) - } - -} - -@ToBeRemoved -@OptIn(MiraiInternalAPI::class) -class TomlConfig internal constructor(content: String) : FileConfigImpl(content) { - constructor(file: File) : this(file.readText()) { - this.file = file - } - - override fun deserialize(content: String): ConfigSection { - if (content.isEmpty() || content.isBlank()) { - return ConfigSectionImpl() - } - return ConfigSectionDelegation( - Collections.synchronizedMap( - Toml().read(content).toMap() - ) - ) - - } - - override fun serialize(config: ConfigSection): String { - return TomlWriter().write(config) - } -} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt index f97eb092b..61c11d4b4 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginBase.kt @@ -70,14 +70,6 @@ abstract class PluginBase } - /** - * 加载一个 [dataFolder] 中的 [Config] - */ - fun loadConfig(fileName: String): Config { - @OptIn(ToBeRemoved::class) - return Config.load(dataFolder.absolutePath + "/" + fileName) - } - /** * 插件的日志 */ @@ -110,16 +102,6 @@ abstract class PluginBase } } - /** - * 加载 resource 中的 [Config] - * 这个 [Config] 是只读的 - */ - @ToBeRemoved - fun getResourcesConfig(fileName: String): Config { - require(fileName.contains(".")) { "Unknown Config Type" } - @OptIn(ToBeRemoved::class) - return Config.load(getResources(fileName) ?: error("No such file: $fileName"), fileName.substringAfter('.')) - } /** * Java API Scheduler diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt new file mode 100644 index 000000000..121fa563f --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt @@ -0,0 +1,123 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("NOTHING_TO_INLINE") + +package net.mamoe.mirai.console.setting + +import kotlinx.serialization.* +import net.mamoe.yamlkt.Yaml +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty +import kotlin.reflect.full.findAnnotation + +internal inline fun KSerializer.bind( + crossinline setter: (E) -> Unit, + crossinline getter: () -> E +): KSerializer { + return object : KSerializer { + override val descriptor: SerialDescriptor get() = this@bind.descriptor + override fun deserialize(decoder: Decoder): E = this@bind.deserialize(decoder).also { setter(it) } + + @Suppress("UNCHECKED_CAST") + override fun serialize(encoder: Encoder, value: E) = + this@bind.serialize(encoder, getter()) + } +} + +typealias SerialName = kotlinx.serialization.SerialName + +/** + * 配置的基类 + */ +abstract class Setting { + open val serialName: String + get() = this::class.findAnnotation()?.value + ?: this::class.qualifiedName + ?: error("Names should be assigned to anonymous classes manually by overriding serialName") + + + @JvmSynthetic + operator fun Value.provideDelegate( + thisRef: Setting, + property: KProperty<*> + ): ReadWriteProperty { + if (built) error("The Setting is already serialized so it's structure is immutable.") + valueList.add(this to property) + return this + } + + @JvmField + internal var valueList: MutableList, KProperty<*>>> = mutableListOf() + private var built: Boolean = false + + internal val serializer: KSerializer by lazy { + built = true + SettingSerializer(this) + } + + internal fun onElementChanged(value: Value<*>) { + println("my value changed!") + } +} + +internal class SettingSerializer( + private val instance: Setting +) : KSerializer { + override val descriptor: SerialDescriptor by lazy { + SerialDescriptor(instance.serialName) { + for ((value, property) in instance.valueList) { + element(property.serialNameOrPropertyName, value.serializer.descriptor, annotations, true) + } + } + } + + @Suppress("UNCHECKED_CAST") // erased, no problem. + override fun deserialize(decoder: Decoder): SettingSerializerMark = decoder.decodeStructure(descriptor) { + if (this.decodeSequentially()) { + instance.valueList.forEachIndexed { index, (value, _) -> + this.decodeSerializableElement( + value.serializer.descriptor, + index, + value.serializer as KSerializer + ) + } + } else { + while (true) { + val index = this.decodeElementIndex(descriptor) + if (index == CompositeDecoder.READ_DONE) return@decodeStructure SettingSerializerMark + val value = instance.valueList[index].first + + this.decodeSerializableElement( + descriptor, + index, + value.serializer + ) + } + } + SettingSerializerMark + } + + override fun serialize(encoder: Encoder, value: SettingSerializerMark) = encoder.encodeStructure(descriptor) { + instance.valueList.forEachIndexed { index, (value, _) -> + @Suppress("UNCHECKED_CAST") // erased, no problem. + this.encodeSerializableElement( + descriptor, + index, + value.serializer as KSerializer, + value.value + ) + } + } + +} + +internal object SettingSerializerMark + +internal val KProperty<*>.serialNameOrPropertyName: String get() = this.findAnnotation()?.value ?: this.name \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt new file mode 100644 index 000000000..989ece08b --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.setting + +object ValueSerializerMark + +/* + * More generic ones + */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Setting.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Setting.kt new file mode 100644 index 000000000..83dbe7345 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Setting.kt @@ -0,0 +1,55 @@ + +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + + +package net.mamoe.mirai.console.setting + +/** + * !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.SettingValueUseSiteCodegen.kt + * !!! DO NOT MODIFY THIS FILE MANUALLY + */ + + +fun Setting.value(default: Int): IntValue = valueImpl(default) +fun Setting.value(default: Short): ShortValue = valueImpl(default) +fun Setting.value(default: Byte): ByteValue = valueImpl(default) +fun Setting.value(default: Long): LongValue = valueImpl(default) +fun Setting.value(default: Float): FloatValue = valueImpl(default) +fun Setting.value(default: Double): DoubleValue = valueImpl(default) +fun Setting.value(default: Boolean): BooleanValue = valueImpl(default) +fun Setting.value(default: Char): CharValue = valueImpl(default) +fun Setting.value(default: String): StringValue = valueImpl(default) +fun Setting.value(default: IntArray): IntArrayValue = valueImpl(default) +fun Setting.value(default: ShortArray): ShortArrayValue = valueImpl(default) +fun Setting.value(default: ByteArray): ByteArrayValue = valueImpl(default) +fun Setting.value(default: LongArray): LongArrayValue = valueImpl(default) +fun Setting.value(default: FloatArray): FloatArrayValue = valueImpl(default) +fun Setting.value(default: DoubleArray): DoubleArrayValue = valueImpl(default) +fun Setting.value(default: BooleanArray): BooleanArrayValue = valueImpl(default) +fun Setting.value(default: CharArray): CharArrayValue = valueImpl(default) +fun Setting.value(default: Array): TypedIntArrayValue = valueImpl(default) +fun Setting.value(default: Array): TypedShortArrayValue = valueImpl(default) +fun Setting.value(default: Array): TypedByteArrayValue = valueImpl(default) +fun Setting.value(default: Array): TypedLongArrayValue = valueImpl(default) +fun Setting.value(default: Array): TypedFloatArrayValue = valueImpl(default) +fun Setting.value(default: Array): TypedDoubleArrayValue = valueImpl(default) +fun Setting.value(default: Array): TypedBooleanArrayValue = valueImpl(default) +fun Setting.value(default: Array): TypedCharArrayValue = valueImpl(default) +fun Setting.value(default: Array): TypedStringArrayValue = valueImpl(default) +fun Setting.value(default: List): IntListValue = valueImpl(default) +fun Setting.value(default: List): ShortListValue = valueImpl(default) +fun Setting.value(default: List): ByteListValue = valueImpl(default) +fun Setting.value(default: List): LongListValue = valueImpl(default) +fun Setting.value(default: List): FloatListValue = valueImpl(default) +fun Setting.value(default: List): DoubleListValue = valueImpl(default) +fun Setting.value(default: List): BooleanListValue = valueImpl(default) +fun Setting.value(default: List): CharListValue = valueImpl(default) +fun Setting.value(default: List): StringListValue = valueImpl(default) + diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Value.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Value.kt new file mode 100644 index 000000000..3e052566a --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Value.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.setting + +import kotlinx.serialization.KSerializer +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/** + * !!! These primitive types are auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.ValuesCodegen.kt + * !!! for better performance + * !!! DO NOT MODIFY THIS FILE MANUALLY + */ + +sealed class Value : ReadWriteProperty { + abstract var value: T + + abstract val serializer: KSerializer + override fun getValue(thisRef: Setting, property: KProperty<*>): T = value + override fun setValue(thisRef: Setting, property: KProperty<*>, value: T) { + this.value = value + } +} + +sealed class PrimitiveValue : Value() + +sealed class NumberValue : Value() +abstract class IntValue internal constructor() : NumberValue() +abstract class ShortValue internal constructor() : NumberValue() +abstract class ByteValue internal constructor() : NumberValue() +abstract class LongValue internal constructor() : NumberValue() +abstract class FloatValue internal constructor() : NumberValue() +abstract class DoubleValue internal constructor() : NumberValue() + +abstract class BooleanValue internal constructor() : PrimitiveValue() +abstract class CharValue internal constructor() : PrimitiveValue() +abstract class StringValue internal constructor() : PrimitiveValue() + +// T can be primitive array or typed Array +sealed class ArrayValue : Value() +sealed class PrimitiveArrayValue : ArrayValue() + +abstract class IntArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { + override fun iterator(): Iterator = this.value.iterator() +} +abstract class ShortArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { + override fun iterator(): Iterator = this.value.iterator() +} +abstract class ByteArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { + override fun iterator(): Iterator = this.value.iterator() +} +abstract class LongArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { + override fun iterator(): Iterator = this.value.iterator() +} +abstract class FloatArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { + override fun iterator(): Iterator = this.value.iterator() +} +abstract class DoubleArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { + override fun iterator(): Iterator = this.value.iterator() +} +abstract class BooleanArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { + override fun iterator(): Iterator = this.value.iterator() +} +abstract class CharArrayValue internal constructor() : PrimitiveArrayValue(), Iterable { + override fun iterator(): Iterator = this.value.iterator() +} + +sealed class TypedPrimitiveArrayValue : ArrayValue>() , Iterable{ + override fun iterator() = this.value.iterator() +} + +abstract class TypedIntArrayValue internal constructor() : TypedPrimitiveArrayValue() +abstract class TypedShortArrayValue internal constructor() : TypedPrimitiveArrayValue() +abstract class TypedByteArrayValue internal constructor() : TypedPrimitiveArrayValue() +abstract class TypedLongArrayValue internal constructor() : TypedPrimitiveArrayValue() +abstract class TypedFloatArrayValue internal constructor() : TypedPrimitiveArrayValue() +abstract class TypedDoubleArrayValue internal constructor() : TypedPrimitiveArrayValue() +abstract class TypedBooleanArrayValue internal constructor() : TypedPrimitiveArrayValue() +abstract class TypedCharArrayValue internal constructor() : TypedPrimitiveArrayValue() +abstract class TypedStringArrayValue internal constructor() : TypedPrimitiveArrayValue() + +sealed class ListValue : Value>(), Iterable{ + override fun iterator() = this.value.iterator() +} +abstract class IntListValue internal constructor() : ListValue() +abstract class ShortListValue internal constructor() : ListValue() +abstract class ByteListValue internal constructor() : ListValue() +abstract class LongListValue internal constructor() : ListValue() +abstract class FloatListValue internal constructor() : ListValue() +abstract class DoubleListValue internal constructor() : ListValue() +abstract class BooleanListValue internal constructor() : ListValue() +abstract class CharListValue internal constructor() : ListValue() +abstract class StringListValue internal constructor() : ListValue() + diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_ValueImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_ValueImpl.kt new file mode 100644 index 000000000..cc8b445aa --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_ValueImpl.kt @@ -0,0 +1,652 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + + +package net.mamoe.mirai.console.setting + +import kotlinx.serialization.builtins.* + + +/** + * !!! This file is auto-generated by test/kotlin/net.mamoe.mirai.console.SettingsCodegen.kt + * !!! DO NOT MODIFY THIS FILE MANUALLY + */ + + +internal fun Setting.valueImpl(default: Int): IntValue { + return object : IntValue() { + private var internalValue: Int = default + override var value: Int + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = Int.serializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Short): ShortValue { + return object : ShortValue() { + private var internalValue: Short = default + override var value: Short + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = Short.serializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Byte): ByteValue { + return object : ByteValue() { + private var internalValue: Byte = default + override var value: Byte + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = Byte.serializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Long): LongValue { + return object : LongValue() { + private var internalValue: Long = default + override var value: Long + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = Long.serializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Float): FloatValue { + return object : FloatValue() { + private var internalValue: Float = default + override var value: Float + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = Float.serializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Double): DoubleValue { + return object : DoubleValue() { + private var internalValue: Double = default + override var value: Double + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = Double.serializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Boolean): BooleanValue { + return object : BooleanValue() { + private var internalValue: Boolean = default + override var value: Boolean + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = Boolean.serializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Char): CharValue { + return object : CharValue() { + private var internalValue: Char = default + override var value: Char + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = Char.serializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: String): StringValue { + return object : StringValue() { + private var internalValue: String = default + override var value: String + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = String.serializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: IntArray): IntArrayValue { + return object : IntArrayValue() { + private var internalValue: IntArray = default + override var value: IntArray + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = IntArraySerializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: ShortArray): ShortArrayValue { + return object : ShortArrayValue() { + private var internalValue: ShortArray = default + override var value: ShortArray + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ShortArraySerializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: ByteArray): ByteArrayValue { + return object : ByteArrayValue() { + private var internalValue: ByteArray = default + override var value: ByteArray + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ByteArraySerializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: LongArray): LongArrayValue { + return object : LongArrayValue() { + private var internalValue: LongArray = default + override var value: LongArray + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = LongArraySerializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: FloatArray): FloatArrayValue { + return object : FloatArrayValue() { + private var internalValue: FloatArray = default + override var value: FloatArray + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = FloatArraySerializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: DoubleArray): DoubleArrayValue { + return object : DoubleArrayValue() { + private var internalValue: DoubleArray = default + override var value: DoubleArray + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = DoubleArraySerializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: BooleanArray): BooleanArrayValue { + return object : BooleanArrayValue() { + private var internalValue: BooleanArray = default + override var value: BooleanArray + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = BooleanArraySerializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: CharArray): CharArrayValue { + return object : CharArrayValue() { + private var internalValue: CharArray = default + override var value: CharArray + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = CharArraySerializer().bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Array): TypedIntArrayValue { + return object : TypedIntArrayValue() { + private var internalValue: Array = default + override var value: Array + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ArraySerializer(Int.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Array): TypedShortArrayValue { + return object : TypedShortArrayValue() { + private var internalValue: Array = default + override var value: Array + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ArraySerializer(Short.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Array): TypedByteArrayValue { + return object : TypedByteArrayValue() { + private var internalValue: Array = default + override var value: Array + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ArraySerializer(Byte.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Array): TypedLongArrayValue { + return object : TypedLongArrayValue() { + private var internalValue: Array = default + override var value: Array + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ArraySerializer(Long.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Array): TypedFloatArrayValue { + return object : TypedFloatArrayValue() { + private var internalValue: Array = default + override var value: Array + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ArraySerializer(Float.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Array): TypedDoubleArrayValue { + return object : TypedDoubleArrayValue() { + private var internalValue: Array = default + override var value: Array + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ArraySerializer(Double.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Array): TypedBooleanArrayValue { + return object : TypedBooleanArrayValue() { + private var internalValue: Array = default + override var value: Array + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ArraySerializer(Boolean.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Array): TypedCharArrayValue { + return object : TypedCharArrayValue() { + private var internalValue: Array = default + override var value: Array + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ArraySerializer(Char.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: Array): TypedStringArrayValue { + return object : TypedStringArrayValue() { + private var internalValue: Array = default + override var value: Array + get() = internalValue + set(new) { + if (!new.contentEquals(internalValue)) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ArraySerializer(String.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: List): IntListValue { + return object : IntListValue() { + private var internalValue: List = default + override var value: List + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ListSerializer(Int.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: List): ShortListValue { + return object : ShortListValue() { + private var internalValue: List = default + override var value: List + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ListSerializer(Short.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: List): ByteListValue { + return object : ByteListValue() { + private var internalValue: List = default + override var value: List + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ListSerializer(Byte.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: List): LongListValue { + return object : LongListValue() { + private var internalValue: List = default + override var value: List + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ListSerializer(Long.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: List): FloatListValue { + return object : FloatListValue() { + private var internalValue: List = default + override var value: List + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ListSerializer(Float.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: List): DoubleListValue { + return object : DoubleListValue() { + private var internalValue: List = default + override var value: List + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ListSerializer(Double.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: List): BooleanListValue { + return object : BooleanListValue() { + private var internalValue: List = default + override var value: List + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ListSerializer(Boolean.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: List): CharListValue { + return object : CharListValue() { + private var internalValue: List = default + override var value: List + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ListSerializer(Char.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + +internal fun Setting.valueImpl(default: List): StringListValue { + return object : StringListValue() { + private var internalValue: List = default + override var value: List + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = ListSerializer(String.serializer()).bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} + + diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotHelper.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotHelper.kt index 1c01500c2..2746a1672 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotHelper.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotHelper.kt @@ -12,7 +12,6 @@ package net.mamoe.mirai.console.utils import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.plugins.* -import net.mamoe.mirai.console.utils.BotManagers.BOT_MANAGERS import net.mamoe.mirai.contact.User import java.io.File @@ -23,12 +22,6 @@ import java.io.File val User.isManager: Boolean get() = this.bot.managers.contains(this.id) -@OptIn(ToBeRemoved::class) -internal object BotManagers { - val config = File("${MiraiConsole.path}/bot.yml").loadAsConfig() - val BOT_MANAGERS: ConfigSection by config.withDefaultWriteSave { ConfigSectionImpl() } -} - @JvmName("addManager") @JvmSynthetic @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @@ -36,31 +29,18 @@ fun Bot.addManagerDeprecated(long: Long) { addManager(long) } -@OptIn(ToBeRemoved::class) internal fun Bot.addManager(long: Long): Boolean { - BOT_MANAGERS.putIfAbsent(this.id.toString(), mutableListOf()) - BOT_MANAGERS[this.id.toString()] = - (BOT_MANAGERS.getLongList(this.id.toString()) as MutableList).apply { - if (contains(long)) return@addManager false - add(long) - } - BotManagers.config.save() + TODO() return true } -@OptIn(ToBeRemoved::class) fun Bot.removeManager(long: Long) { - BOT_MANAGERS.putIfAbsent(this.id.toString(), mutableListOf()) - BOT_MANAGERS[this.id.toString()] = - (BOT_MANAGERS.getLongList(this.id.toString()) as MutableList).apply { remove(long) } - BotManagers.config.save() + TODO() } val Bot.managers: List - @OptIn(ToBeRemoved::class) get() { - BOT_MANAGERS.putIfAbsent(this.id.toString(), mutableListOf()) - return BOT_MANAGERS.getLongList(this.id.toString()) + TODO() } fun Bot.checkManager(long: Long): Boolean { diff --git a/settings.gradle b/settings.gradle index 8055fef21..d63876b77 100644 --- a/settings.gradle +++ b/settings.gradle @@ -28,6 +28,9 @@ def onlyBackEnd = true include(':mirai-console') project(':mirai-console').dir = file("backend/mirai-console") +include(':codegen') +project(':codegen').dir = file("backend/codegen") + if (!onlyBackEnd) { From 0e25b1f46f802b0082dcc853d01609b0037562b2 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 16 May 2020 13:07:23 +0800 Subject: [PATCH 6/6] Add derived types codegen & value impl --- .../mirai/console/codegen/ValuesCodegen.kt | 44 ++++++++++++++++++- .../mamoe/mirai/console/setting/Setting.kt | 43 ++++++++++++++++-- .../net/mamoe/mirai/console/setting/_Value.kt | 38 +++++++++++++++- 3 files changed, 118 insertions(+), 7 deletions(-) diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt index 4d75b33d2..7139245b0 100644 --- a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt +++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt @@ -6,8 +6,7 @@ * * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("ClassName") - +@file:Suppress("ClassName", "unused") package net.mamoe.mirai.console.codegen import org.intellij.lang.annotations.Language @@ -196,4 +195,45 @@ sealed class Value : ReadWriteProperty { } appendln() + + // FOR COMPLEX TYPES + + appendln( + """ + abstract class SettingValue internal constructor() : Value() + + internal fun Setting.valueImpl(default: T): Value { + return object : SettingValue() { + private var internalValue: T = default + override var value: T + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = object : KSerializer{ + override val descriptor: SerialDescriptor + get() = internalValue.updaterSerializer.descriptor + + override fun deserialize(decoder: Decoder): T { + internalValue.updaterSerializer.deserialize(decoder) + return internalValue + } + + override fun serialize(encoder: Encoder, value: T) { + internalValue.updaterSerializer.serialize(encoder, SettingSerializerMark) + } + + }.bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } + } + """ + ) + + appendln() } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt index 121fa563f..c03ff53e9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt @@ -13,6 +13,9 @@ package net.mamoe.mirai.console.setting import kotlinx.serialization.* import net.mamoe.yamlkt.Yaml +import net.mamoe.yamlkt.YamlConfiguration +import net.mamoe.yamlkt.YamlConfiguration.ListSerialization.FLOW_SEQUENCE +import net.mamoe.yamlkt.YamlConfiguration.MapSerialization.FLOW_MAP import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty import kotlin.reflect.full.findAnnotation @@ -57,17 +60,44 @@ abstract class Setting { internal var valueList: MutableList, KProperty<*>>> = mutableListOf() private var built: Boolean = false - internal val serializer: KSerializer by lazy { + internal val updaterSerializer: KSerializer by lazy { built = true - SettingSerializer(this) + SettingUpdaterSerializer(this) + } + + internal val kotlinSerializer: KSerializer by lazy { + object : KSerializer { + override val descriptor: SerialDescriptor + get() = this@Setting.updaterSerializer.descriptor + + override fun deserialize(decoder: Decoder): Setting { + this@Setting.updaterSerializer.deserialize(decoder) + return this@Setting + } + + override fun serialize(encoder: Encoder, value: Setting) { + this@Setting.updaterSerializer.serialize(encoder, SettingSerializerMark) + } + } } internal fun onElementChanged(value: Value<*>) { println("my value changed!") } + + companion object { + private val yaml = + Yaml(configuration = YamlConfiguration(classSerialization = FLOW_MAP, listSerialization = FLOW_SEQUENCE)) + } + + override fun toString(): String = yaml.stringify(this.serializer, this) } -internal class SettingSerializer( +@Suppress("UNCHECKED_CAST") +val T.serializer: KSerializer + get() = kotlinSerializer as KSerializer + +internal class SettingUpdaterSerializer( private val instance: Setting ) : KSerializer { override val descriptor: SerialDescriptor by lazy { @@ -120,4 +150,9 @@ internal class SettingSerializer( internal object SettingSerializerMark -internal val KProperty<*>.serialNameOrPropertyName: String get() = this.findAnnotation()?.value ?: this.name \ No newline at end of file +internal val KProperty<*>.serialNameOrPropertyName: String get() = this.findAnnotation()?.value ?: this.name + + +fun Setting.value(default: T): Value = valueImpl(default) +inline fun Setting.value(default: T, crossinline initializer: T.() -> Unit): Value = + value(default).also { it.value.apply(initializer) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Value.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Value.kt index 3e052566a..1ffe99de5 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Value.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Value.kt @@ -9,12 +9,15 @@ package net.mamoe.mirai.console.setting +import kotlinx.serialization.Decoder +import kotlinx.serialization.Encoder import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialDescriptor import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty /** - * !!! These primitive types are auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.ValuesCodegen.kt + * !!! These primitive types are auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.ValuesCodegen.kt * !!! for better performance * !!! DO NOT MODIFY THIS FILE MANUALLY */ @@ -89,6 +92,7 @@ abstract class TypedStringArrayValue internal constructor() : TypedPrimitiveArra sealed class ListValue : Value>(), Iterable{ override fun iterator() = this.value.iterator() } + abstract class IntListValue internal constructor() : ListValue() abstract class ShortListValue internal constructor() : ListValue() abstract class ByteListValue internal constructor() : ListValue() @@ -99,3 +103,35 @@ abstract class BooleanListValue internal constructor() : ListValue() abstract class CharListValue internal constructor() : ListValue() abstract class StringListValue internal constructor() : ListValue() +abstract class SettingValue internal constructor() : Value() + +internal fun Setting.valueImpl(default: T): Value { + return object : SettingValue() { + private var internalValue: T = default + override var value: T + get() = internalValue + set(new) { + if (new != internalValue) { + internalValue = new + onElementChanged(this) + } + } + override val serializer = object : KSerializer { + override val descriptor: SerialDescriptor + get() = internalValue.updaterSerializer.descriptor + + override fun deserialize(decoder: Decoder): T { + internalValue.updaterSerializer.deserialize(decoder) + return internalValue + } + + override fun serialize(encoder: Encoder, value: T) { + internalValue.updaterSerializer.serialize(encoder, SettingSerializerMark) + } + + }.bind( + getter = { internalValue }, + setter = { internalValue = it } + ) + } +} \ No newline at end of file