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