v0.166.0
 1import re
 2
 3
 4def parse_version(version_str: str) -> tuple[int, ...]:
 5    """Parse a version string into a tuple of integers for comparison.
 6
 7    Lenient on purpose — callers compare versions people typed into config
 8    files, so a string we can't fully parse should sort rather than raise: a
 9    leading `v` is dropped, and each dot-separated segment contributes its
10    leading integer (`0-rc` → `0`), or `0` if it has none.
11
12    This only understands release versions. A pre-release like `1.75.0-rc.1`
13    parses to `(1, 75, 0, 1)` and so sorts *after* `1.75.0` — fine for the
14    release-to-release comparisons here, wrong if you need PEP 440 ordering.
15    """
16    clean_version = version_str.lstrip("v")
17    parts = []
18    for part in clean_version.split("."):
19        numeric_part = re.match(r"\d+", part)
20        parts.append(int(numeric_part.group()) if numeric_part else 0)
21    return tuple(parts)
22
23
24def compare_versions(v1: str, v2: str) -> int:
25    """Compare two version strings: -1 if v1 < v2, 0 if equal, 1 if v1 > v2.
26
27    The shorter side is zero-padded, so `1.75` compares equal to `1.75.0`
28    rather than older than it.
29    """
30    parsed_v1 = parse_version(v1)
31    parsed_v2 = parse_version(v2)
32
33    max_len = max(len(parsed_v1), len(parsed_v2))
34    parsed_v1 += (0,) * (max_len - len(parsed_v1))
35    parsed_v2 += (0,) * (max_len - len(parsed_v2))
36
37    if parsed_v1 < parsed_v2:
38        return -1
39    elif parsed_v1 > parsed_v2:
40        return 1
41    else:
42        return 0