Update SemVersion rules

This commit is contained in:
Karlatemp 2020-09-18 12:34:30 +08:00
parent e299ecffb9
commit 001fac65cc
No known key found for this signature in database
GPG Key ID: 21FBDDF664FF06F8
3 changed files with 115 additions and 34 deletions

View File

@ -184,8 +184,8 @@ internal object SemVersionInternal {
identifier0 = identifier0.substring(ignoredSize)
identifier1 = identifier1.substring(ignoredSize)
// Multi-chunk comparing
val chunks0 = identifier0.split('-', '.', '_')
val chunks1 = identifier1.split('-', '.', '_')
val chunks0 = identifier0.split('-', '.')
val chunks1 = identifier1.split('-', '.')
chunkLoop@ for (index in 0 until (max(chunks0.size, chunks1.size))) {
val value0 = chunks0.getOrNull(index)
val value1 = chunks1.getOrNull(index)

View File

@ -39,16 +39,14 @@ import net.mamoe.mirai.console.util.SemVersion.Companion.equals
* metadata = "c25733b8"
* )
* ```
* 其中 identifier metadata 都是可选的, 该实现对于 mainVersion 的最大长度不作出限制,
* 也建议 mainVersion 的长度不要过长或过短
* 但是必须至少拥有两位及以上的版本描述符, (即必须拥有主版本号和次版本号).
*
* 比如 `1-M4` 是不合法的, 但是 `1.0-M4` 是合法的
* 其中 identifier metadata 都是可选的.
* 对于核心版本号, 此实现稍微比 semver 宽松一些, 允许 x.y 的存在.
* 但是不允许 0.0.0.0 之类的存在
*
*/
@Serializable
public data class SemVersion internal constructor(
/** 核心版本号, 至少包含一个主版本号和一个次版本号 */
/** 核心版本号, 由主版本号, 次版本号和修订号组成, 其中修订号不一定存在 */
public val mainVersion: IntArray,
/** 先行版本号识别符 */
public val identifier: String? = null,
@ -65,6 +63,9 @@ public data class SemVersion internal constructor(
}
public companion object {
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()
/** 解析核心版本号, eg: `1.0.0` -> IntArray[1, 0, 0] */
@JvmStatic
private fun String.parseMainVersion(): IntArray =
@ -78,14 +79,23 @@ public data class SemVersion internal constructor(
* - 必须包含主版本号和次版本号
* - 存在 先行版本号 的时候 先行版本号 不能为空
* - 存在 元数据 的时候 元数据 不能为空
* - 核心版本号只允许 `x.y` `x.y.z` 的存在
* - `1.0-RC` 是合法的
* - `1.0.0-RC` 也是合法的, `1.0-RC` 一样
* - `1.0.0.0-RC` 是不合法的, 将会抛出一个 [IllegalArgumentException]
*
* 注意情况:
* - 第一个 `+` 之后的所有内容全部识别为元数据
* - `1.0+METADATA-M4`, metadata="METADATA-M4"
* - 如果不确定版本号是否合法, 可以使用 [regex101.com](https://regex101.com/r/vkijKf/1/) 进行检查
* - 此实现使用的正则表达式为 `^(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-]+)*))?$`
*/
@Throws(IllegalArgumentException::class, NumberFormatException::class)
@JvmStatic
public fun parse(version: String): SemVersion {
if (!SEM_VERSION_REGEX.matches(version)) {
throw IllegalArgumentException("`$version` not a valid version")
}
var mainVersionEnd: Int = 0
kotlin.run {
val iterator = version.iterator()
@ -116,24 +126,9 @@ public data class SemVersion internal constructor(
}
}
return SemVersion(
mainVersion = version.substring(0, mainVersionEnd).also { mainVersion ->
if (mainVersion.indexOf('.') == -1) {
throw IllegalArgumentException("$mainVersion must has more than one label")
}
if (mainVersion.last() == '.') {
throw IllegalArgumentException("Version string cannot end-with `.`")
}
}.parseMainVersion(),
identifier = identifier?.also {
if (it.isBlank()) {
throw IllegalArgumentException("The identifier cannot be blank.")
}
},
metadata = metadata?.also {
if (it.isBlank()) {
throw IllegalArgumentException("The metadata cannot be blank.")
}
}
mainVersion = version.substring(0, mainVersionEnd).parseMainVersion(),
identifier = identifier,
metadata = metadata
)
}
@ -153,7 +148,7 @@ public data class SemVersion internal constructor(
*
* 对于多个规则, 也允许使用 `||` 拼接在一起.
* 例如:
* - `1.x || 2.x || 3.0`
* - `1.x || 2.x || 3.0.0`
* - `<= 0.5.3 || >= 1.0.0`
*
* 特别注意:

View File

@ -23,7 +23,7 @@ internal class TestSemVersion {
fun String.sem(): SemVersion = SemVersion.parse(this)
assert("1.0".sem() < "1.0.1".sem())
assert("1.0.0".sem() == "1.0".sem())
assert("1.1".sem() > "1.0.0.1".sem())
assert("1.1".sem() > "1.0.0".sem())
assert("1.0-M4".sem() < "1.0-M5".sem())
assert("1.0-M5-dev-7".sem() < "1.0-M5-dev-15".sem())
assert("1.0-M5-dev-79".sem() < "1.0-M5-dev-7001".sem())
@ -54,7 +54,6 @@ internal class TestSemVersion {
}
SemVersion.parseRangeRequirement("1.0")
.assert("1.0").assert("1.0.0")
.assert("1.0.0.0")
.assertFalse("1.1.0").assertFalse("2.0.0")
SemVersion.parseRangeRequirement("1.x")
.assert("1.0").assert("1.1")
@ -62,12 +61,12 @@ internal class TestSemVersion {
.assertFalse("2.33")
SemVersion.parseRangeRequirement("2.0 || 1.2.x")
.assert("2.0").assert("2.0.0")
.assertFalse("2.1").assertFalse("2.0.0.1")
.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.810")
SemVersion.parseRangeRequirement("1.0.0 - 114.514.1919")
.assert("1.0.0")
.assert("114.514").assert("114.514.1919.810")
.assert("114.514").assert("114.514.1919")
.assertFalse("0.0.1")
.assertFalse("4444.4444")
SemVersion.parseRangeRequirement("[1.0.0, 19190.0]")
@ -75,7 +74,7 @@ internal class TestSemVersion {
.assert("19190.0").assertFalse("19198.10")
SemVersion.parseRangeRequirement(" >= 1.0.0")
.assert("1.0.0")
.assert("114.514.1919.810")
.assert("114.514.1919")
.assertFalse("0.0.0")
.assertFalse("0.98774587")
SemVersion.parseRangeRequirement("> 1.0.0")
@ -85,6 +84,16 @@ internal class TestSemVersion {
}
private fun String.check() {
val sem = SemVersion.parse(this)
assert(this == sem.toString()) { "$this != $sem" }
}
private fun String.checkInvalid() {
kotlin.runCatching { SemVersion.parse(this) }
.onSuccess { assert(false) { "$this not a invalid sem-version" } }
}
@Test
internal fun testSemVersionParsing() {
fun String.check() {
@ -99,7 +108,7 @@ internal class TestSemVersion {
}
"0.0".check()
"1.0.0".check()
"1.2.3.4.5.6.7.8".check()
"1.2.3.4.5.6.7.8".checkInvalid()
"5555.0-A".check()
"5555.0-A+METADATA".check()
"5555.0+METADATA".check()
@ -113,4 +122,81 @@ internal class TestSemVersion {
"5.1+68-7".check()
"5.1+68-".check()
}
@Test
internal fun testSemVersionOfficial(){
"""
1.0-RC
0.0.4
1.2.3
10.20.30
1.1.2-prerelease+meta
1.1.2+meta
1.1.2+meta-valid
1.0.0-alpha
1.0.0-beta
1.0.0-alpha.beta
1.0.0-alpha.beta.1
1.0.0-alpha.1
1.0.0-alpha0.valid
1.0.0-alpha.0valid
1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay
1.0.0-rc.1+build.1
2.0.0-rc.1+build.123
1.2.3-beta
10.2.3-DEV-SNAPSHOT
1.2.3-SNAPSHOT-123
1.0.0
2.0.0
1.1.7
2.0.0+build.1848
2.0.1-alpha.1227
1.0.0-alpha+beta
1.2.3----RC-SNAPSHOT.12.9.1--.12+788
1.2.3----R-S.12.9.1--.12+meta
1.2.3----RC-SNAPSHOT.12.9.1--.12
1.0.0+0.build.1-rc.10000aaa-kk-0.1
1.0.0-0A.is.legal
""".trimIndent().split('\n').asSequence()
.filter { it.isNotBlank() }.map { it.trim() }.forEach { it.check() }
"""
1
1.2.3-0123
1.2.3-0123.0123
1.1.2+.123
+invalid
-invalid
-invalid+invalid
-invalid.01
alpha
alpha.beta
alpha.beta.1
alpha.1
alpha+beta
alpha_beta
alpha.
alpha..
beta
1.0.0-alpha_beta
-alpha.
1.0.0-alpha..
1.0.0-alpha..1
1.0.0-alpha...1
1.0.0-alpha....1
1.0.0-alpha.....1
1.0.0-alpha......1
1.0.0-alpha.......1
01.1.1
1.01.1
1.1.01
1.2.3.DEV
1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788
1.2.31.2.3-RC
-1.0.3-gamma+b7718
+justmeta
9.8.7+meta+meta
9.8.7-whatever+meta+meta
99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12
""".trimIndent().split('\n').asSequence()
.filter { it.isNotBlank() }.map { it.trim() }.forEach { it.checkInvalid() }
}
}