diff --git a/.idea/icon.png b/.idea/icon.png new file mode 100644 index 000000000..c872c7a5d Binary files /dev/null and b/.idea/icon.png differ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt index f314be39c..95306d28e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt @@ -14,7 +14,7 @@ import java.time.Instant internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) @JvmStatic - val buildDate: Instant = Instant.ofEpochSecond(1600596035) + val buildDate: Instant = Instant.ofEpochSecond(1600663022) @JvmStatic val version: SemVersion = SemVersion("1.0-RC-dev-28") diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/RangeTokenReader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/RangeTokenReader.kt new file mode 100644 index 000000000..00a0524b2 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/RangeTokenReader.kt @@ -0,0 +1,251 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + * + */ + +@file:Suppress("MemberVisibilityCanBePrivate") + +package net.mamoe.mirai.console.internal.util.semver + +import net.mamoe.mirai.console.util.SemVersion +import kotlin.math.max +import kotlin.math.min + +internal object RangeTokenReader { + enum class TokenType { + STRING, + + /* 左括号 */ + LEFT, + + /* 右括号 */ + RIGHT, + + /* || */ + OR, + + /* && */ + AND, + GROUP + } + + sealed class Token { + abstract val type: TokenType + abstract val value: String + abstract val position: Int + + class LeftBracket(override val position: Int) : Token() { + override val type: TokenType get() = TokenType.LEFT + override val value: String get() = "{" + + override fun toString(): String = "LB{" + } + + class RightBracket(override val position: Int) : Token() { + override val type: TokenType get() = TokenType.RIGHT + override val value: String get() = "}" + + override fun toString(): String = "RB}" + } + + class Or(override val position: Int) : Token() { + override val type: TokenType get() = TokenType.OR + override val value: String get() = "||" + override fun toString(): String = "OR||" + } + + class And(override val position: Int) : Token() { + override val type: TokenType get() = TokenType.AND + override val value: String get() = "&&" + + override fun toString(): String = "AD&&" + } + + class Group(val values: List, override val position: Int) : Token() { + override val type: TokenType get() = TokenType.GROUP + override val value: String get() = "" + } + + class Raw(val source: String, val start: Int, val end: Int) : Token() { + override val value: String get() = source.substring(start, end) + override val position: Int + get() = start + override val type: TokenType get() = TokenType.STRING + + override fun toString(): String = "R:$value" + } + } + + fun parseToTokens(source: String): List = ArrayList( + max(source.length / 3, 16) + ).apply { + var index = 0 + var position = 0 + fun flushOld() { + if (position > index) { + val id = index + index = position + for (i in id until position) { + if (!source[i].isWhitespace()) { + add(Token.Raw(source, id, position)) + return + } + } + } + } + + val iterator = source.indices.iterator() + for (i in iterator) { + position = i + when (source[i]) { + '{' -> { + flushOld() + add(Token.LeftBracket(i)) + index = i + 1 + } + '|' -> { + if (source.getOrNull(i + 1) == '|') { + flushOld() + add(Token.Or(i)) + index = i + 2 + iterator.nextInt() + } + } + '&' -> { + if (source.getOrNull(i + 1) == '&') { + flushOld() + add(Token.And(i)) + index = i + 2 + iterator.nextInt() + } + } + '}' -> { + flushOld() + add(Token.RightBracket(i)) + index = i + 1 + } + } + } + position = source.length + flushOld() + } + + fun collect(source: String, tokens: Iterator, root: Boolean): List = ArrayList().apply { + tokens.forEach { token -> + if (token is Token.LeftBracket) { + add(Token.Group(collect(source, tokens, false), token.position)) + } else if (token is Token.RightBracket) { + if (root) { + throw IllegalArgumentException("Syntax error: Unexpected }, ${buildMsg(source, token.position)}") + } else { + return@apply + } + } else add(token) + } + if (!root) { + throw IllegalArgumentException("Syntax error: Excepted }, ${buildMsg(source, source.length)}") + } + } + + private fun buildMsg(source: String, position: Int): String { + val ed = min(position + 10, source.length) + val st = max(0, position - 10) + return buildString { + append('`') + if (st != 0) append("...") + append(source, st, ed) + if (ed != source.length) append("...") + append("` at ").append(position) + } + } + + fun check(source: String, tokens: Iterator, group: Token.Group?) { + if (!tokens.hasNext()) { + throw IllegalArgumentException("Syntax error: empty rule, ${buildMsg(source, group?.position ?: 0)}") + } + var type = false + do { + val next = tokens.next() + if (type) { + if (next is Token.Group || next is Token.Raw) { + throw IllegalArgumentException("Syntax error: Except logic but got expression, ${buildMsg(source, next.position)}") + } + } else { + if (next is Token.Or || next is Token.And) { + throw IllegalArgumentException("Syntax error: Except expression but got logic, ${buildMsg(source, next.position)}") + } + if (next is Token.Group) { + check(source, next.values.iterator(), next) + } + } + type = !type + } while (tokens.hasNext()) + if (!type) { + throw IllegalArgumentException("Syntax error: Except more expression, ${buildMsg(source, group?.values?.last()?.position ?: source.length)}") + } + } + + fun parse(source: String, token: Token): SemVersion.Requirement { + return when (token) { + is Token.Group -> { + if (token.values.size == 1) { + parse(source, token.values.first()) + } else { + val logic = token.values.asSequence().map { it.type }.filter { + it == TokenType.OR || it == TokenType.AND + }.toSet() + if (logic.size == 2) { + throw IllegalArgumentException("Syntax error: || and && cannot use in one group, ${buildMsg(source, token.position)}") + } + val rules = token.values.asSequence().filter { + it is Token.Raw || it is Token.Group + }.map { parse(source, it) }.toList() + when (logic.first()) { + TokenType.OR -> { + return object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean { + rules.forEach { if (it.test(version)) return true } + return false + } + } + } + TokenType.AND -> { + return object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean { + rules.forEach { if (!it.test(version)) return false } + return true + } + } + } + else -> throw AssertionError() + } + } + } + is Token.Raw -> SemVersionInternal.parseRule(token.value) + else -> throw AssertionError() + } + } + + fun StringBuilder.dump(prefix: String, token: Token) { + when (token) { + is Token.LeftBracket -> append("${prefix}LF {\n") + + is Token.RightBracket -> append("${prefix}LR }\n") + + is Token.Or -> append("${prefix}OR ||\n") + + is Token.And -> append("${prefix}AND &&\n") + is Token.Group -> { + append("${prefix}GROUP {\n") + token.values.forEach { dump("$prefix ", it) } + append("${prefix}}\n") + } + is Token.Raw -> append("${prefix}RAW ${token.value}\n") + } + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt similarity index 76% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt index 02cec4490..d57c226e9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt @@ -8,12 +8,9 @@ * */ -/* - * @author Karlatemp - */ - -package net.mamoe.mirai.console.internal.util +package net.mamoe.mirai.console.internal.util.semver +import net.mamoe.mirai.console.internal.util.semver.RangeTokenReader.dump import net.mamoe.mirai.console.util.SemVersion import kotlin.math.max import kotlin.math.min @@ -22,10 +19,9 @@ import kotlin.math.min internal object SemVersionInternal { private val directVersion = """^[0-9]+(\.[0-9]+)+(|[\-+].+)$""".toRegex() private val versionSelect = """^[0-9]+(\.[0-9]+)*\.x$""".toRegex() - private val versionRange = """([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\-\s*([0-9]+(\.[0-9]+)+(|[\-+].+))""".toRegex() private val versionMathRange = - """\[([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\,\s*([0-9]+(\.[0-9]+)+(|[\-+].+))\]""".toRegex() - private val versionRule = """^((\>\=)|(\<\=)|(\=)|(\>)|(\<))\s*([0-9]+(\.[0-9]+)+(|[\-+].+))$""".toRegex() + """([\[\(])([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\,\s*([0-9]+(\.[0-9]+)+(|[\-+].+))([\]\)])""".toRegex() + private val versionRule = """^((\>\=)|(\<\=)|(\=)|(\!\=)|(\>)|(\<))\s*([0-9]+(\.[0-9]+)+(|[\-+].+))$""".toRegex() private val SEM_VERSION_REGEX = """^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$""".toRegex() @@ -69,16 +65,19 @@ internal object SemVersionInternal { } } } + val mainVersion = version.substring(0, mainVersionEnd).parseMainVersion() return SemVersion( - mainVersion = version.substring(0, mainVersionEnd).parseMainVersion(), + major = mainVersion[0], + minor = mainVersion[1], + patch = mainVersion.getOrNull(2), identifier = identifier, metadata = metadata ) } @JvmStatic - private fun String.parseRule(): SemVersion.Requirement { - val trimmed = trim() + internal fun parseRule(rule: String): SemVersion.Requirement { + val trimmed = rule.trim() if (directVersion.matches(trimmed)) { val parsed = SemVersion.invoke(trimmed) return object : SemVersion.Requirement { @@ -95,22 +94,40 @@ internal object SemVersionInternal { override fun test(version: SemVersion): Boolean = regex.matches(version.toString()) } } - (versionRange.matchEntire(trimmed) ?: versionMathRange.matchEntire(trimmed))?.let { range -> - var start = SemVersion.invoke(range.groupValues[1]) - var end = SemVersion.invoke(range.groupValues[4]) + versionMathRange.matchEntire(trimmed)?.let { range -> + // 1 mode + // 2 first + // 5 sec + // 8 type + var typeStart = range.groupValues[1][0] + var typeEnd = range.groupValues[8][0] + var start = SemVersion.invoke(range.groupValues[2]) + var end = SemVersion.invoke(range.groupValues[5]) if (start > end) { val c = end end = start start = c + val x = typeEnd + typeEnd = typeStart + typeStart = x + } + val a: (SemVersion) -> Boolean = when (typeStart) { + '[', ']' -> ({ start <= it }) + '(', ')' -> ({ start < it }) + else -> throw AssertionError() + } + val b: (SemVersion) -> Boolean = when (typeEnd) { + '[', ']' -> ({ it <= end }) + '(', ')' -> ({ it < end }) + else -> throw AssertionError() } - val compareRange = start..end return object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean = version in compareRange + override fun test(version: SemVersion): Boolean = a(version) && b(version) } } versionRule.matchEntire(trimmed)?.let { result -> val operator = result.groupValues[1] - val version1 = SemVersion.invoke(result.groupValues[7]) + val version1 = SemVersion.invoke(result.groupValues[8]) return when (operator) { ">=" -> { object : SemVersion.Requirement { @@ -137,10 +154,15 @@ internal object SemVersionInternal { override fun test(version: SemVersion): Boolean = version.compareTo(version1) == 0 } } + "!=" -> { + object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean = version.compareTo(version1) != 0 + } + } else -> error("operator=$operator, version=$version1") } } - throw IllegalArgumentException("Cannot parse $this") + throw IllegalArgumentException("Cannot parse $rule") } private fun SemVersion.Requirement.withRule(rule: String): SemVersion.Requirement { @@ -155,19 +177,16 @@ internal object SemVersionInternal { if (requirement.isBlank()) { throw IllegalArgumentException("Invalid requirement: Empty requirement rule.") } - return requirement.split("||").map { - it.parseRule().withRule(it) - }.let { checks -> - if (checks.size == 1) return checks[0] - object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean { - checks.forEach { rule -> - if (rule.test(version)) return true - } - return false - } - }.withRule(requirement) - } + val tokens = RangeTokenReader.parseToTokens(requirement) + val collected = RangeTokenReader.collect(requirement, tokens.iterator(), true) + RangeTokenReader.check(requirement, collected.iterator(), null) + return kotlin.runCatching { + RangeTokenReader.parse(requirement, RangeTokenReader.Token.Group(collected, 0)).withRule(requirement) + }.onFailure { error -> + throw IllegalArgumentException("Exception in parsing $requirement\n\n" + buildString { + collected.forEach { dump("", it) } + }, error) + }.getOrThrow() } @JvmStatic @@ -176,16 +195,12 @@ internal object SemVersionInternal { // If $this equals $other (without metadata), // return same. - if (other.mainVersion.contentEquals(source.mainVersion) && source.identifier == other.identifier) { - return 0 - } - fun IntArray.getSafe(index: Int) = getOrElse(index) { 0 } - // Compare main-version - for (index in 0 until (max(source.mainVersion.size, other.mainVersion.size))) { - val result = source.mainVersion.getSafe(index).compareTo(other.mainVersion.getSafe(index)) - if (result != 0) return result - } + + source.major.compareTo(other.major).takeUnless { it == 0 }?.let { return it } + source.minor.compareTo(other.minor).takeUnless { it == 0 }?.let { return it } + (source.patch ?: 0).compareTo(other.patch ?: 0).takeUnless { it == 0 }?.let { return it } + // If main-versions are same. var identifier0 = source.identifier var identifier1 = other.identifier diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt index 736517b52..954d19919 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt @@ -16,7 +16,6 @@ import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.* import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.util.SemVersion -import net.mamoe.mirai.console.util.SemVersionRangeRequirementBuilder /** * JVM 插件的描述. 通常作为 `plugin.yml` @@ -187,31 +186,6 @@ public class JvmPluginDescriptionBuilder( this.dependencies.add(PluginDependency(pluginId, null, isOptional)) } - /** - * 示例: - * - * ``` - * dependsOn("org.example.test-plugin") { "1.0.0".."1.2.0" } - * dependsOn("org.example.test-plugin") { npmPattern("1.x || >=2.5.0 || 5.0.0 - 7.2.3") } - * dependsOn("org.example.test-plugin") { ivyPattern("[1.0,2.0[") } - * dependsOn("org.example.test-plugin") { custom { it.toString() == "1.0.0" } } - * ``` - * - * @see PluginDependency - * @see SemVersionRangeRequirementBuilder - */ - @ILoveKuriyamaMiraiForever - public fun dependsOn( - @ResolveContext(PLUGIN_ID) pluginId: String, - isOptional: Boolean = false, - versionRequirement: SemVersionRangeRequirementBuilder.() -> SemVersion.Requirement, - ): JvmPluginDescriptionBuilder = - apply { - this.dependencies.add(PluginDependency(pluginId, - SemVersionRangeRequirementBuilder.run(versionRequirement), - isOptional)) - } - @Suppress("DEPRECATION_ERROR") public fun build(): JvmPluginDescription = diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index 095280b90..a80c467a2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_VERSION import net.mamoe.mirai.console.internal.data.map -import net.mamoe.mirai.console.internal.util.SemVersionInternal +import net.mamoe.mirai.console.internal.util.semver.SemVersionInternal import net.mamoe.mirai.console.util.SemVersion.Companion.equals import net.mamoe.mirai.console.util.SemVersion.Requirement @@ -32,10 +32,13 @@ import net.mamoe.mirai.console.util.SemVersion.Requirement * * 解析示例: * - * `1.0.0-M4+c25733b8` 将会解析出三个内容, mainVersion (核心版本号), [identifier] (先行版本号) 和 [metadata] (元数据). + * `1.0.0-M4+c25733b8` 将会解析出下面的内容, + * [major] (主本号), [minor] (次版本号), [patch] (修订号), [identifier] (先行版本号) 和 [metadata] (元数据). * ``` * SemVersion( - * mainVersion = IntArray [1, 0, 0], + * major = 1, + * minor = 0, + * patch = 0, * identifier = "M4" * metadata = "c25733b8" * ) @@ -53,8 +56,12 @@ public data class SemVersion * @see SemVersion.invoke 字符串解析 */ internal constructor( - /** 核心版本号, 由主版本号, 次版本号和修订号组成, 其中修订号不一定存在 */ - public val mainVersion: IntArray, + /** 主版本号 */ + public val major: Int, + /** 次版本号 */ + public val minor: Int, + /** 修订号 */ + public val patch: Int?, /** 先行版本号识别符 */ public val identifier: String? = null, /** 版本号元数据, 不参与版本号对比([compareTo]), 但是参与版本号严格对比([equals]) */ @@ -106,21 +113,28 @@ internal constructor( * * - `1.0.0-M4` 要求 1.0.0-M4 版本, 且只能是 1.0.0-M4 版本 * - `1.x` 要求 1.x 版本 - * - `1.0.0 - 1.2.0` 要求 1.0.0 到 1.2.0 的任意版本, 注意 `-` 两边必须要有空格 - * - `[1.0.0, 1.2.0]` 与 `1.0.0 - 1.2.0` 一致 * - `> 1.0.0-RC` 要求 1.0.0-RC 之后的版本, 不能是 1.0.0-RC * - `>= 1.0.0-RC` 要求 1.0.0-RC 或之后的版本, 可以是 1.0.0-RC * - `< 1.0.0-RC` 要求 1.0.0-RC 之前的版本, 不能是 1.0.0-RC * - `<= 1.0.0-RC` 要求 1.0.0-RC 或之前的版本, 可以是 1.0.0-RC + * - `!= 1.0.0-RC` 要求 除了1.0.0-RC 的任何版本 + * - `[1.0.0, 1.2.0]` + * - `(1.0.0, 1.2.0]` + * - `[1.0.0, 1.2.0)` + * - `(1.0.0, 1.2.0)` [数学区间](https://baike.baidu.com/item/%E5%8C%BA%E9%97%B4/1273117) * - * 对于多个规则, 也允许使用 `||` 拼接. + * 对于多个规则, 允许使用逻辑符号 `{}`, `||`, `&&` * 例如: * - `1.x || 2.x || 3.0.0` * - `<= 0.5.3 || >= 1.0.0` + * - `{> 1.0 && < 1.5} || {> 1.8}` + * - `{> 1.0 && < 1.5} || {> 1.8}` + * - `> 1.0.0 && != 1.2.0` * * 特别注意: * - 依赖规则版本号不需要携带版本号元数据, 元数据不参与依赖需求的检查 * - 如果目标版本号携带有先行版本号, 请不要忘记先行版本号 + * - 因为 `()` 已经用于数学区间, 使用 `{}` 替代 `()` */ @Throws(IllegalArgumentException::class) @JvmStatic @@ -151,14 +165,14 @@ internal constructor( @Transient private val toString: String by lazy(LazyThreadSafetyMode.NONE) { buildString { - mainVersion.joinTo(this, ".") + append(major) + append('.').append(minor) + patch?.let { append('.').append(it) } identifier?.let { identifier -> - append('-') - append(identifier) + append('-').append(identifier) } metadata?.let { metadata -> - append('+') - append(metadata) + append('+').append(metadata) } } } @@ -169,7 +183,7 @@ internal constructor( * 将 [SemVersion] 转为 Kotlin data class 风格的 [String] */ public fun toStructuredString(): String { - return "SemVersion(mainVersion=${mainVersion.contentToString()}, identifier=$identifier, metadata=$metadata)" + return "SemVersion(major=$major, minor=$minor, patch=$patch, identifier=$identifier, metadata=$metadata)" } override fun equals(other: Any?): Boolean { @@ -182,7 +196,8 @@ internal constructor( } override fun hashCode(): Int { - var result = mainVersion.contentHashCode() + var result = major shl minor + result *= (patch ?: 1) result = 31 * result + (identifier?.hashCode() ?: 0) result = 31 * result + (metadata?.hashCode() ?: 0) return result diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt deleted file mode 100644 index 74145d1f8..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - * - */ - -package net.mamoe.mirai.console.util - -/** - * 构造 [SemVersion.Requirement] 的 DSL - */ -public object SemVersionRangeRequirementBuilder { - /** @see [SemVersion.parseRangeRequirement] */ - @ILoveHim188moeForever - public fun parse(rule: String): SemVersion.Requirement = SemVersion.parseRangeRequirement(rule) - - @ILoveHim188moeForever - public infix fun SemVersion.Requirement.or(other: SemVersion.Requirement): SemVersion.Requirement { - return object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean { - return this@or.test(version) || other.test(version) - } - - override fun toString(): String { - return "(${this@or}) or ($other)" - } - } - } - - @ILoveHim188moeForever - public infix fun String.or(other: String): SemVersion.Requirement = parse(this) or parse(other) - - @ILoveHim188moeForever - public infix fun SemVersion.Requirement.or(other: String): SemVersion.Requirement = or(parse(other)) - - @ILoveHim188moeForever - public infix fun String.or(other: SemVersion.Requirement): SemVersion.Requirement = parse(this) or other - - @ILoveHim188moeForever - public infix fun SemVersion.Requirement.and(other: SemVersion.Requirement): SemVersion.Requirement { - return object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean { - return this@and.test(version) && other.test(version) - } - - override fun toString(): String { - return "(${this@and}) or ($other)" - } - } - } - - @ILoveHim188moeForever - public infix fun String.and(other: String): SemVersion.Requirement = parse(this) and parse(other) - - @ILoveHim188moeForever - public infix fun SemVersion.Requirement.and(other: String): SemVersion.Requirement = and(parse(other)) - - @ILoveHim188moeForever - public infix fun String.and(other: SemVersion.Requirement): SemVersion.Requirement = parse(this) and other - - @Suppress("NOTHING_TO_INLINE") - @ILoveHim188moeForever - public inline fun custom(rule: SemVersion.Requirement): SemVersion.Requirement = rule - - /** - * 标注一个 [SemVersionRangeRequirementBuilder] DSL - */ - @Suppress("SpellCheckingInspection") - @Retention(AnnotationRetention.BINARY) - @DslMarker - internal annotation class ILoveHim188moeForever - - /** [SemVersionRangeRequirementBuilder] 的使用示例 */ - @Suppress("unused") - private class ExampleOfBuilder { - val e1 = SemVersionRangeRequirementBuilder.run { - "1.0.0" or "1.1.5" - } - val e2 = SemVersionRangeRequirementBuilder.run { - parse("> 1.0.0") and parse("< 1.2.3") - } - val e3 = SemVersionRangeRequirementBuilder.run { - ("> 1.0.0" and "< 1.2.3") or "2.0.0" - } - } -} \ No newline at end of file diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt index c7325c177..3f74dd191 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt @@ -48,6 +48,12 @@ internal class TestSemVersion { return this } + fun assertInvalid(requirement: String) { + kotlin.runCatching { + SemVersion.parseRangeRequirement(requirement) + }.onSuccess { assert(false) { requirement } } + } + fun SemVersion.Requirement.assertFalse(version: String): SemVersion.Requirement { assert(!test(version)) { version } return this @@ -59,19 +65,24 @@ internal class TestSemVersion { .assert("1.0").assert("1.1") .assert("1.5").assert("1.14514") .assertFalse("2.33") + SemVersion.parseRangeRequirement("2.0||1.2.x") + SemVersion.parseRangeRequirement("{2.0||1.2.x} && 1.1.0 &&1.2.3") SemVersion.parseRangeRequirement("2.0 || 1.2.x") .assert("2.0").assert("2.0.0") .assertFalse("2.1") .assert("1.2.5").assert("1.2.0").assertFalse("1.2") .assertFalse("1.0.0") - SemVersion.parseRangeRequirement("1.0.0 - 114.514.1919") - .assert("1.0.0") - .assert("114.514").assert("114.514.1919") - .assertFalse("0.0.1") - .assertFalse("4444.4444") SemVersion.parseRangeRequirement("[1.0.0, 19190.0]") .assert("1.0.0").assertFalse("0.1.0") .assert("19190.0").assertFalse("19198.10") + SemVersion.parseRangeRequirement("[1.0.0, 2.0.0)") + .assert("1.0.0").assert("1.2.3").assertFalse("2.0.0") + SemVersion.parseRangeRequirement("(2.0.0, 1.0.0]") + .assert("1.0.0").assert("1.2.3").assertFalse("2.0.0") + SemVersion.parseRangeRequirement("(2.0.0, 1.0.0)") + .assertFalse("1.0.0").assert("1.2.3").assertFalse("2.0.0") + SemVersion.parseRangeRequirement("(1.0.0, 2.0.0)") + .assertFalse("1.0.0").assert("1.2.3").assertFalse("2.0.0") SemVersion.parseRangeRequirement(" >= 1.0.0") .assert("1.0.0") .assert("114.514.1919") @@ -79,9 +90,30 @@ internal class TestSemVersion { .assertFalse("0.98774587") SemVersion.parseRangeRequirement("> 1.0.0") .assertFalse("1.0.0") - kotlin.runCatching { SemVersion.parseRangeRequirement("WPOXAXW") } - .onSuccess { assert(false) } + SemVersion.parseRangeRequirement("!= 1.0.0 && != 2.0.0") + .assert("1.2.3").assert("2.1.1") + .assertFalse("1.0").assertFalse("1.0.0") + .assertFalse("2.0").assertFalse("2.0.0") + .assert("2.0.1").assert("1.0.1") + SemVersion.parseRangeRequirement("> 1.0.0 || < 0.9.0") + .assertFalse("1.0.0") + .assert("0.8.0") + .assertFalse("0.9.0") + SemVersion.parseRangeRequirement("{>= 1.0.0 && <= 1.2.3} || {>= 2.0.0 && <= 2.2.3}") + .assertFalse("1.3.0") + .assert("1.0.0").assert("1.2.3") + .assertFalse("0.9.0") + .assert("2.0.0").assert("2.2.3").assertFalse("2.3.4") + + assertInvalid("WPOXAXW") + assertInvalid("1.0.0 || 1.0.0 && 1.0.0") + assertInvalid("{") + assertInvalid("}") + assertInvalid("") + assertInvalid("1.2.3 - 3.2.1") + assertInvalid("1.5.78 &&") + assertInvalid("|| 1.0.0") } private fun String.check() { @@ -112,8 +144,9 @@ internal class TestSemVersion { "5.1+68-7".check() "5.1+68-".check() } + @Test - internal fun testSemVersionOfficial(){ + internal fun testSemVersionOfficial() { """ 1.0-RC 0.0.4 diff --git a/buildSrc/src/main/kotlin/PublishingHelpers.kt b/buildSrc/src/main/kotlin/PublishingHelpers.kt index 69b954dac..95e16283a 100644 --- a/buildSrc/src/main/kotlin/PublishingHelpers.kt +++ b/buildSrc/src/main/kotlin/PublishingHelpers.kt @@ -7,6 +7,9 @@ import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.bundling.Jar import org.gradle.kotlin.dsl.* import upload.Bintray +import java.io.InputStream +import java.io.OutputStream +import java.security.MessageDigest import java.util.* import kotlin.reflect.KProperty @@ -51,6 +54,44 @@ internal fun org.gradle.api.Project.`publishing`(configure: org.gradle.api.publi (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("publishing", configure) +fun InputStream.md5(): ByteArray { + val digest = MessageDigest.getInstance("md5") + digest.reset() + use { input -> + object : OutputStream() { + override fun write(b: Int) { + digest.update(b.toByte()) + } + }.use { output -> + input.copyTo(output) + } + } + return digest.digest() +} + +@OptIn(ExperimentalUnsignedTypes::class) +@JvmOverloads +fun ByteArray.toUHexString( + separator: String = " ", + offset: Int = 0, + length: Int = this.size - offset +): String { + if (length == 0) { + return "" + } + val lastIndex = offset + length + return buildString(length * 2) { + this@toUHexString.forEachIndexed { index, it -> + if (index in offset until lastIndex) { + var ret = it.toUByte().toString(16).toUpperCase() + if (ret.length == 1) ret = "0$ret" + append(ret) + if (index < lastIndex - 1) append(separator) + } + } + } +} + inline fun Project.setupPublishing( artifactId: String, bintrayRepo: String = "mirai", diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index ef3a362e1..801f93344 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -9,7 +9,7 @@ object Versions { const val core = "1.3.0" - const val console = "1.0-RC-dev-28" + const val console = "1.0-RC-dev-29" const val consoleGraphical = "0.0.7" const val consoleTerminal = console diff --git a/docs/ConfiguringProjects.md b/docs/ConfiguringProjects.md index 6cd90981c..3fa2db93f 100644 --- a/docs/ConfiguringProjects.md +++ b/docs/ConfiguringProjects.md @@ -40,7 +40,7 @@ Mirai 鼓励插件开发者将自己的作品开源,并为此提供了模板 ### 使用 Gradle 插件配置项目 -`VERSION` 可在 +`VERSION`: [选择版本](#选择版本) 若使用 `build.gradle.kts`: ```kotlin diff --git a/docs/Run.md b/docs/Run.md index c4651e4bf..2e794c54f 100644 --- a/docs/Run.md +++ b/docs/Run.md @@ -2,9 +2,10 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。 -## 使用第三方工具自动独立启动 +## 使用工具自动独立启动 -https://github.com/LXY1226/MiraiOK +官方: https://github.com/iTXTech/mirai-console-loader +第三方: https://github.com/LXY1226/MiraiOK ## 手动配置独立启动