mcdrpost.utils.version 源代码

import re
from types import NotImplementedType
from typing import Any, NamedTuple, TypeAlias, TypeVar, overload

from mcdreforged.utils import class_utils

from mcdrpost.utils.general import TotalOrdering

ValidVersionTupleType: TypeAlias = (
    tuple[int, int]  # major and minor, its patch version will be set to 0
    # major, minor, patch
    | tuple[int, int, int]
    # major, minor, patch, pre_release
    | tuple[int, int, int, str]
    # major, minor, patch, pre_release, build_metadata
    # pre_release cannot be None, but can be an empty str for a non-pre-release version
    | tuple[int, int, int, str, str]
)

# TODO: transform into 3.12 generic grammar
SemanticVersionType = TypeVar("SemanticVersionType", bound="SemanticVersion")


class SimpleVersionTuple(NamedTuple):
    major: int
    minor: int
    patch: int = 0
    pre_release: str = ""
    build_metadata: str = ""

    @property
    def __version_string(self):
        s = f"{self.major}.{self.minor}.{self.patch}"
        if self.pre_release:
            s += f"-{self.pre_release}"
        if self.build_metadata:
            s += f"+{self.build_metadata}"

        return s

    def to_semantic_version(self) -> "SemanticVersion":
        return SemanticVersion(self.__version_string)


ComparableType: TypeAlias = (
    SemanticVersionType | SimpleVersionTuple | ValidVersionTupleType | str
)


[文档] class SemanticVersion(TotalOrdering[ComparableType]): """语义化版本号 该类生成的实例支持不同的比较方法, 不仅可以和自己比较, 还可以和 :class:`str`, :class:`tuple` 类型比较, 只需要它确实是一个语义化版本号的样子 .. note:: :class:`tuple` 的比较按 ``(major, minor, patch, prerelease, build_metadata)`` 进行比较 """ PATTERN = re.compile( r"^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$" ) """:meta private:""" major: int """主版本号""" minor: int """次版本号""" patch: int """补丁版本号""" pre_release: str | None = None """预发布版本号""" build_metadata: str | None = None """构建元数据""" def __init__(self, version_str: str) -> None: self._original_string = version_str match = re.match(self.PATTERN, version_str) if not match: raise ValueError(f"Invalid version string: {version_str}") major, minor, patch, self.pre_release, self.build_metadata = match.groups() if any(i is None for i in [major, minor, patch]): raise ValueError(f"Invalid semantic version string: {version_str}") self.major = int(major) self.minor = int(minor) self.patch = int(patch) @property def is_pre_release(self) -> bool: """是否是预发布版本""" return self.pre_release is not None @overload @staticmethod def __param_normalize(param: Any) -> NotImplementedType: ... @overload @staticmethod def __param_normalize(param: ComparableType) -> SemanticVersionType: # type: ignore[type-var, misc] ... @staticmethod def __param_normalize(param): if isinstance(param, str): return SemanticVersion(param) elif isinstance(param, tuple): return SimpleVersionTuple(*param).to_semantic_version() elif isinstance(param, SemanticVersion): return param else: return NotImplemented def __eq__(self, other) -> bool: other = self.__param_normalize(other) if other is NotImplemented: return False return (self.major, self.minor, self.patch, self.pre_release) == ( other.major, other.minor, other.patch, other.pre_release, ) @staticmethod def __compare_pre_release(pre1: str, pre2: str) -> bool: """比较预发布版本号""" parts1 = pre1.split(".") parts2 = pre2.split(".") for p1, p2 in zip(parts1, parts2): # 尝试转换为数字比较,否则按字符串比较 try: num1, num2 = int(p1), int(p2) if num1 != num2: return num1 < num2 except ValueError: if p1 != p2: return p1 < p2 # 如果共同部分都相同,长度短的更小 return len(parts1) < len(parts2) def __lt__(self, other) -> bool: n_other = self.__param_normalize(other) if n_other is NotImplemented: return NotImplemented if (self.major, self.minor, self.patch) < ( n_other.major, n_other.minor, n_other.patch, ): return True elif (self.major, self.minor, self.patch) > ( n_other.major, n_other.minor, n_other.patch, ): return False elif self.pre_release is None: return False elif n_other.pre_release is None: return True return self.__compare_pre_release(self.pre_release, n_other.pre_release) def __str__(self) -> str: return self._original_string def __repr__(self) -> str: return class_utils.represent(self)
MinecraftVersionType = TypeVar("MinecraftVersionType", bound="MinecraftVersion")
[文档] class MinecraftVersion(TotalOrdering[ComparableType | MinecraftVersionType]): """Minecraft 版本, 主要目的是兼容新版本号系统 如果是普通的 1.x 版本, 那么它相当于语义化版本号 如果是新的版本命名系统, 那么 major 会储存年份, minor 会储存版本号, 快照版本会在 patch 和 pre_release 中储存, 其中的 pre_release 是 str 类型并且会保留 ``snapshot`` """ major: int """主版本号""" minor: int """次版本号""" patch: int """补丁版本号, 或快照版本号(snapshot后的数字)""" pre_release: str | None = None """预发布版本号""" build_metadata: str | None = None """构建元数据""" version: SemanticVersion """语义化版本号""" __NEW_VERSION_PATTERN = re.compile(r"(\d+)\.(\d+)(-snapshot-(\d+))?") def __init__(self, original_version_str: str): try: self.version = SemanticVersion(original_version_str) except ValueError: match = re.match(self.__NEW_VERSION_PATTERN, original_version_str) if not match: raise ValueError(f"Invalid version string: {original_version_str}") major, minor, pre_release, patch = match.groups() self.build_metadata = None self.version = SimpleVersionTuple( int(major), int(minor), int(patch), pre_release[1:], ).to_semantic_version() self.major = self.version.major self.minor = self.version.minor self.patch = self.version.patch self.pre_release = self.version.pre_release self.build_metadata = self.version.build_metadata @overload @staticmethod def __param_normalize( other: ComparableType | MinecraftVersionType, ) -> MinecraftVersionType: pass @overload @staticmethod def __param_normalize(other: Any) -> NotImplementedType: pass @staticmethod def __param_normalize(other): if isinstance(other, MinecraftVersion): return other if isinstance(other, SimpleVersionTuple): other = other.to_semantic_version() elif isinstance(other, tuple): other = SimpleVersionTuple(*other).to_semantic_version() if isinstance(other, SemanticVersion): other = str(other) if isinstance(other, str): return MinecraftVersion(other) return NotImplemented def __eq__(self, other) -> bool: other = self.__param_normalize(other) if other is NotImplemented: return False return self.version == other.version def __lt__(self, other) -> bool: other = self.__param_normalize(other) if other is NotImplemented: return NotImplemented return self.version < other.version def is_pre_release(self) -> bool: return not self.pre_release def __repr__(self): return class_utils.represent(self)