From 62527f0ed0ce4e2d676341c3bfd884eb9c98c72e Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 20 Sep 2020 22:48:54 +0800 Subject: [PATCH 1/5] Extract SemVersion.major, minor, patch --- .../internal/util/SemVersionInternal.kt | 19 ++++++------ .../mamoe/mirai/console/util/SemVersion.kt | 30 ++++++++++++------- 2 files changed, 28 insertions(+), 21 deletions(-) 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/SemVersionInternal.kt index 02cec4490..2cbe56ec4 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/SemVersionInternal.kt @@ -69,8 +69,11 @@ 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 ) @@ -176,16 +179,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/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index 095280b90..bed586514 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 @@ -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]) */ @@ -151,14 +158,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 +176,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 +189,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 From 1c909ae752a493c42041d062dd2d347ad165a997 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 21 Sep 2020 12:15:01 +0800 Subject: [PATCH 2/5] Better Requirement Rule --- .../internal/util/semver/RangeTokenReader.kt | 251 ++++++++++++++++++ .../util/{ => semver}/SemVersionInternal.kt | 36 ++- .../mamoe/mirai/console/util/SemVersion.kt | 2 +- .../mirai/console/util/TestSemVersion.kt | 30 ++- 4 files changed, 294 insertions(+), 25 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/RangeTokenReader.kt rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/{ => semver}/SemVersionInternal.kt (91%) 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 91% 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 2cbe56ec4..afe950ce3 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 @@ -80,8 +77,8 @@ internal object SemVersionInternal { } @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 { @@ -143,7 +140,7 @@ internal object SemVersionInternal { 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 { @@ -158,19 +155,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 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 bed586514..de1d647f8 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 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..ac7975d51 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,6 +65,8 @@ 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") @@ -79,9 +87,24 @@ 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 || < 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.5.78 &&") + assertInvalid("|| 1.0.0") } private fun String.check() { @@ -112,8 +135,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 From 00781c215a15dcaf8fa23fe84d9b01cf49c32822 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 21 Sep 2020 12:31:12 +0800 Subject: [PATCH 3/5] Update Math interval --- .../util/semver/SemVersionInternal.kt | 31 ++++++++++++++----- .../mirai/console/util/TestSemVersion.kt | 14 ++++++--- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt index afe950ce3..f71be72f6 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt @@ -19,9 +19,8 @@ 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() + """([\[\(])([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 = @@ -95,17 +94,35 @@ 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 -> 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 ac7975d51..e7de240c9 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 @@ -72,14 +72,17 @@ internal class TestSemVersion { .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") @@ -103,6 +106,7 @@ internal class TestSemVersion { assertInvalid("{") assertInvalid("}") assertInvalid("") + assertInvalid("1.2.3 - 3.2.1") assertInvalid("1.5.78 &&") assertInvalid("|| 1.0.0") } From 209bc97b32824b6e03805dc86a593c47a7953ecf Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 21 Sep 2020 12:42:05 +0800 Subject: [PATCH 4/5] Update KDoc --- .../internal/util/semver/SemVersionInternal.kt | 9 +++++++-- .../net/mamoe/mirai/console/util/SemVersion.kt | 13 ++++++++++--- .../net/mamoe/mirai/console/util/TestSemVersion.kt | 5 +++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt index f71be72f6..d57c226e9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt @@ -21,7 +21,7 @@ internal object SemVersionInternal { private val versionSelect = """^[0-9]+(\.[0-9]+)*\.x$""".toRegex() private val versionMathRange = """([\[\(])([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\,\s*([0-9]+(\.[0-9]+)+(|[\-+].+))([\]\)])""".toRegex() - private val versionRule = """^((\>\=)|(\<\=)|(\=)|(\>)|(\<))\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() @@ -127,7 +127,7 @@ internal object SemVersionInternal { } 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 { @@ -154,6 +154,11 @@ 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") } } 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 de1d647f8..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 @@ -113,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 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 e7de240c9..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 @@ -90,6 +90,11 @@ internal class TestSemVersion { .assertFalse("0.98774587") SemVersion.parseRangeRequirement("> 1.0.0") .assertFalse("1.0.0") + 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") From dc0ba1d8ffdd27335cdc72ebbcdc51346b4e3c65 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 21 Sep 2020 12:47:53 +0800 Subject: [PATCH 5/5] Fix SemVersionRangeRequirementBuilder --- .../console/util/SemVersionRangeRequirementBuilder.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 index 74145d1f8..be4b936ce 100644 --- 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 @@ -26,7 +26,7 @@ public object SemVersionRangeRequirementBuilder { } override fun toString(): String { - return "(${this@or}) or ($other)" + return "{${this@or}} || {$other}" } } } @@ -48,7 +48,7 @@ public object SemVersionRangeRequirementBuilder { } override fun toString(): String { - return "(${this@and}) or ($other)" + return "{${this@and}} && {$other}" } } } @@ -64,7 +64,12 @@ public object SemVersionRangeRequirementBuilder { @Suppress("NOTHING_TO_INLINE") @ILoveHim188moeForever - public inline fun custom(rule: SemVersion.Requirement): SemVersion.Requirement = rule + public fun custom(rule: (SemVersion) -> Boolean): SemVersion.Requirement = object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean = rule(version) + override fun toString(): String { + return "Custom{$rule}" + } + } /** * 标注一个 [SemVersionRangeRequirementBuilder] DSL