mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 18:40:15 +08:00
Sem Version (#164)
* Sem Version * Review: Add missing logic * Review code - Removed @JvmField - Comments - Fix Compare Logic - Add tests from SemVer.org * Deleted redundant statement * Rename RangeChecker to RangeRequirement * Code Review - Move SemVersion#compareTo to SemVersionInternal#compareInternal - Move top-level functions to companion object - Make SemVersion comparable * KDoc * Update KDoc; fix parseRangeRequirement * Update KDoc * Update comment * Update KDoc * Update KDoc * Typo * Typo
This commit is contained in:
parent
466b067d9f
commit
dc81835b68
@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.console.internal.util
|
||||||
|
|
||||||
|
import net.mamoe.mirai.console.util.SemVersion
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
@Suppress("RegExpRedundantEscape")
|
||||||
|
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()
|
||||||
|
private fun Collection<*>.dump() {
|
||||||
|
forEachIndexed { index, value ->
|
||||||
|
println("$index, $value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
private fun String.parseRule(): SemVersion.RangeRequirement {
|
||||||
|
val trimmed = trim()
|
||||||
|
if (directVersion.matches(trimmed)) {
|
||||||
|
val parsed = SemVersion.parse(trimmed)
|
||||||
|
return SemVersion.RangeRequirement {
|
||||||
|
it.compareTo(parsed) == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (versionSelect.matches(trimmed)) {
|
||||||
|
val regex = ("^" +
|
||||||
|
trimmed.replace(".", "\\.")
|
||||||
|
.replace("x", ".+") +
|
||||||
|
"$"
|
||||||
|
).toRegex()
|
||||||
|
return SemVersion.RangeRequirement {
|
||||||
|
regex.matches(it.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(versionRange.matchEntire(trimmed) ?: versionMathRange.matchEntire(trimmed))?.let { range ->
|
||||||
|
var start = SemVersion.parse(range.groupValues[1])
|
||||||
|
var end = SemVersion.parse(range.groupValues[4])
|
||||||
|
if (start > end) {
|
||||||
|
val c = end
|
||||||
|
end = start
|
||||||
|
start = c
|
||||||
|
}
|
||||||
|
val compareRange = start..end
|
||||||
|
return SemVersion.RangeRequirement {
|
||||||
|
it in compareRange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
versionRule.matchEntire(trimmed)?.let { result ->
|
||||||
|
val operator = result.groupValues[1]
|
||||||
|
val version = SemVersion.parse(result.groupValues[7])
|
||||||
|
return when (operator) {
|
||||||
|
">=" -> {
|
||||||
|
SemVersion.RangeRequirement { it >= version }
|
||||||
|
}
|
||||||
|
">" -> {
|
||||||
|
SemVersion.RangeRequirement { it > version }
|
||||||
|
}
|
||||||
|
"<=" -> {
|
||||||
|
SemVersion.RangeRequirement { it <= version }
|
||||||
|
}
|
||||||
|
"<" -> {
|
||||||
|
SemVersion.RangeRequirement { it < version }
|
||||||
|
}
|
||||||
|
"=" -> {
|
||||||
|
SemVersion.RangeRequirement { it.compareTo(version) == 0 }
|
||||||
|
}
|
||||||
|
else -> throw AssertionError("operator=$operator, version=$version")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw UnsupportedOperationException("Cannot parse $this")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SemVersion.RangeRequirement.withRule(rule: String): SemVersion.RangeRequirement {
|
||||||
|
return object : SemVersion.RangeRequirement {
|
||||||
|
override fun check(version: SemVersion): Boolean {
|
||||||
|
return this@withRule.check(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun parseRangeRequirement(requirement: String): SemVersion.RangeRequirement {
|
||||||
|
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]
|
||||||
|
SemVersion.RangeRequirement {
|
||||||
|
checks.forEach { rule ->
|
||||||
|
if (rule.check(it)) return@RangeRequirement true
|
||||||
|
}
|
||||||
|
return@RangeRequirement false
|
||||||
|
}.withRule(requirement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun SemVersion.compareInternal(other: SemVersion): Int {
|
||||||
|
// ignored metadata in comparing
|
||||||
|
|
||||||
|
// If $this equals $other (without metadata),
|
||||||
|
// return same.
|
||||||
|
if (other.mainVersion.contentEquals(mainVersion) && identifier == other.identifier) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
fun IntArray.getSafe(index: Int) = getOrElse(index) { 0 }
|
||||||
|
|
||||||
|
// Compare main-version
|
||||||
|
for (index in 0 until (max(mainVersion.size, other.mainVersion.size))) {
|
||||||
|
val result = mainVersion.getSafe(index).compareTo(other.mainVersion.getSafe(index))
|
||||||
|
if (result != 0) return result
|
||||||
|
}
|
||||||
|
// If main-versions are same.
|
||||||
|
var identifier0 = identifier
|
||||||
|
var identifier1 = other.identifier
|
||||||
|
// If anyone doesn't have the identifier...
|
||||||
|
if (identifier0 == null || identifier1 == null) {
|
||||||
|
return when (identifier0) {
|
||||||
|
identifier1 -> { // null == null
|
||||||
|
// Nobody has identifier
|
||||||
|
0
|
||||||
|
}
|
||||||
|
null -> {
|
||||||
|
// $other has identifier, but $this don't have identifier
|
||||||
|
// E.g:
|
||||||
|
// this = 1.0.0
|
||||||
|
// other = 1.0.0-dev
|
||||||
|
1
|
||||||
|
}
|
||||||
|
// It is the opposite of the above.
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun String.getSafe(index: Int) = getOrElse(index) { ' ' }
|
||||||
|
|
||||||
|
// ignored same prefix
|
||||||
|
fun getSameSize(s1: String, s2: String): Int {
|
||||||
|
val size = min(s1.length, s2.length)
|
||||||
|
// 1.0-RC19 -> 19
|
||||||
|
// 1.0-RC107 -> 107
|
||||||
|
var realSameSize = 0
|
||||||
|
for (index in 0 until size) {
|
||||||
|
if (s1[index] != s2[index]) {
|
||||||
|
return realSameSize
|
||||||
|
} else {
|
||||||
|
if (!s1[index].isDigit()) {
|
||||||
|
realSameSize = index + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return realSameSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// We ignore the same parts. Because we only care about the differences.
|
||||||
|
// E.g:
|
||||||
|
// 1.0-RC1 -> 1
|
||||||
|
// 1.0-RC2 -> 2
|
||||||
|
val ignoredSize = getSameSize(identifier0, identifier1)
|
||||||
|
identifier0 = identifier0.substring(ignoredSize)
|
||||||
|
identifier1 = identifier1.substring(ignoredSize)
|
||||||
|
// Multi-chunk comparing
|
||||||
|
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)
|
||||||
|
// Any chunk is null
|
||||||
|
if (value0 == null || value1 == null) {
|
||||||
|
// value0 == null && value1 == null is impossible
|
||||||
|
return if (value0 == null) {
|
||||||
|
// E.g:
|
||||||
|
// value0 = 1.0-RC-dev
|
||||||
|
// value1 = 1.0-RC-dev-1
|
||||||
|
-1
|
||||||
|
} else {
|
||||||
|
// E.g:
|
||||||
|
// value0 = 1.0-RC-dev-1
|
||||||
|
// value1 = 1.0-RC-dev
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val result = value0.toInt().compareTo(value1.toInt())
|
||||||
|
if (result != 0) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
continue@chunkLoop
|
||||||
|
} catch (ignored: NumberFormatException) {
|
||||||
|
}
|
||||||
|
// compare chars
|
||||||
|
for (index0 in 0 until (max(value0.length, value1.length))) {
|
||||||
|
val result = value0.getSafe(index0).compareTo(value1.getSafe(index0))
|
||||||
|
if (result != 0)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,239 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.console.util
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
import net.mamoe.mirai.console.internal.util.SemVersionInternal
|
||||||
|
import net.mamoe.mirai.console.util.SemVersion.Companion.equals
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语义化版本支持
|
||||||
|
*
|
||||||
|
* 在阅读此文件前, 请先阅读 https://semver.org/lang/zh-CN/
|
||||||
|
* 该文档说明了语义化版本是什么, 此文件不再过多描述
|
||||||
|
*
|
||||||
|
* ----
|
||||||
|
*
|
||||||
|
* 这是一个例子 `1.0.0-M4+c25733b8`
|
||||||
|
*
|
||||||
|
* 将会解析出三个内容, mainVersion(核心版本号), identifier(先行版本号) 和 metadata(元数据).
|
||||||
|
*
|
||||||
|
* 对这个例子进行解析会得到
|
||||||
|
* ```
|
||||||
|
* SemVersion(
|
||||||
|
* mainVersion = IntArray [1, 0, 0],
|
||||||
|
* identifier = "M4"
|
||||||
|
* metadata = "c25733b8"
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
* 其中 identifier 和 metadata 都是可选的, 该实现对于 mainVersion 的最大长度不作出限制,
|
||||||
|
* 也建议 mainVersion 的长度不要过长或过短
|
||||||
|
* 但是必须至少拥有两位及以上的版本描述符, (即必须拥有主版本号和次版本号).
|
||||||
|
*
|
||||||
|
* 比如 `1-M4` 是不合法的, 但是 `1.0-M4` 是合法的
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
public data class SemVersion internal constructor(
|
||||||
|
/** 核心版本号, 至少包含一个主版本号和一个次版本号 */
|
||||||
|
public val mainVersion: IntArray,
|
||||||
|
/** 先行版本号识别符 */
|
||||||
|
public val identifier: String? = null,
|
||||||
|
/** 版本号元数据, 不参与版本号对比([compareTo]), 但是参与版本号严格对比([equals]) */
|
||||||
|
public val metadata: String? = null
|
||||||
|
) : Comparable<SemVersion> {
|
||||||
|
/**
|
||||||
|
* 一条依赖规则
|
||||||
|
* @see [parseRangeRequirement]
|
||||||
|
*/
|
||||||
|
public fun interface RangeRequirement {
|
||||||
|
/** 在 [version] 满足此要求时返回 true */
|
||||||
|
public fun check(version: SemVersion): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
/** 解析核心版本号, eg: `1.0.0` -> IntArray[1, 0, 0] */
|
||||||
|
@JvmStatic
|
||||||
|
private fun String.parseMainVersion(): IntArray =
|
||||||
|
split('.').map { it.toInt() }.toIntArray()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析一个版本号, 将会返回一个 [SemVersion],
|
||||||
|
* 如果发生解析错误将会抛出一个 [IllegalArgumentException] 或者 [NumberFormatException]
|
||||||
|
*
|
||||||
|
* 对于版本号的组成, 我们有以下规定:
|
||||||
|
* - 必须包含主版本号和次版本号
|
||||||
|
* - 存在 先行版本号 的时候 先行版本号 不能为空
|
||||||
|
* - 存在 元数据 的时候 元数据 不能为空
|
||||||
|
*
|
||||||
|
* 注意情况:
|
||||||
|
* - 第一个 `+` 之后的所有内容全部识别为元数据
|
||||||
|
* - `1.0+METADATA-M4`, metadata="METADATA-M4"
|
||||||
|
*/
|
||||||
|
@Throws(IllegalArgumentException::class, NumberFormatException::class)
|
||||||
|
@JvmStatic
|
||||||
|
public fun parse(version: String): SemVersion {
|
||||||
|
var mainVersionEnd: Int = 0
|
||||||
|
kotlin.run {
|
||||||
|
val iterator = version.iterator()
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
val next = iterator.next()
|
||||||
|
if (next == '-' || next == '+') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
mainVersionEnd++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var identifier: String? = null
|
||||||
|
var metadata: String? = null
|
||||||
|
if (mainVersionEnd != version.length) {
|
||||||
|
when (version[mainVersionEnd]) {
|
||||||
|
'-' -> {
|
||||||
|
val metadataSplitter = version.indexOf('+', startIndex = mainVersionEnd)
|
||||||
|
if (metadataSplitter == -1) {
|
||||||
|
identifier = version.substring(mainVersionEnd + 1)
|
||||||
|
} else {
|
||||||
|
identifier = version.substring(mainVersionEnd + 1, metadataSplitter)
|
||||||
|
metadata = version.substring(metadataSplitter + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'+' -> {
|
||||||
|
metadata = version.substring(mainVersionEnd + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析一条依赖需求描述, 在无法解析的时候抛出 [IllegalArgumentException]
|
||||||
|
*
|
||||||
|
* 对于一条规则, 有以下方式可选
|
||||||
|
*
|
||||||
|
* - `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.x || 2.x || 3.0`
|
||||||
|
* - `<= 0.5.3 || >= 1.0.0`
|
||||||
|
*
|
||||||
|
* 特别注意:
|
||||||
|
* - 依赖规则版本号不需要携带版本号元数据, 元数据不参与依赖需求的检查
|
||||||
|
* - 如果目标版本号携带有先行版本号, 请不要忘记先行版本号
|
||||||
|
*/
|
||||||
|
@Throws(IllegalArgumentException::class)
|
||||||
|
@JvmStatic
|
||||||
|
public fun parseRangeRequirement(requirement: String): RangeRequirement {
|
||||||
|
return SemVersionInternal.parseRangeRequirement(requirement)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @see [RangeRequirement.check] */
|
||||||
|
@JvmStatic
|
||||||
|
public fun RangeRequirement.check(version: String): Boolean = check(parse(version))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当满足 [requirement] 时返回 true, 否则返回 false
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
public fun SemVersion.satisfies(requirement: RangeRequirement): Boolean = requirement.check(this)
|
||||||
|
|
||||||
|
/** for Kotlin only */
|
||||||
|
@JvmStatic
|
||||||
|
@JvmSynthetic
|
||||||
|
public operator fun RangeRequirement.contains(version: SemVersion): Boolean = check(version)
|
||||||
|
|
||||||
|
/** for Kotlin only */
|
||||||
|
@JvmStatic
|
||||||
|
@JvmSynthetic
|
||||||
|
public operator fun RangeRequirement.contains(version: String): Boolean = check(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private var toString: String? = null // For cache.
|
||||||
|
override fun toString(): String {
|
||||||
|
return toString ?: kotlin.run {
|
||||||
|
buildString {
|
||||||
|
mainVersion.joinTo(this, ".")
|
||||||
|
identifier?.let { identifier ->
|
||||||
|
append('-')
|
||||||
|
append(identifier)
|
||||||
|
}
|
||||||
|
metadata?.let { metadata ->
|
||||||
|
append('+')
|
||||||
|
append(metadata)
|
||||||
|
}
|
||||||
|
}.also { toString = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 [SemVersion] 转为 Kotlin data class 风格的 [String]
|
||||||
|
*/
|
||||||
|
public fun toStructuredString(): String {
|
||||||
|
return "SemVersion(mainVersion=${mainVersion.contentToString()}, identifier=$identifier, metadata=$metadata)"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as SemVersion
|
||||||
|
|
||||||
|
return compareTo(other) == 0 && other.identifier == identifier && other.metadata == metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = mainVersion.contentHashCode()
|
||||||
|
result = 31 * result + (identifier?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (metadata?.hashCode() ?: 0)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares this object with the specified object for order. Returns zero if this object is equal
|
||||||
|
* to the specified [other] object, a negative number if it's less than [other], or a positive number
|
||||||
|
* if it's greater than [other].
|
||||||
|
*/
|
||||||
|
public override operator fun compareTo(other: SemVersion): Int {
|
||||||
|
return SemVersionInternal.run { compareInternal(other) }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.console.util
|
||||||
|
|
||||||
|
import net.mamoe.mirai.console.util.SemVersion.Companion.check
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class TestSemVersion {
|
||||||
|
@Test
|
||||||
|
internal fun testCompare() {
|
||||||
|
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.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())
|
||||||
|
assert("1.0-M6".sem() > "1.0-M5-dev-15".sem())
|
||||||
|
assert("1.0-RC".sem() > "1.0-M5-dev-15".sem())
|
||||||
|
assert("1.0-RC2".sem() > "1.0-RC".sem())
|
||||||
|
// example on semver
|
||||||
|
// 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0
|
||||||
|
assert("1.0.0-alpha".sem() < "1.0.0-alpha.1".sem())
|
||||||
|
assert("1.0.0-alpha.1".sem() < "1.0.0-alpha.beta".sem())
|
||||||
|
assert("1.0.0-alpha.beta".sem() < "1.0.0-beta".sem())
|
||||||
|
assert("1.0.0-beta".sem() < "1.0.0-beta.2".sem())
|
||||||
|
assert("1.0.0-beta.2".sem() < "1.0.0-beta.11".sem())
|
||||||
|
assert("1.0.0-beta.11".sem() < "1.0.0-rc.1".sem())
|
||||||
|
assert("1.0.0-rc.1".sem() < "1.0.0".sem())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
internal fun testRequirement() {
|
||||||
|
fun SemVersion.RangeRequirement.assert(version: String): SemVersion.RangeRequirement {
|
||||||
|
assert(check(version)) { version }
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SemVersion.RangeRequirement.assertFalse(version: String): SemVersion.RangeRequirement {
|
||||||
|
assert(!check(version)) { version }
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
.assert("1.5").assert("1.14514")
|
||||||
|
.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")
|
||||||
|
.assert("1.2.5").assert("1.2.0").assertFalse("1.2")
|
||||||
|
.assertFalse("1.0.0")
|
||||||
|
SemVersion.parseRangeRequirement("1.0.0 - 114.514.1919.810")
|
||||||
|
.assert("1.0.0")
|
||||||
|
.assert("114.514").assert("114.514.1919.810")
|
||||||
|
.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")
|
||||||
|
.assert("1.0.0")
|
||||||
|
.assert("114.514.1919.810")
|
||||||
|
.assertFalse("0.0.0")
|
||||||
|
.assertFalse("0.98774587")
|
||||||
|
SemVersion.parseRangeRequirement("> 1.0.0")
|
||||||
|
.assertFalse("1.0.0")
|
||||||
|
kotlin.runCatching { SemVersion.parseRangeRequirement("WPOXAXW") }
|
||||||
|
.onSuccess { assert(false) }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
internal fun testSemVersionParsing() {
|
||||||
|
fun String.check() {
|
||||||
|
val sem = SemVersion.parse(this)
|
||||||
|
assert(this == sem.toString()) { "$this != $sem" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.checkInvalid() {
|
||||||
|
kotlin.runCatching { SemVersion.parse(this) }
|
||||||
|
.onSuccess { assert(false) { "$this not a invalid sem-version" } }
|
||||||
|
.onFailure { println("$this - $it") }
|
||||||
|
}
|
||||||
|
"0.0".check()
|
||||||
|
"1.0.0".check()
|
||||||
|
"1.2.3.4.5.6.7.8".check()
|
||||||
|
"5555.0-A".check()
|
||||||
|
"5555.0-A+METADATA".check()
|
||||||
|
"5555.0+METADATA".check()
|
||||||
|
"987.0+wwwxx-wk".check()
|
||||||
|
"NOT.NUMBER".checkInvalid()
|
||||||
|
"0".checkInvalid()
|
||||||
|
"".checkInvalid()
|
||||||
|
"1.".checkInvalid()
|
||||||
|
"0.1-".checkInvalid()
|
||||||
|
"1.9+".checkInvalid()
|
||||||
|
"5.1+68-7".check()
|
||||||
|
"5.1+68-".check()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user