diff --git a/backend/codegen/build.gradle.kts b/backend/codegen/build.gradle.kts index 574abfe7f..78077df4a 100644 --- a/backend/codegen/build.gradle.kts +++ b/backend/codegen/build.gradle.kts @@ -1,8 +1,6 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { - id("kotlin") - kotlin("plugin.serialization") + kotlin("jvm") version "1.4-M2" + kotlin("plugin.serialization") version "1.4-M2" id("java") } @@ -10,8 +8,10 @@ kotlin { sourceSets { all { languageSettings.useExperimentalAnnotation("kotlin.Experimental") - languageSettings.useExperimentalAnnotation("kotlin.OptIn") + languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn") languageSettings.progressiveMode = true + languageSettings.languageVersion = "1.4" + languageSettings.apiVersion = "1.4" languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI") languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes") languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference") @@ -21,21 +21,5 @@ kotlin { } 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" + api(kotlin("stdlib-jdk8")) } \ No newline at end of file diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/Codegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/Codegen.kt new file mode 100644 index 000000000..cc1486ef2 --- /dev/null +++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/Codegen.kt @@ -0,0 +1,98 @@ +/* + * 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("FunctionName", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + +package net.mamoe.mirai.console.codegen + +import org.intellij.lang.annotations.Language + +abstract class Replacer(private val name: String) : (String) -> String { + override fun toString(): String { + return name + } +} + +fun Codegen.Replacer(block: (String) -> String): Replacer { + return object : Replacer(this@Replacer::class.simpleName ?: "") { + override fun invoke(p1: String): String = block(p1) + } +} + +class CodegenScope : MutableList by mutableListOf() { + fun applyTo(fileContent: String): String { + return this.fold(fileContent) { acc, replacer -> replacer(acc) } + } + + @CodegenDsl + operator fun Codegen.invoke(vararg ktTypes: KtType) { + if (ktTypes.isEmpty() && this is DefaultInvoke) { + invoke(defaultInvokeArgs) + } + invoke(ktTypes.toList()) + } + + @CodegenDsl + operator fun Codegen.invoke(ktTypes: Collection) { + add(Replacer { + it + buildString { + ktTypes.forEach { applyTo(this, it) } + } + }) + } + + @RegionCodegenDsl + operator fun RegionCodegen.invoke(vararg ktTypes: KtType) = invoke(ktTypes.toList()) + + @RegionCodegenDsl + operator fun RegionCodegen.invoke(ktTypes: Collection) { + add(Replacer { + it.replace(Regex("""//// region $regionName CODEGEN ////([\s\S]*?)//// endregion $regionName CODEGEN ////""")) { + val code = CodegenScope().apply { (this@invoke as Codegen).invoke(*ktTypes.toTypedArray()) }.applyTo("") + """ + |//// region $regionName CODEGEN //// + | + |$code + | + |//// endregion $regionName CODEGEN //// + """.trimMargin() + } + }) + } + + @DslMarker + annotation class CodegenDsl +} + +@DslMarker +annotation class RegionCodegenDsl + +interface DefaultInvoke { + val defaultInvokeArgs: List +} + +abstract class Codegen { + fun applyTo(stringBuilder: StringBuilder, ktType: KtType) = this.run { stringBuilder.apply(ktType) } + + protected abstract fun StringBuilder.apply(ktType: KtType) +} + +abstract class RegionCodegen(regionName: String? = null) : Codegen() { + val regionName: String by lazy { + regionName ?: this::class.simpleName!!.substringBefore("Codegen") + } +} + +abstract class PrimitiveCodegen : Codegen() { + protected abstract fun StringBuilder.apply(ktType: KtPrimitive) + + fun StringBuilder.apply(ktType: List) = ktType.forEach { apply(it) } +} + +fun StringBuilder.appendKCode(@Language("kt") ktCode: String): StringBuilder = append(kCode(ktCode)).appendLine() diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/JSettingCodegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/JSettingCodegen.kt index fff3f18ae..885a7e898 100644 --- a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/JSettingCodegen.kt +++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/JSettingCodegen.kt @@ -46,35 +46,37 @@ fun JClazz.getTemplate():String = """ fun main(){ println(buildString { - appendln(COPYRIGHT) - appendln() - appendln(FILE_SUPPRESS) - appendln() - appendln("/**\n" + - " * !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.JSettingCodegen.kt\n" + - " * !!! DO NOT MODIFY THIS FILE MANUALLY\n" + - " */\n" + - "\"\"\"") - appendln() - appendln() + appendLine(COPYRIGHT) + appendLine() + appendLine(FILE_SUPPRESS) + appendLine() + appendLine( + "/**\n" + + " * !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.JSettingCodegen.kt\n" + + " * !!! DO NOT MODIFY THIS FILE MANUALLY\n" + + " */\n" + + "\"\"\"" + ) + appendLine() + appendLine() //do simplest (J_EXTRA + J_NUMBERS).forEach { - appendln(it.getTemplate()) + appendLine(it.getTemplate()) } (J_EXTRA + J_NUMBERS).forEach { - appendln(JListClazz(it).getTemplate()) + appendLine(JListClazz(it).getTemplate()) } (J_EXTRA + J_NUMBERS).forEach { - appendln(JArrayClazz(it).getTemplate()) + appendLine(JArrayClazz(it).getTemplate()) } (J_EXTRA + J_NUMBERS).forEach {key -> (J_EXTRA + J_NUMBERS).forEach { value -> - appendln(JMapClazz(key, value).getTemplate()) + appendLine(JMapClazz(key, value).getTemplate()) } } }) 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 index 36af092a4..561bb40c9 100644 --- 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 @@ -19,19 +19,19 @@ fun main() { File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Setting.kt").apply { createNewFile() }.writeText(buildString { - appendln(COPYRIGHT) - appendln() - appendln(FILE_SUPPRESS) - appendln() - appendln(PACKAGE) - appendln() - appendln(IMPORTS) - appendln() - appendln() - appendln(DO_NOT_MODIFY) - appendln() - appendln() - appendln(genAllValueUseSite()) + appendLine(COPYRIGHT) + appendLine() + appendLine(FILE_SUPPRESS) + appendLine() + appendLine(PACKAGE) + appendLine() + appendLine(IMPORTS) + appendLine() + appendLine() + appendLine(DO_NOT_MODIFY) + appendLine() + appendLine() + appendLine(genAllValueUseSite()) }) } @@ -56,7 +56,7 @@ import kotlin.internal.LowPriorityInOverloadResolution fun genAllValueUseSite(): String = buildString { fun appendln(@Language("kt") code: String) { - this.appendln(code.trimIndent()) + this.appendLine(code.trimIndent()) } // PRIMITIVE for (number in NUMBERS + OTHER_PRIMITIVES) { @@ -83,7 +83,7 @@ fun genAllValueUseSite(): String = buildString { // MUTABLE LIST / MUTABLE SET for (collectionName in listOf("List", "Set")) { for (number in NUMBERS + OTHER_PRIMITIVES) { - appendln() + appendLine() appendln( """ @JvmName("valueMutable") @@ -94,7 +94,7 @@ fun genAllValueUseSite(): String = buildString { } // SPECIAL - appendln() + appendLine() appendln( """ fun Setting.value(default: T): Value { 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 index b4c0014a3..f9e94702f 100644 --- 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 @@ -19,17 +19,17 @@ fun main() { File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_ValueImpl.kt").apply { createNewFile() }.writeText(buildString { - appendln(COPYRIGHT) - appendln() - appendln(PACKAGE) - appendln() - appendln(IMPORTS) - appendln() - appendln() - appendln(DO_NOT_MODIFY) - appendln() - appendln() - appendln(genAllValueImpl()) + appendLine(COPYRIGHT) + appendLine() + appendLine(PACKAGE) + appendLine() + appendLine(IMPORTS) + appendLine() + appendLine() + appendLine(DO_NOT_MODIFY) + appendLine() + appendLine() + appendLine(genAllValueImpl()) }) } @@ -52,19 +52,19 @@ import net.mamoe.mirai.console.setting.* fun genAllValueImpl(): String = buildString { fun appendln(@Language("kt") code: String) { - this.appendln(code.trimIndent()) + this.appendLine(code.trimIndent()) } // PRIMITIVE for (number in NUMBERS + OTHER_PRIMITIVES) { appendln(genPrimitiveValueImpl(number, number, "$number.serializer()", false)) - appendln() + appendLine() } // PRIMITIVE ARRAYS for (number in NUMBERS + OTHER_PRIMITIVES.filterNot { it == "String" }) { appendln(genPrimitiveValueImpl("${number}Array", "${number}Array", "${number}ArraySerializer()", true)) - appendln() + appendLine() } // TYPED ARRAYS @@ -77,7 +77,7 @@ fun genAllValueImpl(): String = buildString { true ) ) - appendln() + appendLine() } // PRIMITIVE LISTS / SETS @@ -92,11 +92,11 @@ fun genAllValueImpl(): String = buildString { false ) ) - appendln() + appendLine() } } - appendln() + appendLine() // MUTABLE LIST / MUTABLE SET @@ -141,11 +141,11 @@ fun genAllValueImpl(): String = buildString { } """ ) - appendln() + appendLine() } } - appendln() + appendLine() appendln( diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueKtCodegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueKtCodegen.kt new file mode 100644 index 000000000..5f44ba567 --- /dev/null +++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueKtCodegen.kt @@ -0,0 +1,41 @@ +/* + * 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 + + +object ValueKtCodegen { + object SettingCodegen { + object PrimitiveValuesCodegen : RegionCodegen(), DefaultInvoke { + override val defaultInvokeArgs: List + get() = KtType.Primitives + KtString + + override fun StringBuilder.apply(ktType: KtType) { + @Suppress("ClassName") + appendKCode( + """ + /** + * Represents a non-null [$ktType] value. + */ + interface ${ktType}Value : PrimitiveValue<$ktType> + """ + ) + } + } + + } + + @JvmStatic + fun main(args: Array) { + codegen("Value.kt") { + SettingCodegen.PrimitiveValuesCodegen() + } + } +} + 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 018f7242e..4920d6754 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 @@ -56,11 +56,11 @@ internal val OTHER_PRIMITIVES = listOf( fun genPublicApi() = buildString { fun appendln(@Language("kt") code: String) { - this.appendln(code.trimIndent()) + this.appendLine(code.trimIndent()) } appendln(COPYRIGHT.trim()) - appendln() + appendLine() appendln( """ package net.mamoe.mirai.console.setting @@ -70,7 +70,7 @@ fun genPublicApi() = buildString { import kotlin.reflect.KProperty """ ) - appendln() + appendLine() appendln( """ /** @@ -80,7 +80,7 @@ fun genPublicApi() = buildString { */ """ ) - appendln() + appendLine() appendln( """ @@ -107,7 +107,7 @@ sealed class Value : ReadWriteProperty { } """ ) - appendln() + appendLine() // PRIMITIVES @@ -127,7 +127,7 @@ sealed class Value : ReadWriteProperty { appendln(template) } - appendln() + appendLine() for (number in OTHER_PRIMITIVES) { val template = """ @@ -137,7 +137,7 @@ sealed class Value : ReadWriteProperty { appendln(template) } - appendln() + appendLine() // ARRAYS @@ -154,7 +154,7 @@ sealed class Value : ReadWriteProperty { sealed class PrimitiveArrayValue : ArrayValue() """ ) - appendln() + appendLine() for (number in (NUMBERS + OTHER_PRIMITIVES).filterNot { it == "String" }) { appendln( @@ -164,10 +164,10 @@ sealed class Value : ReadWriteProperty { } """ ) - appendln() + appendLine() } - appendln() + appendLine() // TYPED ARRAYS @@ -178,7 +178,7 @@ sealed class Value : ReadWriteProperty { } """ ) - appendln() + appendLine() for (number in (NUMBERS + OTHER_PRIMITIVES)) { appendln( @@ -188,7 +188,7 @@ sealed class Value : ReadWriteProperty { ) } - appendln() + appendLine() // TYPED LISTS / SETS for (collectionName in listOf("List", "Set")) { @@ -207,14 +207,14 @@ sealed class Value : ReadWriteProperty { appendln(template) } - appendln() + appendLine() // SETTING appendln( """ abstract class Setting${collectionName}Value internal constructor() : Value<${collectionName}>(), ${collectionName} """ ) - appendln() + appendLine() } // SETTING VALUE @@ -225,7 +225,7 @@ sealed class Value : ReadWriteProperty { """ ) - appendln() + appendLine() // MUTABLE LIST / MUTABLE SET for (collectionName in listOf("List", "Set")) { @@ -235,7 +235,7 @@ sealed class Value : ReadWriteProperty { """ ) - appendln() + appendLine() for (number in (NUMBERS + OTHER_PRIMITIVES)) { appendln( @@ -245,17 +245,17 @@ sealed class Value : ReadWriteProperty { ) } - appendln() + appendLine() // SETTING appendln( """ abstract class MutableSetting${collectionName}Value internal constructor() : Value>(), Mutable${collectionName} """ ) - appendln() + appendLine() } - appendln() + appendLine() // DYNAMIC appendln( diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/util.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/util.kt new file mode 100644 index 000000000..241f971cf --- /dev/null +++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/util.kt @@ -0,0 +1,122 @@ +/* + * 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", "MemberVisibilityCanBePrivate", "unused") + +package net.mamoe.mirai.console.codegen + +import org.intellij.lang.annotations.Language +import java.io.File + + +typealias KtByte = KtType.KtPrimitive.KtByte +typealias KtShort = KtType.KtPrimitive.KtShort +typealias KtInt = KtType.KtPrimitive.KtInt +typealias KtLong = KtType.KtPrimitive.KtLong +typealias KtFloat = KtType.KtPrimitive.KtFloat +typealias KtDouble = KtType.KtPrimitive.KtDouble +typealias KtChar = KtType.KtPrimitive.KtChar +typealias KtBoolean = KtType.KtPrimitive.KtBoolean + +typealias KtString = KtType.KtString + +typealias KtCollection = KtType.KtCollection +typealias KtMap = KtType.KtMap + +typealias KtPrimitive = KtType.KtPrimitive + + +sealed class KtType { + /** + * Its classname in standard library + */ + abstract val standardName: String + override fun toString(): String = standardName + + /** + * Not Including [String] + */ + sealed class KtPrimitive( + override val standardName: String, + val jPrimitiveName: String = standardName.toLowerCase(), + val jObjectName: String = standardName + ) : KtType() { + object KtByte : KtPrimitive("Byte") + object KtShort : KtPrimitive("Short") + object KtInt : KtPrimitive("Int", jObjectName = "Integer") + object KtLong : KtPrimitive("Long") + + object KtFloat : KtPrimitive("Float") + object KtDouble : KtPrimitive("Double") + + object KtChar : KtPrimitive("Char", jObjectName = "Character") + object KtBoolean : KtPrimitive("Boolean") + } + + object KtString : KtType() { + override val standardName: String get() = "String" + } + + /** + * [List], [Set] + */ + data class KtCollection(override val standardName: String) : KtType() + + object KtMap : KtType() { + override val standardName: String get() = "Map" + } + + companion object { + val PrimitiveIntegers = listOf(KtByte, KtShort, KtInt, KtLong) + val PrimitiveFloatings = listOf(KtFloat, KtDouble) + + val PrimitiveNumbers = PrimitiveIntegers + PrimitiveFloatings + val PrimitiveNonNumbers = listOf(KtChar, KtBoolean) + + val Primitives = PrimitiveNumbers + PrimitiveNonNumbers + } +} + +operator fun KtType.plus(type: KtType): List { + return listOf(this, type) +} + +val KtType.lowerCaseName: String get() = this.standardName.toLowerCase() + +inline fun kCode(@Language("kt") source: String) = source.trimIndent() + +fun codegen(targetFile: String, block: CodegenScope.() -> Unit) { + //// region PrimitiveValue CODEGEN //// + //// region PrimitiveValue CODEGEN //// + + targetFile.findFileSmart().also { + println("Codegen target: ${it.absolutePath}") + }.apply { + writeText( + CodegenScope().apply(block).also { list -> + list.forEach { + println("Applying replacement: $it") + } + }.applyTo(readText()) + ) + } +} + +fun String.findFileSmart(): File = kotlin.run { + if (contains("/")) { // absolute + File(this) + } else { + val list = File(".").walk().filter { it.name == this }.toList() + if (list.isNotEmpty()) return list.single() + + File(".").walk().filter { it.name.contains(this) }.single() + } +}.also { + require(it.exists()) { "file doesn't exist" } +} \ No newline at end of file diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts index 97178d3fe..c04b32633 100644 --- a/backend/mirai-console/build.gradle.kts +++ b/backend/mirai-console/build.gradle.kts @@ -1,17 +1,17 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import upload.Bintray +import java.text.SimpleDateFormat import java.util.* plugins { - id("kotlin") - kotlin("plugin.serialization") + kotlin("jvm") version Versions.kotlin + kotlin("plugin.serialization") version Versions.kotlin id("java") `maven-publish` - id("com.jfrog.bintray") + id("com.jfrog.bintray") version Versions.bintray } -apply(plugin = "com.github.johnrengelman.shadow") - -version = Versions.Mirai.console +version = Versions.console description = "Console backend for mirai" java { @@ -56,14 +56,14 @@ kotlin { } dependencies { - compileAndRuntime("net.mamoe:mirai-core:${Versions.Mirai.core}") + compileAndRuntime("net.mamoe:mirai-core:${Versions.core}") compileAndRuntime(kotlin("stdlib")) api("net.mamoe.yamlkt:yamlkt:0.3.1") api("org.jetbrains:annotations:19.0.0") - api(kotlinx("coroutines-jdk8", Versions.Kotlin.coroutines)) + api(kotlinx("coroutines-jdk8", Versions.coroutines)) - testApi("net.mamoe:mirai-core-qqandroid:${Versions.Mirai.core}") + testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}") testApi(kotlin("stdlib-jdk8")) testApi(kotlin("test")) testApi(kotlin("test-junit5")) @@ -76,6 +76,33 @@ tasks { "test"(Test::class) { useJUnitPlatform() } + + val compileKotlin by getting {} + + val fillBuildConstants by registering { + doLast { + (compileKotlin as KotlinCompile).source.filter { it.name == "MiraiConsole.kt" }.single().let { file -> + file.writeText(file.readText() + .replace(Regex("""val buildDate: Date = Date\((.*)\) //(.*)""")) { + """ + val buildDate: Date = Date(${System.currentTimeMillis()}L) // ${SimpleDateFormat("yyyy-MM-dd HH:mm:ss").apply { + timeZone = TimeZone.getTimeZone("GMT+8") + }.format(Date())} + """.trimIndent() + } + .replace(Regex("""const val version: String = "(.*)"""")) { + """ + const val version: String = "${Versions.console}" + """.trimIndent() + } + ) + } + } + } + + "compileKotlin" { + dependsOn(fillBuildConstants) + } } // region PUBLISHING diff --git a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/command/JCommandManager.java b/backend/mirai-console/src/main/java/net/mamoe/mirai/console/command/JCommandManager.java index e9e803446..58d922319 100644 --- a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/command/JCommandManager.java +++ b/backend/mirai-console/src/main/java/net/mamoe/mirai/console/command/JCommandManager.java @@ -105,10 +105,10 @@ public final class JCommandManager { * 解析并执行一个指令 * * @param args 接受 {@link String} 或 {@link Message} , 其他对象将会被 {@link Object#toString()} - * @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配 + * @see CommandExecuteResult * @see #executeCommandAsync(CoroutineScope, CommandSender, Object...) */ - public static boolean executeCommand(final @NotNull CommandSender sender, final @NotNull Object... args) throws InterruptedException { + public static CommandExecuteResult executeCommand(final @NotNull CommandSender sender, final @NotNull Object... args) throws InterruptedException { Objects.requireNonNull(sender, "sender"); Objects.requireNonNull(args, "args"); for (Object arg : args) { @@ -123,10 +123,10 @@ public final class JCommandManager { * * @param scope 协程作用域 (用于管理协程生命周期). 一般填入 {@link JavaPlugin} 实例. * @param args 接受 {@link String} 或 {@link Message} , 其他对象将会被 {@link Object#toString()} - * @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配 + * @see CommandExecuteResult * @see #executeCommand(CommandSender, Object...) */ - public static CompletableFuture executeCommandAsync(final @NotNull CoroutineScope scope, final @NotNull CommandSender sender, final @NotNull Object... args) { + public static CompletableFuture executeCommandAsync(final @NotNull CoroutineScope scope, final @NotNull CommandSender sender, final @NotNull Object... args) { Objects.requireNonNull(sender, "sender"); Objects.requireNonNull(args, "args"); Objects.requireNonNull(scope, "scope"); diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt index 3669f780f..aa40060db 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt @@ -13,41 +13,58 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.io.charsets.Charset import net.mamoe.mirai.Bot +import net.mamoe.mirai.console.center.CuiPluginCenter +import net.mamoe.mirai.console.center.PluginCenter import net.mamoe.mirai.console.plugin.PluginLoader import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader -import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiLogger import java.io.ByteArrayOutputStream import java.io.File import java.io.PrintStream +import java.util.* import kotlin.coroutines.CoroutineContext +internal object MiraiConsoleInitializer { + internal lateinit var instance: IMiraiConsole + + /** 由前端调用 */ + internal fun init(instance: IMiraiConsole) { + this.instance = instance + MiraiConsole.initialize() + } + +} + +internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) + @JvmStatic + val buildDate: Date = Date(1592799753404L) // 2020-06-22 12:22:33 + const val version: String = "0.5.1" +} + /** * mirai 控制台实例. */ object MiraiConsole : CoroutineScope, IMiraiConsole { - private lateinit var instance: IMiraiConsole + val pluginCenter: PluginCenter get() = CuiPluginCenter - /** 由前端调用 */ - internal fun init(instance: IMiraiConsole) { - this.instance = instance - } + private val instance: IMiraiConsole + get() = MiraiConsoleInitializer.instance /** * `mirai-console` build 号 + * + * UTC+8 时间 */ - @MiraiExperimentalAPI - override val build: String - get() = instance.build + @JvmStatic + val buildDate: Date + get() = MiraiConsoleBuildConstants.buildDate /** * `mirai-console` 版本 */ - @MiraiExperimentalAPI - override val version: String - get() = instance.version + const val version: String = MiraiConsoleBuildConstants.version /** * Console 运行路径 @@ -79,14 +96,15 @@ object MiraiConsole : CoroutineScope, IMiraiConsole { @MiraiExperimentalAPI fun newLogger(identity: String?): MiraiLogger = frontEnd.loggerFor(identity) + + internal fun initialize() { + // Only for initialize + } } // 前端使用 internal interface IMiraiConsole : CoroutineScope { - val build: String - val version: String - /** * Console 运行路径 */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEnd.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEnd.kt index f94a8eaea..48043cb76 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEnd.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEnd.kt @@ -10,8 +10,6 @@ package net.mamoe.mirai.console import net.mamoe.mirai.Bot -import net.mamoe.mirai.console.center.CuiPluginCenter -import net.mamoe.mirai.console.center.PluginCenter import net.mamoe.mirai.utils.LoginSolver import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiLogger @@ -22,11 +20,6 @@ import net.mamoe.mirai.utils.MiraiLogger */ @MiraiInternalAPI interface MiraiConsoleFrontEnd { - /** - * 提供 [PluginCenter] - */ - val pluginCenter: PluginCenter get() = CuiPluginCenter - fun loggerFor(identity: String?): MiraiLogger /** @@ -43,12 +36,6 @@ interface MiraiConsoleFrontEnd { bot: Bot ) - fun pushVersion( - consoleVersion: String, - consoleBuild: String, - coreVersion: String - ) - /** * 让 UI 层提供一个输入, 相当于 [readLine] */ 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 2a5cca566..56d980cda 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 @@ -134,12 +134,14 @@ fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock { * Java 调用方式: ` CommandManager.executeCommand(Command)` * * @param messages 接受 [String] 或 [Message], 其他对象将会被 [Any.toString] - * @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配 + * @see CommandExecuteResult * * @see JCommandManager.executeCommand Java 方法 */ -suspend fun CommandSender.executeCommand(vararg messages: Any): Boolean { - if (messages.isEmpty()) return false +suspend fun CommandSender.executeCommand(vararg messages: Any): CommandExecuteResult { + if (messages.isEmpty()) return CommandExecuteResult( + status = CommandExecuteStatus.EMPTY_COMMAND + ) return executeCommandInternal( messages, messages[0].let { if (it is SingleMessage) it.toString() else it.toString().substringBefore(' ') }) @@ -150,12 +152,14 @@ internal inline fun List.dropToTypedArray(n: Int): Array = Arr /** * 解析并执行一个指令 - * @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配 + * @see CommandExecuteResult * * @see JCommandManager.executeCommand Java 方法 */ -suspend fun CommandSender.executeCommand(message: MessageChain): Boolean { - if (message.isEmpty()) return false +suspend fun CommandSender.executeCommand(message: MessageChain): CommandExecuteResult { + if (message.isEmpty()) return CommandExecuteResult( + status = CommandExecuteStatus.EMPTY_COMMAND + ) return executeCommandInternal(message, message[0].toString()) } @@ -163,9 +167,63 @@ suspend fun CommandSender.executeCommand(message: MessageChain): Boolean { internal suspend inline fun CommandSender.executeCommandInternal( messages: Any, commandName: String -): Boolean { - val command = InternalCommandManager.matchCommand(commandName) ?: return false +): CommandExecuteResult { + val command = InternalCommandManager.matchCommand(commandName) ?: return CommandExecuteResult( + status = CommandExecuteStatus.COMMAND_NOT_FOUND, + commandName = commandName + ) val rawInput = messages.flattenCommandComponents() - command.onCommand(this, rawInput.dropToTypedArray(1)) - return true -} \ No newline at end of file + kotlin.runCatching { + command.onCommand(this, rawInput.dropToTypedArray(1)) + }.onFailure { + return CommandExecuteResult( + status = CommandExecuteStatus.FAILED, + commandName = commandName, + command = command, + exception = it + ) + } + return CommandExecuteResult( + status = CommandExecuteStatus.SUCCESSFUL, + commandName = commandName, + command = command + ) +} + +/** + * 命令的执行返回 + * + * @param status 命令最终执行状态 + * @param exception 命令执行时发生的错误(如果有) + * @param command 尝试执行的命令 (status = SUCCESSFUL | FAILED) + * @param commandName 尝试执行的命令的名字 (status != EMPTY_COMMAND) + * + * + * @see CommandExecuteStatus + */ +class CommandExecuteResult( + val status: CommandExecuteStatus, + val exception: Throwable? = null, + val command: Command? = null, + val commandName: String? = null +) { + /** + * 命令的执行状态 + * + * 当为 [SUCCESSFUL] 的时候,代表命令执行成功 + * + * 当为 [FAILED] 的时候, 代表命令执行出现了错误 + * + * 当为 [COMMAND_NOT_FOUND] 的时候,代表没有匹配的命令 + * + * 当为 [EMPTY_COMMAND] 的时候, 代表尝试执行 "" + * + */ + enum class CommandExecuteStatus { + SUCCESSFUL, FAILED, COMMAND_NOT_FOUND, EMPTY_COMMAND + } + +} + +@Suppress("RemoveRedundantQualifierName") +typealias CommandExecuteStatus = CommandExecuteResult.CommandExecuteStatus diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/PluginsLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/PluginsLoader.kt index 848cf4dfd..a9a3d9f85 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/PluginsLoader.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/PluginsLoader.kt @@ -16,7 +16,7 @@ import java.net.URLClassLoader internal class PluginsLoader(private val parentClassLoader: ClassLoader) { private val loggerName = "PluginsLoader" private val pluginLoaders = linkedMapOf() - private val classesCache = mutableMapOf>() + private val classesCache = mutableMapOf>() private val logger = MiraiConsole.newLogger(loggerName) /** @@ -34,7 +34,7 @@ internal class PluginsLoader(private val parentClassLoader: ClassLoader) { } catch (e: Throwable) { logger.error("Plugin(${plugin.key}) can't not close its ClassLoader(${cl})", e) } - } + } classesCache.clear() } @@ -93,9 +93,9 @@ internal class PluginsLoader(private val parentClassLoader: ClassLoader) { * A Adapted URL Class Loader that supports Android and JVM for single URL(File) Class Load */ -internal open class AdaptiveURLClassLoader(file: File, parent: ClassLoader):ClassLoader(){ +internal open class AdaptiveURLClassLoader(file: File, parent: ClassLoader) : ClassLoader() { - private val internalClassLoader:ClassLoader by lazy { + private val internalClassLoader: ClassLoader by lazy { kotlin.runCatching { val loaderClass = Class.forName("dalvik.system.PathClassLoader") loaderClass.getConstructor(String::class.java, ClassLoader::class.java) @@ -110,19 +110,19 @@ internal open class AdaptiveURLClassLoader(file: File, parent: ClassLoader):Clas } - private val internalClassCache = mutableMapOf>() + private val internalClassCache = mutableMapOf>() - internal val classesCache:Map> + internal val classesCache: Map> get() = internalClassCache - internal fun addClassCache(string: String, clazz: Class<*>){ - synchronized(internalClassCache){ + internal fun addClassCache(string: String, clazz: Class<*>) { + synchronized(internalClassCache) { internalClassCache[string] = clazz } } - fun close(){ + fun close() { if (internalClassLoader is URLClassLoader) { (internalClassLoader as URLClassLoader).close() } @@ -135,24 +135,25 @@ internal class PluginClassLoader( file: File, private val pluginsLoader: PluginsLoader, parent: ClassLoader -) :AdaptiveURLClassLoader(file,parent){ +) : AdaptiveURLClassLoader(file, parent) { override fun findClass(name: String): Class<*> { - return findClass(name,true) + return findClass(name, true) } - fun findClass(name: String, global: Boolean = true): Class<*>{ - return classesCache[name]?: kotlin.run { + fun findClass(name: String, global: Boolean = true): Class<*> { + return classesCache[name] ?: kotlin.run { var clazz: Class<*>? = null if (global) { clazz = pluginsLoader.findClassByName(name) } - if(clazz == null) { + if (clazz == null) { clazz = loadClass(name)//这里应该是find, 如果不行就要改 } pluginsLoader.addClassCache(name, clazz) - this.addClassCache(name, clazz) - clazz + this.addClassCache(name, clazz) + @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") + clazz!! // compiler bug } } } 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..8999c61c5 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt @@ -0,0 +1,86 @@ +/* + * 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("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_SUPER_CLASS") + +package net.mamoe.mirai.console.setting + +import kotlinx.serialization.KSerializer +import net.mamoe.mirai.console.setting.internal.cast +import net.mamoe.mirai.console.setting.internal.valueFromKTypeImpl +import net.mamoe.mirai.console.setting.internal.valueImpl +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import java.util.* +import kotlin.internal.LowPriorityInOverloadResolution +import kotlin.reflect.KProperty +import kotlin.reflect.KType +import kotlin.reflect.typeOf + + +// TODO: 2020/6/21 move to JvmPlugin to inherit SettingStorage and CoroutineScope for saving +// Shows public APIs such as deciding when to auto-save. +abstract class Setting : SettingImpl() { + + operator fun SerializerAwareValue.provideDelegate( + thisRef: Any?, + property: KProperty<*> + ): SerializerAwareValue { + @Suppress("UNCHECKED_CAST") + valueNodes.add(Node(property as KProperty, this, this.serializer)) + return this + } +} + +/** + * Internal implementation for [Setting] including: + * - Reflection on Kotlin properties and Java fields + * - Auto-saving + */ +// TODO move to internal package. +internal abstract class SettingImpl { + internal class Node( + val property: KProperty, + val value: Value, + val updaterSerializer: KSerializer + ) + + internal val valueNodes: MutableList> = Collections.synchronizedList(mutableListOf()) + + /** + * flatten + */ + internal fun onValueChanged(value: Value<*>) { + // TODO: 2020/6/22 + } +} + + +//// region Setting.value primitives CODEGEN //// + +// TODO: 2020/6/19 CODEGEN + +fun Setting.value(default: Int): SerializableValue = valueImpl(default) + +//// endregion Setting.value primitives CODEGEN //// + + +/** + * Creates a [Value] with reified type. + * + * @param T reified param type T. + * Supports only primitives, Kotlin built-in collections, + * and classes that are serializable with Kotlinx.serialization + * (typically annotated with [kotlinx.serialization.Serializable]) + */ +@LowPriorityInOverloadResolution +@OptIn(ExperimentalStdlibApi::class) // stable in 1.4 +inline fun Setting.valueReified(default: T): SerializableValue = valueFromKTypeImpl(typeOf()).cast() + +@MiraiExperimentalAPI +fun Setting.valueFromKType(type: KType): SerializableValue = valueFromKTypeImpl(type).cast() \ 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..7a3d7b6e4 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt @@ -0,0 +1,216 @@ +/* + * 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("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "unused", "NOTHING_TO_INLINE") + +package net.mamoe.mirai.console.setting + +import kotlinx.serialization.KSerializer +import net.mamoe.mirai.console.setting.internal.map +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import kotlin.reflect.KProperty + +/** + * Represents a observable, immutable value wrapping. + * + * The value can be modified by delegation just like Kotlin's `var`, however it can also be done by the user, e.g. changing using the UI frontend. + * + * Some frequently used types are specially treated with performance enhancement by codegen. + * + * @see PrimitiveValue + * @see CompositeValue + */ +interface Value { + var value: T +} + +/** + * Typically returned by [Setting.value] functions. + */ +class SerializableValue( + delegate: Value, + /** + * The serializer used to update and dump [delegate] + */ + override val serializer: KSerializer +) : Value by delegate, SerializerAwareValue + +fun Value.serializableValueWith( + serializer: KSerializer +): SerializableValue { + return SerializableValue(this, serializer.map(serializer = { this.value }, deserializer = { this.value = it })) +} + +/** + * @see SerializableValue + */ +interface SerializerAwareValue : Value { + val serializer: KSerializer +} + +inline operator fun Value.getValue(mySetting: Any?, property: KProperty<*>): T = value +inline operator fun Value.setValue(mySetting: Any?, property: KProperty<*>, value: T) { + this.value = value +} + +/** + * The serializer for a specific kind of [Value]. + */ +typealias ValueSerializer = KSerializer> + +/** + * Represents a observable *primitive* value wrapping. + * + * 9 types that are considered *primitive*: + * - Integers: [Byte], [Short], [Int], [Long] + * - Floating: [Float], [Double] + * - [Boolean] + * - [Char], [String] + * + * Note: The values are actually *boxed* because of the generic type T. + * *Primitive* indicates only it is one of the 9 types mentioned above. + */ +interface PrimitiveValue : Value + + +//// region PrimitiveValues CODEGEN //// + +/** + * Represents a non-null [Byte] value. + */ +interface ByteValue : PrimitiveValue + +/** + * Represents a non-null [Short] value. + */ +interface ShortValue : PrimitiveValue + +/** + * Represents a non-null [Int] value. + */ +interface IntValue : PrimitiveValue + +/** + * Represents a non-null [Long] value. + */ +interface LongValue : PrimitiveValue + +/** + * Represents a non-null [Float] value. + */ +interface FloatValue : PrimitiveValue + +/** + * Represents a non-null [Double] value. + */ +interface DoubleValue : PrimitiveValue + +/** + * Represents a non-null [Char] value. + */ +interface CharValue : PrimitiveValue + +/** + * Represents a non-null [Boolean] value. + */ +interface BooleanValue : PrimitiveValue + +/** + * Represents a non-null [String] value. + */ +interface StringValue : PrimitiveValue + + +//// endregion PrimitiveValues CODEGEN //// + + +@MiraiExperimentalAPI +interface CompositeValue : Value + + +/** + * Superclass of [CompositeListValue], [PrimitiveListValue]. + */ +interface ListValue : CompositeValue> + +/** + * Elements can by anything, wrapped as [Value]. + * @param E is not primitive types. + */ +interface CompositeListValue : ListValue + +/** + * Elements can only be primitives, not wrapped. + * @param E is not primitive types. + */ +interface PrimitiveListValue : ListValue + + +//// region PrimitiveListValue CODEGEN //// + +interface PrimitiveIntListValue : PrimitiveListValue +interface PrimitiveLongListValue : PrimitiveListValue +// TODO + codegen + +//// endregion PrimitiveListValue CODEGEN //// + + +/** + * Superclass of [CompositeSetValue], [PrimitiveSetValue]. + */ +interface SetValue : CompositeValue> + +/** + * Elements can by anything, wrapped as [Value]. + * @param E is not primitive types. + */ +interface CompositeSetValue : SetValue + +/** + * Elements can only be primitives, not wrapped. + * @param E is not primitive types. + */ +interface PrimitiveSetValue : SetValue + + +//// region PrimitiveSetValue CODEGEN //// + +interface PrimitiveIntSetValue : PrimitiveSetValue +interface PrimitiveLongSetValue : PrimitiveSetValue +// TODO + codegen + +//// endregion PrimitiveSetValue CODEGEN //// + + +/** + * Superclass of [CompositeMapValue], [PrimitiveMapValue]. + */ +interface MapValue : CompositeValue> + +interface CompositeMapValue : MapValue + +interface PrimitiveMapValue : MapValue + + +//// region PrimitiveMapValue CODEGEN //// + +interface PrimitiveIntIntMapValue : PrimitiveMapValue +interface PrimitiveIntLongMapValue : PrimitiveMapValue +// TODO + codegen + +//// endregion PrimitiveSetValue CODEGEN //// + + + + + + + + + diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/CompositeValueImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/CompositeValueImpl.kt new file mode 100644 index 000000000..1b611fe9d --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/CompositeValueImpl.kt @@ -0,0 +1,104 @@ +/* + * 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("unused") + +package net.mamoe.mirai.console.setting.internal + +import net.mamoe.mirai.console.setting.* + + +// type inference bug +internal fun Setting.createCompositeSetValueImpl(tToValue: (T) -> Value): CompositeSetValueImpl { + return object : CompositeSetValueImpl(tToValue) { + override fun onChanged() { + this@createCompositeSetValueImpl.onValueChanged(this) + } + } +} + +internal abstract class CompositeSetValueImpl( + tToValue: (T) -> Value // should override onChanged +) : CompositeSetValue { + private val internalSet: MutableSet> = mutableSetOf() + + private var _value: Set = internalSet.shadowMap({ it.value }, tToValue).observable { onChanged() } + + override var value: Set + get() = _value + set(v) { + if (_value != v) { + onChanged() + _value = v + } + } + + protected abstract fun onChanged() +} + + +// type inference bug +internal fun Setting.createCompositeListValueImpl(tToValue: (T) -> Value): CompositeListValueImpl { + return object : CompositeListValueImpl(tToValue) { + override fun onChanged() { + this@createCompositeListValueImpl.onValueChanged(this) + } + } +} + +internal abstract class CompositeListValueImpl( + tToValue: (T) -> Value // should override onChanged +) : CompositeListValue { + private val internalList: MutableList> = mutableListOf() + + private var _value: List = internalList.shadowMap({ it.value }, tToValue).observable { onChanged() } + + override var value: List + get() = _value + set(v) { + if (_value != v) { + onChanged() + _value = v + } + } + + protected abstract fun onChanged() +} + +// workaround to a type inference bug +internal fun Setting.createCompositeMapValueImpl( + kToValue: (K) -> Value, + vToValue: (V) -> Value +): CompositeMapValueImpl { + return object : CompositeMapValueImpl(kToValue, vToValue) { + override fun onChanged() { + this@createCompositeMapValueImpl.onValueChanged(this) + } + } +} + +internal abstract class CompositeMapValueImpl( + kToValue: (K) -> Value, // should override onChanged + vToValue: (V) -> Value // should override onChanged +) : CompositeMapValue { + private val internalList: MutableMap, Value> = mutableMapOf() + + private var _value: Map = + internalList.shadowMap({ it.value }, kToValue, { it.value }, vToValue).observable { onChanged() } + override var value: Map + get() = _value + set(v) { + if (_value != v) { + onChanged() + _value = v + } + } + + protected abstract fun onChanged() +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/Setting.value composite impl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/Setting.value composite impl.kt new file mode 100644 index 000000000..83f29ea69 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/Setting.value composite impl.kt @@ -0,0 +1,151 @@ +/* + * 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.internal + +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import net.mamoe.mirai.console.setting.SerializableValue +import net.mamoe.mirai.console.setting.Setting +import net.mamoe.mirai.console.setting.serializableValueWith +import net.mamoe.mirai.console.setting.valueFromKType +import net.mamoe.yamlkt.YamlDynamicSerializer +import net.mamoe.yamlkt.YamlNullableDynamicSerializer +import kotlin.reflect.KClass +import kotlin.reflect.KType + + +@PublishedApi +@Suppress("UnsafeCall", "SMARTCAST_IMPOSSIBLE", "UNCHECKED_CAST") +internal fun Setting.valueFromKTypeImpl(type: KType): SerializableValue<*> { + val classifier = type.classifier + require(classifier is KClass<*>) + + if (classifier.isPrimitiveOrBuiltInSerializableValue()) { + TODO("是基础类型, 可以直接创建 ValueImpl. ") + } + + // 复合类型 + + when (classifier) { + Map::class -> { + val keyClass = type.arguments[0].type?.classifier + require(keyClass is KClass<*>) + + val valueClass = type.arguments[1].type?.classifier + require(valueClass is KClass<*>) + + if (keyClass.isPrimitiveOrBuiltInSerializableValue() && valueClass.isPrimitiveOrBuiltInSerializableValue()) { + // PrimitiveIntIntMap + // ... + TODO() + } else { + return createCompositeMapValueImpl( + kToValue = { valueFromKType(type.arguments[0].type!!) }, + vToValue = { valueFromKType(type.arguments[1].type!!) } + ).serializableValueWith(serializerMirai(type) as KSerializer>) // erased + } + } + List::class -> { + val elementClass = type.arguments[0].type?.classifier + require(elementClass is KClass<*>) + + if (elementClass.isPrimitiveOrBuiltInSerializableValue()) { + // PrimitiveIntList + // ... + TODO() + } else { + return createCompositeListValueImpl { valueFromKType(type.arguments[0].type!!) } + .serializableValueWith(serializerMirai(type) as KSerializer>) + } + } + Set::class -> { + val elementClass = type.arguments[0].type?.classifier + require(elementClass is KClass<*>) + + if (elementClass.isPrimitiveOrBuiltInSerializableValue()) { + // PrimitiveIntSet + // ... + TODO() + } else { + return createCompositeSetValueImpl { valueFromKType(type.arguments[0].type!!) } + .serializableValueWith(serializerMirai(type) as KSerializer>) + } + } + else -> error("Custom composite value is not supported yet (${classifier.qualifiedName})") + } +} + +internal fun KClass<*>.isPrimitiveOrBuiltInSerializableValue(): Boolean { + return false // debug + when (this) { + Byte::class, Short::class, Int::class, Long::class, + Boolean::class, + Char::class, String::class, + Pair::class, Triple::class + -> return true + } + + return false +} + +@PublishedApi +@Suppress("UNCHECKED_CAST") +internal inline fun T.cast(): R = this as R + +/** + * Copied from kotlinx.serialization, modifications are marked with "/* mamoe modify */" + * Copyright 2017-2020 JetBrains s.r.o. + */ +@Suppress("UNCHECKED_CAST", "NO_REFLECTION_IN_CLASS_PATH", "UNSUPPORTED", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +@OptIn(ImplicitReflectionSerializer::class) +internal fun serializerMirai(type: KType): KSerializer { + fun serializerByKTypeImpl(type: KType): KSerializer { + val rootClass = when (val t = type.classifier) { + is KClass<*> -> t + else -> error("Only KClass supported as classifier, got $t") + } as KClass + + val typeArguments = type.arguments + .map { requireNotNull(it.type) { "Star projections are not allowed, had $it instead" } } + return when { + typeArguments.isEmpty() -> rootClass.serializer() + else -> { + val serializers = typeArguments + .map(::serializer) + // Array is not supported, see KT-32839 + when (rootClass) { + List::class, MutableList::class, ArrayList::class -> ListSerializer(serializers[0]) + HashSet::class -> SetSerializer(serializers[0]) + Set::class, MutableSet::class, LinkedHashSet::class -> SetSerializer(serializers[0]) + HashMap::class -> MapSerializer(serializers[0], serializers[1]) + Map::class, MutableMap::class, LinkedHashMap::class -> MapSerializer(serializers[0], serializers[1]) + Map.Entry::class -> MapEntrySerializer(serializers[0], serializers[1]) + Pair::class -> PairSerializer(serializers[0], serializers[1]) + Triple::class -> TripleSerializer(serializers[0], serializers[1], serializers[2]) + /* mamoe modify */ Any::class -> if (type.isMarkedNullable) YamlNullableDynamicSerializer else YamlDynamicSerializer + else -> { + if (isReferenceArray(type, rootClass)) { + return ArraySerializer(typeArguments[0].classifier as KClass, serializers[0]).cast() + } + requireNotNull(rootClass.constructSerializerForGivenTypeArgs(*serializers.toTypedArray())) { + "Can't find a method to construct serializer for type ${rootClass.simpleName()}. " + + "Make sure this class is marked as @Serializable or provide serializer explicitly." + } + } + } + } + }.cast() + } + + val result = serializerByKTypeImpl(type) + return if (type.isMarkedNullable) result.nullable else result.cast() +} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_PrimitiveValueDeclareations.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_PrimitiveValueDeclareations.kt new file mode 100644 index 000000000..650200b97 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_PrimitiveValueDeclareations.kt @@ -0,0 +1,39 @@ +/* + * 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.internal + +import net.mamoe.mirai.console.setting.IntValue + + +//// region PrimitiveValues CODEGEN //// + +// TODO: 2020/6/21 CODEGEN + +internal abstract class IntValueImpl : IntValue { + constructor() + constructor(default: Int) { + _value = default + } + + private var _value: Int? = null + + override var value: Int + get() = _value ?: throw IllegalStateException("IntValue should be initialized before get.") + set(v) { + if (v != this._value) { + this._value = v + onChanged() + } + } + + protected abstract fun onChanged() +} + +//// endregion PrimitiveValues CODEGEN //// diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_Setting.value.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_Setting.value.kt new file mode 100644 index 000000000..019b42489 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_Setting.value.kt @@ -0,0 +1,31 @@ +/* + * 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.internal + +import kotlinx.serialization.builtins.serializer +import net.mamoe.mirai.console.setting.SerializableValue +import net.mamoe.mirai.console.setting.Setting + + +//// region Setting.value primitives impl CODEGEN //// + +// TODO: 2020/6/21 CODEGEN + +internal fun Setting.valueImpl(default: Int): SerializableValue { + val instance = object : IntValueImpl(default) { + override fun onChanged() = this@valueImpl.onValueChanged(this) + } + return SerializableValue(instance, Int.serializer().map( + serializer = { instance.value }, + deserializer = { instance.value = it } + )) +} + +//// endregion Setting.value primitives impl CODEGEN //// diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/collectionUtil.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/collectionUtil.kt index c686d2fdb..75208385a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/collectionUtil.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/collectionUtil.kt @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("DuplicatedCode") + package net.mamoe.mirai.console.setting.internal import kotlinx.serialization.ImplicitReflectionSerializer @@ -14,7 +16,89 @@ import kotlinx.serialization.serializer import net.mamoe.yamlkt.Yaml import kotlin.reflect.KClass -internal fun MutableList.shadowMap(transform: (E) -> R, transformBack: (R) -> E): MutableList { +internal inline fun MutableMap.shadowMap( + crossinline kTransform: (K) -> KR, + crossinline kTransformBack: (KR) -> K, + crossinline vTransform: (V) -> VR, + crossinline vTransformBack: (VR) -> V +): MutableMap { + return object : MutableMap { + override val size: Int get() = this@shadowMap.size + override fun containsKey(key: KR): Boolean = this@shadowMap.containsKey(key.let(kTransformBack)) + override fun containsValue(value: VR): Boolean = this@shadowMap.containsValue(value.let(vTransformBack)) + override fun get(key: KR): VR? = this@shadowMap[key.let(kTransformBack)]?.let(vTransform) + override fun isEmpty(): Boolean = this@shadowMap.isEmpty() + + override val entries: MutableSet> + get() = this@shadowMap.entries.shadowMap( + transform = { entry: MutableMap.MutableEntry -> + object : MutableMap.MutableEntry { + override val key: KR get() = entry.key.let(kTransform) + override val value: VR get() = entry.value.let(vTransform) + override fun setValue(newValue: VR): VR = + entry.setValue(newValue.let(vTransformBack)).let(vTransform) + } + } as ((MutableMap.MutableEntry) -> MutableMap.MutableEntry), // type inference bug + transformBack = { entry -> + object : MutableMap.MutableEntry { + override val key: K get() = entry.key.let(kTransformBack) + override val value: V get() = entry.value.let(vTransformBack) + override fun setValue(newValue: V): V = + entry.setValue(newValue.let(vTransform)).let(vTransformBack) + } + } + ) + override val keys: MutableSet + get() = this@shadowMap.keys.shadowMap(kTransform, kTransformBack) + override val values: MutableCollection + get() = this@shadowMap.values.shadowMap(vTransform, vTransformBack) + + override fun clear() = this@shadowMap.clear() + override fun put(key: KR, value: VR): VR? = + this@shadowMap.put(key.let(kTransformBack), value.let(vTransformBack))?.let(vTransform) + + override fun putAll(from: Map) { + from.forEach { (kr, vr) -> + this@shadowMap[kr.let(kTransformBack)] = vr.let(vTransformBack) + } + } + + override fun remove(key: KR): VR? = this@shadowMap.remove(key.let(kTransformBack))?.let(vTransform) + } +} + +internal inline fun MutableCollection.shadowMap( + crossinline transform: (E) -> R, + crossinline transformBack: (R) -> E +): MutableCollection { + return object : MutableCollection { + override val size: Int get() = this@shadowMap.size + + override fun contains(element: R): Boolean = this@shadowMap.any { it.let(transform) == element } + override fun containsAll(elements: Collection): Boolean = elements.all(::contains) + override fun isEmpty(): Boolean = this@shadowMap.isEmpty() + override fun iterator(): MutableIterator = object : MutableIterator { + private val delegate = this@shadowMap.iterator() + override fun hasNext(): Boolean = delegate.hasNext() + override fun next(): R = delegate.next().let(transform) + override fun remove() = delegate.remove() + } + + override fun add(element: R): Boolean = this@shadowMap.add(element.let(transformBack)) + + override fun addAll(elements: Collection): Boolean = this@shadowMap.addAll(elements.map(transformBack)) + override fun clear() = this@shadowMap.clear() + + override fun remove(element: R): Boolean = this@shadowMap.removeIf { it.let(transform) == element } + override fun removeAll(elements: Collection): Boolean = elements.all(::remove) + override fun retainAll(elements: Collection): Boolean = this@shadowMap.retainAll(elements.map(transformBack)) + } +} + +internal inline fun MutableList.shadowMap( + crossinline transform: (E) -> R, + crossinline transformBack: (R) -> E +): MutableList { return object : MutableList { override val size: Int get() = this@shadowMap.size @@ -78,7 +162,10 @@ internal fun MutableList.shadowMap(transform: (E) -> R, transformBack: } -internal fun MutableSet.shadowMap(transform: (E) -> R, transformBack: (R) -> E): MutableSet { +internal inline fun MutableSet.shadowMap( + crossinline transform: (E) -> R, + crossinline transformBack: (R) -> E +): MutableSet { return object : MutableSet { override val size: Int get() = this@shadowMap.size @@ -102,7 +189,7 @@ internal fun MutableSet.shadowMap(transform: (E) -> R, transformBack: } } -internal fun dynamicList(supplier: () -> List): List { +internal inline fun dynamicList(crossinline supplier: () -> List): List { return object : List { override val size: Int get() = supplier().size override fun contains(element: T): Boolean = supplier().contains(element) @@ -118,7 +205,7 @@ internal fun dynamicList(supplier: () -> List): List { } } -internal fun dynamicSet(supplier: () -> Set): Set { +internal inline fun dynamicSet(crossinline supplier: () -> Set): Set { return object : Set { override val size: Int get() = supplier().size override fun contains(element: T): Boolean = supplier().contains(element) @@ -129,7 +216,7 @@ internal fun dynamicSet(supplier: () -> Set): Set { } -internal fun dynamicMutableList(supplier: () -> MutableList): MutableList { +internal inline fun dynamicMutableList(crossinline supplier: () -> MutableList): MutableList { return object : MutableList { override val size: Int get() = supplier().size override fun contains(element: T): Boolean = supplier().contains(element) @@ -156,7 +243,7 @@ internal fun dynamicMutableList(supplier: () -> MutableList): MutableList } -internal fun dynamicMutableSet(supplier: () -> MutableSet): MutableSet { +internal inline fun dynamicMutableSet(crossinline supplier: () -> MutableSet): MutableSet { return object : MutableSet { override val size: Int get() = supplier().size override fun contains(element: T): Boolean = supplier().contains(element) @@ -172,6 +259,23 @@ internal fun dynamicMutableSet(supplier: () -> MutableSet): MutableSet } } +@Suppress("UNCHECKED_CAST", "USELESS_CAST") // type inference bug +internal inline fun MutableMap.observable(crossinline onChanged: () -> Unit): MutableMap { + return object : MutableMap, Map by (this as Map) { + override val keys: MutableSet + get() = this@observable.keys.observable(onChanged) + override val values: MutableCollection + get() = this@observable.values.observable(onChanged) + override val entries: MutableSet> + get() = this@observable.entries.observable(onChanged) + + override fun clear() = this@observable.clear().also { onChanged() } + override fun put(key: K, value: V): V? = this@observable.put(key, value).also { onChanged() } + override fun putAll(from: Map) = this@observable.putAll(from).also { onChanged() } + override fun remove(key: K): V? = this@observable.remove(key).also { onChanged() } + } +} + internal inline fun MutableList.observable(crossinline onChanged: () -> Unit): MutableList { return object : MutableList { override val size: Int get() = this@observable.size @@ -234,6 +338,31 @@ internal inline fun MutableList.observable(crossinline onChanged: () -> U } } +internal inline fun MutableCollection.observable(crossinline onChanged: () -> Unit): MutableCollection { + return object : MutableCollection { + override val size: Int get() = this@observable.size + override fun contains(element: T): Boolean = this@observable.contains(element) + override fun containsAll(elements: Collection): Boolean = this@observable.containsAll(elements) + override fun isEmpty(): Boolean = this@observable.isEmpty() + override fun iterator(): MutableIterator = object : MutableIterator { + private val delegate = this@observable.iterator() + override fun hasNext(): Boolean = delegate.hasNext() + override fun next(): T = delegate.next() + override fun remove() = delegate.remove().also { onChanged() } + } + + override fun add(element: T): Boolean = this@observable.add(element).also { onChanged() } + override fun addAll(elements: Collection): Boolean = this@observable.addAll(elements).also { onChanged() } + override fun clear() = this@observable.clear().also { onChanged() } + override fun remove(element: T): Boolean = this@observable.remove(element).also { onChanged() } + override fun removeAll(elements: Collection): Boolean = + this@observable.removeAll(elements).also { onChanged() } + + override fun retainAll(elements: Collection): Boolean = + this@observable.retainAll(elements).also { onChanged() } + } +} + internal inline fun MutableSet.observable(crossinline onChanged: () -> Unit): MutableSet { return object : MutableSet { override val size: Int get() = this@observable.size diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/internal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/serializerUtil.kt similarity index 86% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/internal.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/serializerUtil.kt index b054b31b8..454544929 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/internal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/serializerUtil.kt @@ -10,16 +10,9 @@ package net.mamoe.mirai.console.setting.internal import kotlinx.serialization.* -import kotlinx.serialization.builtins.ListSerializer -import kotlinx.serialization.builtins.serializer -import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.yamlkt.Yaml -import net.mamoe.yamlkt.YamlConfiguration import kotlin.reflect.KProperty import kotlin.reflect.full.findAnnotation -internal object SettingSerializerMark - internal val KProperty<*>.serialNameOrPropertyName: String get() = this.findAnnotation()?.value ?: this.name internal inline fun KSerializer.bind( diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestComposite.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestComposite.kt index c1cfe009f..133eb62db 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestComposite.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestComposite.kt @@ -18,8 +18,9 @@ object TestCompositeCommand : CompositeCommand( "groupManagement", "grpMgn" ) { @SubCommand - suspend fun CommandSender.mute(image: Image, target: Member, seconds: Int) { + suspend fun CommandSender.mute(image: Image, target: Member, seconds: Int): Boolean { target.mute(seconds) + return true } } diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt new file mode 100644 index 000000000..011486cc5 --- /dev/null +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt @@ -0,0 +1,26 @@ +/* + * 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 org.junit.jupiter.api.Test + +internal class SettingTest { + + class MySetting : Setting() { + val int by value(1) + val map by valueReified(mapOf("" to "")) + val map2 by valueReified(mapOf("" to mapOf("" to mapOf("" to "")))) + } + + @Test + fun testPrimitive() { + + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index ad1429f2e..e747edcae 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,33 +1,15 @@ @file:Suppress("UnstableApiUsage") -import kotlin.math.pow - tasks.withType(JavaCompile::class.java) { options.encoding = "UTF8" } -buildscript { - repositories { - maven(url = "https://dl.bintray.com/kotlin/kotlin-eap") - maven(url = "https://mirrors.huaweicloud.com/repository/maven") - jcenter() - mavenCentral() - } - - dependencies { - classpath("com.github.jengelman.gradle.plugins:shadow:5.2.0") - classpath("org.jetbrains.kotlin:kotlin-serialization:${Versions.Kotlin.stdlib}") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.Kotlin.stdlib}") - classpath("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4") // don"t use any other. - } -} - allprojects { group = "net.mamoe" repositories { + mavenLocal() maven(url = "https://dl.bintray.com/kotlin/kotlin-eap") - maven(url = "https://mirrors.huaweicloud.com/repository/maven") jcenter() mavenCentral() } @@ -35,97 +17,8 @@ allprojects { subprojects { afterEvaluate { - apply(plugin = "com.github.johnrengelman.shadow") - val kotlin = - (this as ExtensionAware).extensions.getByName("kotlin") as? org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension - ?: return@afterEvaluate + apply() - tasks.getByName("shadowJar") { - doLast { - this.outputs.files.forEach { - if (it.nameWithoutExtension.endsWith("-all")) { - val output = File( - it.path.substringBeforeLast(File.separator) + File.separator + it.nameWithoutExtension.substringBeforeLast( - "-all" - ) + "." + it.extension - ) - - println("Renaming to ${output.path}") - if (output.exists()) { - output.delete() - } - - it.renameTo(output) - } - } - } - } - - val githubUpload by tasks.creating { - group = "mirai" - dependsOn(tasks.getByName("shadowJar")) - - doFirst { - timeout.set(java.time.Duration.ofHours(3)) - findLatestFile()?.let { (_, file) -> - val filename = file.name - println("Uploading file $filename") - runCatching { - upload.GitHub.upload( - file, - "https://api.github.com/repos/mamoe/mirai-repo/contents/shadow/${project.name}/$filename", - project - ) - }.exceptionOrNull()?.let { - System.err.println("GitHub Upload failed") - it.printStackTrace() // force show stacktrace - throw it - } - } - } - } - - val cuiCloudUpload by tasks.creating { - group = "mirai" - dependsOn(tasks.getByName("shadowJar")) - - doFirst { - timeout.set(java.time.Duration.ofHours(3)) - findLatestFile()?.let { (_, file) -> - val filename = file.name - println("Uploading file $filename") - runCatching { - upload.CuiCloud.upload( - file, - project - ) - }.exceptionOrNull()?.let { - System.err.println("CuiCloud Upload failed") - it.printStackTrace() // force show stacktrace - throw it - } - } - } - - } + setJavaCompileTarget() } -} - - -fun Project.findLatestFile(): Map.Entry { - return File(projectDir, "build/libs").walk() - .filter { it.isFile } - .onEach { println("all files=$it") } - .filter { it.name.matches(Regex("""${project.name}-[0-9][0-9]*(\.[0-9]*)*.*\.jar""")) } - .onEach { println("matched file: ${it.name}") } - .associateBy { it.nameWithoutExtension.substringAfterLast('-') } - .onEach { println("versions: $it") } - .maxBy { (version, file) -> - version.split('.').let { - if (it.size == 2) it + "0" - else it - }.reversed().foldIndexed(0) { index: Int, acc: Int, s: String -> - acc + 100.0.pow(index).toInt() * (s.toIntOrNull() ?: 0) - } - } ?: error("cannot find any file to upload") -} +} \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index a0b4b2f9b..0473f2f16 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -3,7 +3,10 @@ plugins { } repositories { + mavenLocal() jcenter() + maven(url = "https://dl.bintray.com/kotlin/kotlin-eap") + mavenCentral() } kotlin { @@ -24,4 +27,11 @@ dependencies { api(ktor("client-core", "1.3.2")) api(ktor("client-cio", "1.3.2")) api(ktor("client-json", "1.3.2")) + + //api(gradleApi()) + //compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72") + compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72") + //runtimeOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72") + + api("com.github.jengelman.gradle.plugins:shadow:6.0.0") } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt b/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt new file mode 100644 index 000000000..2df6c68ab --- /dev/null +++ b/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt @@ -0,0 +1,115 @@ +/* + * 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("UnstableApiUsage") + +import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.creating +import java.io.File +import kotlin.math.pow + +class MiraiConsoleBuildPlugin : Plugin { + override fun apply(target: Project) = target.run { + apply() + + if (tasks.none { it.name == "shadowJar" }) { + return@run + } + + tasks.getByName("shadowJar") { + doLast { + this.outputs.files.forEach { + if (it.nameWithoutExtension.endsWith("-all")) { + val output = File( + it.path.substringBeforeLast(File.separator) + File.separator + it.nameWithoutExtension.substringBeforeLast( + "-all" + ) + "." + it.extension + ) + + println("Renaming to ${output.path}") + if (output.exists()) { + output.delete() + } + + it.renameTo(output) + } + } + } + } + + tasks.creating { + group = "mirai" + dependsOn(tasks.getByName("shadowJar")) + + doFirst { + timeout.set(java.time.Duration.ofHours(3)) + findLatestFile().let { (_, file) -> + val filename = file.name + println("Uploading file $filename") + runCatching { + upload.GitHub.upload( + file, + "https://api.github.com/repos/mamoe/mirai-repo/contents/shadow/${project.name}/$filename", + project + ) + }.exceptionOrNull()?.let { + System.err.println("GitHub Upload failed") + it.printStackTrace() // force show stacktrace + throw it + } + } + } + } + + tasks.creating { + group = "mirai" + dependsOn(tasks.getByName("shadowJar")) + + doFirst { + timeout.set(java.time.Duration.ofHours(3)) + findLatestFile().let { (_, file) -> + val filename = file.name + println("Uploading file $filename") + runCatching { + upload.CuiCloud.upload( + file, + project + ) + }.exceptionOrNull()?.let { + System.err.println("CuiCloud Upload failed") + it.printStackTrace() // force show stacktrace + throw it + } + } + } + + } + } +} + +fun Project.findLatestFile(): Map.Entry { + return File(projectDir, "build/libs").walk() + .filter { it.isFile } + .onEach { println("all files=$it") } + .filter { it.name.matches(Regex("""${project.name}-[0-9][0-9]*(\.[0-9]*)*.*\.jar""")) } + .onEach { println("matched file: ${it.name}") } + .associateBy { it.nameWithoutExtension.substringAfterLast('-') } + .onEach { println("versions: $it") } + .maxBy { (version, _) -> + version.split('.').let { + if (it.size == 2) it + "0" + else it + }.reversed().foldIndexed(0) { index: Int, acc: Int, s: String -> + acc + 100.0.pow(index).toInt() * (s.toIntOrNull() ?: 0) + } + } ?: error("cannot find any file to upload") +} diff --git a/buildSrc/src/main/kotlin/SetCompileTargetPlugin.kt b/buildSrc/src/main/kotlin/SetCompileTargetPlugin.kt new file mode 100644 index 000000000..8910359a3 --- /dev/null +++ b/buildSrc/src/main/kotlin/SetCompileTargetPlugin.kt @@ -0,0 +1,45 @@ +/* + * 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 + */ + +import org.gradle.api.JavaVersion +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.compile.JavaCompile +import java.lang.reflect.Method +import kotlin.reflect.KClass + + +fun Any.reflectMethod(name: String, vararg params: KClass): Pair { + return this to this::class.java.getMethod(name, *params.map { it.java }.toTypedArray()) +} + +operator fun Pair.invoke(vararg args: Any?): Any? { + return second.invoke(first, *args) +} + +@Suppress("NOTHING_TO_INLINE") // or error +fun Project.setJavaCompileTarget() { + tasks.filter { it.name in arrayOf("compileKotlin", "compileTestKotlin") }.forEach { task -> + task + .reflectMethod("getKotlinOptions")()!! + .reflectMethod("setJvmTarget", String::class)("1.8") + } + + + kotlin.runCatching { // apply only when java plugin is available + (extensions.getByName("java") as JavaPluginExtension).run { + 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/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 6647de79e..050457830 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -8,18 +8,18 @@ */ object Versions { - object Mirai { - const val core = "1.0.0" - const val console = "0.5.1" - const val consoleGraphical = "0.0.7" - const val consoleTerminal = "0.1.0" - const val consolePure = "0.1.0" - } + const val core = "1.1-EA" + const val console = "0.5.1" + const val consoleGraphical = "0.0.7" + const val consoleTerminal = "0.1.0" + const val consolePure = "0.1.0" - object Kotlin { - const val stdlib = "1.3.72" - const val coroutines = "1.3.7" - const val serialization = "0.20.0" - const val ktor = "1.3.2" - } + const val kotlin = "1.3.72" + const val coroutines = "1.3.7" + const val serialization = "0.20.0" + const val ktor = "1.3.2" + + const val androidGradle = "3.6.2" + + const val bintray = "1.8.4" } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/dependencyExtensions.kt b/buildSrc/src/main/kotlin/dependencyExtensions.kt index 03bee26e5..5a9f08d92 100644 --- a/buildSrc/src/main/kotlin/dependencyExtensions.kt +++ b/buildSrc/src/main/kotlin/dependencyExtensions.kt @@ -5,7 +5,7 @@ import org.gradle.kotlin.dsl.DependencyHandlerScope fun DependencyHandlerScope.kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version" @Suppress("unused") -fun DependencyHandlerScope.ktor(id: String, version: String = Versions.Kotlin.ktor) = "io.ktor:ktor-$id:$version" +fun DependencyHandlerScope.ktor(id: String, version: String = Versions.ktor) = "io.ktor:ktor-$id:$version" @Suppress("unused") fun DependencyHandler.compileAndRuntime(any: Any) { diff --git a/frontend/mirai-console-graphical/build.gradle.kts b/frontend/mirai-console-graphical/build.gradle.kts index c9e6de907..12bbba3b6 100644 --- a/frontend/mirai-console-graphical/build.gradle.kts +++ b/frontend/mirai-console-graphical/build.gradle.kts @@ -29,17 +29,17 @@ version = Versions.Mirai.consoleGraphical description = "Graphical frontend for mirai-console" dependencies { - compileOnly("net.mamoe:mirai-core:${Versions.Mirai.core}") + compileOnly("net.mamoe:mirai-core:${Versions.core}") implementation(project(":mirai-console")) api(group = "no.tornado", name = "tornadofx", version = "1.7.19") api(group = "com.jfoenix", name = "jfoenix", version = "9.0.8") testApi(project(":mirai-console")) - testApi(kotlinx("coroutines-core", Versions.Kotlin.coroutines)) + testApi(kotlinx("coroutines-core", Versions.coroutines)) testApi(group = "org.yaml", name = "snakeyaml", version = "1.25") - testApi("net.mamoe:mirai-core:${Versions.Mirai.core}") - testApi("net.mamoe:mirai-core-qqandroid:${Versions.Mirai.core}") + testApi("net.mamoe:mirai-core:${Versions.core}") + testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}") } kotlin { @@ -47,7 +47,7 @@ kotlin { all { languageSettings.useExperimentalAnnotation("kotlin.Experimental") - languageSettings.useExperimentalAnnotation("kotlin.OptIn") + languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn") languageSettings.progressiveMode = true languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI") } diff --git a/frontend/mirai-console-pure/build.gradle.kts b/frontend/mirai-console-pure/build.gradle.kts index c0ce1e6a7..ea7d4c798 100644 --- a/frontend/mirai-console-pure/build.gradle.kts +++ b/frontend/mirai-console-pure/build.gradle.kts @@ -1,22 +1,18 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { - kotlin("jvm") - kotlin("plugin.serialization") + kotlin("jvm") version Versions.kotlin + kotlin("plugin.serialization") version Versions.kotlin id("java") `maven-publish` - id("com.jfrog.bintray") + id("com.jfrog.bintray") version Versions.bintray } -apply(plugin = "com.github.johnrengelman.shadow") - kotlin { sourceSets { all { languageSettings.enableLanguageFeature("InlineClasses") languageSettings.useExperimentalAnnotation("kotlin.Experimental") - languageSettings.useExperimentalAnnotation("kotlin.OptIn") + languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn") languageSettings.progressiveMode = true languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI") languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes") @@ -25,31 +21,19 @@ kotlin { } } } -dependencies { - compileOnly(project(":mirai-console")) - compileOnly("net.mamoe:mirai-core:${Versions.Mirai.core}") - compileOnly(kotlin("stdlib")) // embedded by core - testApi("net.mamoe:mirai-core-qqandroid:${Versions.Mirai.core}") +dependencies { + implementation("org.jline:jline:3.15.0") + implementation("org.fusesource.jansi:jansi:1.18") + + compileAndRuntime(project(":mirai-console")) + compileAndRuntime("net.mamoe:mirai-core:${Versions.core}") + compileAndRuntime(kotlin("stdlib")) // embedded by core + + testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}") testApi(project(":mirai-console")) } -version = Versions.Mirai.consolePure +version = Versions.consolePure -description = "Console Pure CLI frontend for mirai" - -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 +description = "Console Pure CLI frontend for mirai" \ No newline at end of file diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleUtils.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleUtils.kt new file mode 100644 index 000000000..a363e5e03 --- /dev/null +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleUtils.kt @@ -0,0 +1,37 @@ +/* + * 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.pure + +import org.jline.reader.LineReader +import org.jline.reader.LineReaderBuilder +import org.jline.reader.impl.completer.NullCompleter +import org.jline.terminal.Terminal +import org.jline.terminal.TerminalBuilder + +object ConsoleUtils { + + val lineReader: LineReader + val terminal: Terminal + + init { + + val dumb = System.getProperty("java.class.path") + .contains("idea_rt.jar") || System.getProperty("mirai.idea") !== null + + terminal = TerminalBuilder.builder() + .dumb(dumb) + .build() + lineReader = LineReaderBuilder.builder() + .terminal(terminal) + .completer(NullCompleter()) + .build() + } +} \ No newline at end of file diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt index e000b7a7d..e8dc2ef2e 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt @@ -9,20 +9,90 @@ package net.mamoe.mirai.console.pure -import kotlinx.coroutines.delay +//import net.mamoe.mirai.console.command.CommandManager +//import net.mamoe.mirai.console.utils.MiraiConsoleFrontEnd import net.mamoe.mirai.Bot -import net.mamoe.mirai.console.command.CommandManager -import net.mamoe.mirai.console.command.ConsoleCommandSender -import net.mamoe.mirai.console.utils.MiraiConsoleFrontEnd -import net.mamoe.mirai.utils.DefaultLogger +import net.mamoe.mirai.console.MiraiConsoleFrontEnd import net.mamoe.mirai.utils.DefaultLoginSolver import net.mamoe.mirai.utils.LoginSolver import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.mirai.utils.PlatformLogger +import org.fusesource.jansi.Ansi import java.text.SimpleDateFormat import java.util.* -import kotlin.concurrent.thread +import java.util.concurrent.ConcurrentHashMap + +private val ANSI_RESET = Ansi().reset().toString() + +internal val LoggerCreator: (identity: String?) -> MiraiLogger = { + PlatformLogger(identity = it, output = { line -> + ConsoleUtils.lineReader.printAbove(line + ANSI_RESET) + }) +} + +@Suppress("unused") +object MiraiConsoleFrontEndPure : MiraiConsoleFrontEnd { + private val globalLogger = LoggerCreator("Mirai") + private val cachedLoggers = ConcurrentHashMap() + + // companion object { + // ANSI color codes + const val COLOR_RED = "\u001b[38;5;196m" + const val COLOR_CYAN = "\u001b[38;5;87m" + const val COLOR_GREEN = "\u001b[38;5;82m" + + // use a dark yellow(more like orange) instead of light one to save Solarized-light users + const val COLOR_YELLOW = "\u001b[38;5;220m" + const val COLOR_GREY = "\u001b[38;5;244m" + const val COLOR_BLUE = "\u001b[38;5;27m" + const val COLOR_NAVY = "\u001b[38;5;24m" // navy uniform blue + const val COLOR_PINK = "\u001b[38;5;207m" + const val COLOR_RESET = "\u001b[39;49m" + // } + + val sdf by lazy { + SimpleDateFormat("HH:mm:ss") + } + override fun loggerFor(identity: String?): MiraiLogger { + identity?.apply { + return cachedLoggers.computeIfAbsent(this, LoggerCreator) + } + return globalLogger + } + + override fun prePushBot(identity: Long) { + } + + override fun pushBot(bot: Bot) { + } + + override suspend fun requestInput(hint: String): String { + if (hint.isNotEmpty()) { + ConsoleUtils.lineReader.printAbove( + Ansi.ansi() + .fgCyan().a(sdf.format(Date())) + .fgMagenta().a(hint) + .toString() + ) + } + return ConsoleUtils.lineReader.readLine("> ") + } + + override fun pushBotAdminStatus(identity: Long, admins: List) { + } + + override fun createLoginSolver(): LoginSolver { + return DefaultLoginSolver( + input = suspend { + requestInput("") + } + ) + } +} + +/* class MiraiConsoleFrontEndPure : MiraiConsoleFrontEnd { private var requesting = false private var requestStr = "" @@ -106,4 +176,4 @@ class MiraiConsoleFrontEndPure : MiraiConsoleFrontEnd { } - +*/ diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePure.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePure.kt new file mode 100644 index 000000000..0cb890027 --- /dev/null +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePure.kt @@ -0,0 +1,46 @@ +/* + * 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( + "INVISIBLE_MEMBER", + "INVISIBLE_REFERENCE", + "CANNOT_OVERRIDE_INVISIBLE_MEMBER", + "INVISIBLE_SETTER", + "INVISIBLE_GETTER", + "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER", + "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPE_WARNING" +) + +package net.mamoe.mirai.console.pure + + +import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.console.IMiraiConsole +import net.mamoe.mirai.console.MiraiConsoleFrontEnd +import net.mamoe.mirai.console.plugin.PluginLoader +import net.mamoe.mirai.utils.DefaultLogger +import net.mamoe.mirai.utils.MiraiLogger +import java.io.File +import java.util.* +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +private val delegateScope = CoroutineScope(EmptyCoroutineContext) + +object MiraiConsolePure : IMiraiConsole { + override val builtInPluginLoaders: List> = LinkedList() + override val frontEnd: MiraiConsoleFrontEnd = MiraiConsoleFrontEndPure + override val mainLogger: MiraiLogger = DefaultLogger("Console") + override val rootDir: File = File("./test/console").also { + it.mkdirs() + } + override val coroutineContext: CoroutineContext + get() = delegateScope.coroutineContext +} \ No newline at end of file diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt index d5a144a5c..ad142caa5 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt @@ -7,68 +7,67 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress( + "INVISIBLE_MEMBER", + "INVISIBLE_REFERENCE", + "CANNOT_OVERRIDE_INVISIBLE_MEMBER", + "INVISIBLE_SETTER", + "INVISIBLE_GETTER", + "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER", + "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPE_WARNING" +) + package net.mamoe.mirai.console.pure -import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.command.CommandManager -import net.mamoe.mirai.console.command.DefaultCommands -import net.mamoe.mirai.console.plugins.PluginManager -import net.mamoe.mirai.console.utils.MiraiConsoleFrontEnd +import net.mamoe.mirai.console.MiraiConsoleInitializer +import net.mamoe.mirai.console.command.CommandExecuteStatus +import net.mamoe.mirai.console.command.ConsoleCommandSender +import net.mamoe.mirai.console.command.executeCommand +import net.mamoe.mirai.message.data.Message +import net.mamoe.mirai.message.data.PlainText +import net.mamoe.mirai.utils.DefaultLogger import kotlin.concurrent.thread -class MiraiConsolePureLoader { - companion object { - @JvmStatic - fun load( - coreVersion: String, - consoleVersion: String - ) { - start( - MiraiConsoleFrontEndPure(), - coreVersion, - consoleVersion - ) - Runtime.getRuntime().addShutdownHook(thread(start = false) { - MiraiConsole.stop() - }) +object MiraiConsolePureLoader { + @JvmStatic + fun main(args: Array?) { + startup() + } +} + + +internal fun startup() { + MiraiConsoleInitializer.init(MiraiConsolePure) + startConsoleThread() +} + +internal fun startConsoleThread() { + thread(name = "Console", isDaemon = false) { + val consoleLogger = DefaultLogger("Console") + kotlinx.coroutines.runBlocking { + while (true) { + val next = MiraiConsoleFrontEndPure.requestInput("") + consoleLogger.debug("INPUT> $next") + val result = ConsoleCS.executeCommand(PlainText(next)) + when (result.status) { + CommandExecuteStatus.SUCCESSFUL -> { + } + CommandExecuteStatus.EMPTY_COMMAND -> { + } + CommandExecuteStatus.FAILED -> { + consoleLogger.error("An error occurred while executing the command: $next", result.exception) + } + CommandExecuteStatus.COMMAND_NOT_FOUND -> { + consoleLogger.warning("Unknown command: ${result.commandName}") + } + } + } } } } -/** - * 启动 Console - */ -@JvmOverloads -internal fun start( - frontEnd: MiraiConsoleFrontEnd, - coreVersion: String = "0.0.0", - consoleVersion: String = "0.0.0", - path: String = System.getProperty("user.dir") -) { - if (MiraiConsole.started) { - return +object ConsoleCS : ConsoleCommandSender() { + override suspend fun sendMessage(message: Message) { + ConsoleUtils.lineReader.printAbove(message.contentToString()) } - MiraiConsole.started = true - this.path = path - /* 初始化前端 */ - this.version = consoleVersion - this.frontEnd = frontEnd - this.frontEnd.pushVersion(consoleVersion, MiraiConsole.build, coreVersion) - logger("Mirai-console now running under $path") - logger("Get news in github: https://github.com/mamoe/mirai") - logger("Mirai为开源项目,请自觉遵守开源项目协议") - logger("Powered by Mamoe Technologies and contributors") - - /* 依次启用功能 */ - DefaultCommands() - PluginManager.loadPlugins() - CommandManager.start() - - /* 通知启动完成 */ - logger("Mirai-console 启动完成") - logger("\"login qqnumber qqpassword \" to login a bot") - logger("\"login qq号 qq密码 \" 来登录一个BOT") - - /* 尝试从系统配置自动登录 */ - DefaultCommands.tryLoginAuto() } \ No newline at end of file diff --git a/frontend/mirai-console-terminal/build.gradle.kts b/frontend/mirai-console-terminal/build.gradle.kts index 101775f61..cea7f593a 100644 --- a/frontend/mirai-console-terminal/build.gradle.kts +++ b/frontend/mirai-console-terminal/build.gradle.kts @@ -20,7 +20,7 @@ kotlin { all { languageSettings.useExperimentalAnnotation("kotlin.Experimental") - languageSettings.useExperimentalAnnotation("kotlin.OptIn") + languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn") languageSettings.progressiveMode = true languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI") } @@ -28,7 +28,7 @@ kotlin { } dependencies { - compileOnly("net.mamoe:mirai-core-qqandroid:${Versions.Mirai.core}") + compileOnly("net.mamoe:mirai-core-qqandroid:${Versions.core}") api(project(":mirai-console")) api(group = "com.googlecode.lanterna", name = "lanterna", version = "3.0.2") } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 77f95ee0c..1ee034fe0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ #Wed Mar 04 22:27:09 CST 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index d63876b77..000000000 --- a/settings.gradle +++ /dev/null @@ -1,72 +0,0 @@ -pluginManagement { - resolutionStrategy { - eachPlugin { - switch (requested.id.id) { - case "org.jetbrains.kotlin.multiplatform": useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}"); break - case "com.android.library": useModule("com.android.tools.build:gradle:${requested.version}"); break - case "com.jfrog.bintray": useModule("com.jfrog.bintray.gradle:gradle-bintray-plugin:${requested.version}") - } - } - } - - repositories { - mavenLocal() - jcenter() - google() - mavenCentral() - maven { url "https://plugins.gradle.org/m2/" } - maven { url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies" } - maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } - maven { url 'https://plugins.gradle.org/m2/' } - } -} - -rootProject.name = 'mirai-console' - -def onlyBackEnd = true - -include(':mirai-console') -project(':mirai-console').dir = file("backend/mirai-console") - -include(':codegen') -project(':codegen').dir = file("backend/codegen") - - -if (!onlyBackEnd) { - - include(':mirai-console-pure') - project(':mirai-console-pure').dir = file("frontend/mirai-console-pure") - - include(':mirai-console-terminal') - project(':mirai-console-terminal').dir = file("frontend/mirai-console-terminal") - - try { - def javaVersion = System.getProperty("java.version") - def versionPos = javaVersion.indexOf(".") - def javaVersionNum = javaVersion.substring(0, 1).toInteger() - - if (javaVersion.startsWith("1.")) { - javaVersionNum = javaVersion.substring(2, 3).toInteger() - } else { - if (versionPos == -1) versionPos = javaVersion.indexOf("-") - if (versionPos == -1) { - println("jdk version unknown") - } else { - javaVersionNum = javaVersion.substring(0, versionPos).toInteger() - } - } - if (javaVersionNum >= 9) { - include(':mirai-console-graphical') - project(':mirai-console-graphical').dir = file("frontend/mirai-console-graphical") - } else { - println("jdk版本为 " + javaVersionNum) - println("当前使用的 JDK 版本为 ${System.getProperty("java.version")}, 请使用JDK 9以上版本引入模块 `:mirai-console-graphical`\n") - } - - } catch (Exception ignored) { - println("无法确定 JDK 版本, 将不会引入 `:mirai-console-graphical`") - } -} - - -enableFeaturePreview('GRADLE_METADATA') \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 000000000..4283675f6 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,53 @@ +pluginManagement { + repositories { + mavenLocal() + jcenter() + maven(url = "https://dl.bintray.com/kotlin/kotlin-eap") + mavenCentral() + } + + resolutionStrategy { + eachPlugin { + val version = requested.version + when (requested.id.id) { + "org.jetbrains.kotlin.jvm" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${version}") + "org.jetbrains.kotlin.plugin.serialization" -> useModule("org.jetbrains.kotlin:kotlin-serialization:${version}") + "com.jfrog.bintray" -> useModule("com.jfrog.bintray.gradle:gradle-bintray-plugin:$version") + } + } + } +} + +rootProject.name = "mirai-console" + +val disableOldFrontEnds = true + +fun includeProject(projectPath: String, path: String? = null) { + include(projectPath) + if (path != null) project(projectPath).projectDir = file(path) +} + +includeProject(":mirai-console", "backend/mirai-console") +includeProject(":mirai-console.codegen", "backend/codegen") +includeProject(":mirai-console-pure", "frontend/mirai-console-pure") + +@Suppress("ConstantConditionIf") +if (!disableOldFrontEnds) { + includeProject(":mirai-console-terminal", "frontend/mirai-console-terminal") + + val jdkVersion = kotlin.runCatching { + System.getProperty("java.version").let { v -> + v.toIntOrNull() ?: v.removePrefix("1.").substringBefore("-").toIntOrNull() + } + }.getOrNull() ?: -1 + + println("JDK version: $jdkVersion") + + if (jdkVersion >= 9) { + includeProject(":mirai-console-graphical", "frontend/mirai-console-graphical") + } else { + println("当前使用的 JDK 版本为 ${System.getProperty("java.version")}, 请使用 JDK 9 以上版本引入模块 `:mirai-console-graphical`\n") + } +} + +enableFeaturePreview("GRADLE_METADATA") \ No newline at end of file