|
| 1 | +import re |
| 2 | + |
| 3 | + |
| 4 | +class VersionComparator: |
| 5 | + @staticmethod |
| 6 | + def compare_version(v1: str, v2: str) -> int: |
| 7 | + """根据 Semver 语义版本规范来比较版本号的大小。支持不仅局限于 3 个数字的版本号,并处理预发布标签。 |
| 8 | +
|
| 9 | + 参考: https://semver.org/lang/zh-CN/ |
| 10 | +
|
| 11 | + 返回 1 表示 v1 > v2,返回 -1 表示 v1 < v2,返回 0 表示 v1 = v2。 |
| 12 | + """ |
| 13 | + v1 = v1.lower().replace("v", "") |
| 14 | + v2 = v2.lower().replace("v", "") |
| 15 | + |
| 16 | + def split_version(version): |
| 17 | + match = re.match( |
| 18 | + r"^([0-9]+(?:\.[0-9]+)*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+(.+))?$", |
| 19 | + version, |
| 20 | + ) |
| 21 | + if not match: |
| 22 | + return [], None |
| 23 | + major_minor_patch = match.group(1).split(".") |
| 24 | + prerelease = match.group(2) |
| 25 | + # buildmetadata = match.group(3) # 构建元数据在比较时忽略 |
| 26 | + parts = [int(x) for x in major_minor_patch] |
| 27 | + prerelease = VersionComparator._split_prerelease(prerelease) |
| 28 | + return parts, prerelease |
| 29 | + |
| 30 | + v1_parts, v1_prerelease = split_version(v1) |
| 31 | + v2_parts, v2_prerelease = split_version(v2) |
| 32 | + |
| 33 | + # 比较数字部分 |
| 34 | + length = max(len(v1_parts), len(v2_parts)) |
| 35 | + v1_parts.extend([0] * (length - len(v1_parts))) |
| 36 | + v2_parts.extend([0] * (length - len(v2_parts))) |
| 37 | + |
| 38 | + for i in range(length): |
| 39 | + if v1_parts[i] > v2_parts[i]: |
| 40 | + return 1 |
| 41 | + elif v1_parts[i] < v2_parts[i]: |
| 42 | + return -1 |
| 43 | + |
| 44 | + # 比较预发布标签 |
| 45 | + if v1_prerelease is None and v2_prerelease is not None: |
| 46 | + return 1 # 没有预发布标签的版本高于有预发布标签的版本 |
| 47 | + elif v1_prerelease is not None and v2_prerelease is None: |
| 48 | + return -1 # 有预发布标签的版本低于没有预发布标签的版本 |
| 49 | + elif v1_prerelease is not None and v2_prerelease is not None: |
| 50 | + len_pre = max(len(v1_prerelease), len(v2_prerelease)) |
| 51 | + for i in range(len_pre): |
| 52 | + p1 = v1_prerelease[i] if i < len(v1_prerelease) else None |
| 53 | + p2 = v2_prerelease[i] if i < len(v2_prerelease) else None |
| 54 | + |
| 55 | + if p1 is None and p2 is not None: |
| 56 | + return -1 |
| 57 | + elif p1 is not None and p2 is None: |
| 58 | + return 1 |
| 59 | + elif isinstance(p1, int) and isinstance(p2, str): |
| 60 | + return -1 |
| 61 | + elif isinstance(p1, str) and isinstance(p2, int): |
| 62 | + return 1 |
| 63 | + elif isinstance(p1, int) and isinstance(p2, int): |
| 64 | + if p1 > p2: |
| 65 | + return 1 |
| 66 | + elif p1 < p2: |
| 67 | + return -1 |
| 68 | + elif isinstance(p1, str) and isinstance(p2, str): |
| 69 | + if p1 > p2: |
| 70 | + return 1 |
| 71 | + elif p1 < p2: |
| 72 | + return -1 |
| 73 | + return 0 # 预发布标签完全相同 |
| 74 | + |
| 75 | + return 0 # 数字部分和预发布标签都相同 |
| 76 | + |
| 77 | + @staticmethod |
| 78 | + def _split_prerelease(prerelease): |
| 79 | + if not prerelease: |
| 80 | + return None |
| 81 | + parts = prerelease.split(".") |
| 82 | + result = [] |
| 83 | + for part in parts: |
| 84 | + if part.isdigit(): |
| 85 | + result.append(int(part)) |
| 86 | + else: |
| 87 | + result.append(part) |
| 88 | + return result |
0 commit comments