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