Skip to content

Commit 896056e

Browse files
committed
Custom Version implementation
1 parent afb218e commit 896056e

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

src/libtmux/_compat.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# flake8: NOQA
2+
import functools
23
import sys
34
import types
45
import typing as t
@@ -31,3 +32,106 @@ def str_from_console(s: t.Union[str, bytes]) -> str:
3132
return str(s)
3233
except UnicodeDecodeError:
3334
return str(s, encoding="utf_8") if isinstance(s, bytes) else s
35+
36+
37+
import re
38+
from typing import Iterator, List, Tuple
39+
40+
from packaging.version import Version
41+
42+
###
43+
### Legacy support for LooseVersion / LegacyVersion, e.g. 2.4-openbsd
44+
### https://github.com/pypa/packaging/blob/21.3/packaging/version.py#L106-L115
45+
### License: BSD, Accessed: Jan 14th, 2022
46+
###
47+
48+
LegacyCmpKey = Tuple[int, Tuple[str, ...]]
49+
50+
_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE)
51+
_legacy_version_replacement_map = {
52+
"pre": "c",
53+
"preview": "c",
54+
"-": "final-",
55+
"rc": "c",
56+
"dev": "@",
57+
}
58+
59+
60+
def _parse_version_parts(s: str) -> Iterator[str]:
61+
for part in _legacy_version_component_re.split(s):
62+
part = _legacy_version_replacement_map.get(part, part)
63+
64+
if not part or part == ".":
65+
continue
66+
67+
if part[:1] in "0123456789":
68+
# pad for numeric comparison
69+
yield part.zfill(8)
70+
else:
71+
yield "*" + part
72+
73+
# ensure that alpha/beta/candidate are before final
74+
yield "*final"
75+
76+
77+
def _legacy_cmpkey(version: str) -> LegacyCmpKey:
78+
# We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch
79+
# greater than or equal to 0. This will effectively put the LegacyVersion,
80+
# which uses the defacto standard originally implemented by setuptools,
81+
# as before all PEP 440 versions.
82+
epoch = -1
83+
84+
# This scheme is taken from pkg_resources.parse_version setuptools prior to
85+
# it's adoption of the packaging library.
86+
parts: List[str] = []
87+
for part in _parse_version_parts(version.lower()):
88+
if part.startswith("*"):
89+
# remove "-" before a prerelease tag
90+
if part < "*final":
91+
while parts and parts[-1] == "*final-":
92+
parts.pop()
93+
94+
# remove trailing zeros from each series of numeric parts
95+
while parts and parts[-1] == "00000000":
96+
parts.pop()
97+
98+
parts.append(part)
99+
100+
return epoch, tuple(parts)
101+
102+
103+
@functools.total_ordering
104+
class LegacyVersion:
105+
_key: LegacyCmpKey
106+
107+
def __hash__(self) -> int:
108+
return hash(self._key)
109+
110+
def __init__(self, version: object) -> None:
111+
self._version = str(version)
112+
self._key = _legacy_cmpkey(self._version)
113+
114+
def __str__(self) -> str:
115+
return self._version
116+
117+
def __lt__(self, other: object) -> bool:
118+
if isinstance(other, str):
119+
other = LegacyVersion(other)
120+
if not isinstance(other, LegacyVersion):
121+
return NotImplemented
122+
123+
return self._key < other._key
124+
125+
def __eq__(self, other: object) -> bool:
126+
if isinstance(other, str):
127+
other = LegacyVersion(other)
128+
if not isinstance(other, LegacyVersion):
129+
return NotImplemented
130+
131+
return self._key == other._key
132+
133+
def __repr__(self) -> str:
134+
return "<LegacyVersion({0})>".format(repr(str(self)))
135+
136+
137+
LooseVersion = LegacyVersion

tests/test_version.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import operator
2+
from contextlib import nullcontext as does_not_raise
3+
4+
import pytest
5+
6+
from libtmux._compat import LooseVersion
7+
8+
9+
@pytest.mark.parametrize(
10+
"version",
11+
[
12+
"1",
13+
"1.0",
14+
"1.0.0",
15+
"1.0.0b",
16+
"1.0.0b1",
17+
"1.0.0b-openbsd",
18+
"1.0.0-next",
19+
"1.0.0-next.1",
20+
],
21+
)
22+
def test_version(version):
23+
assert LooseVersion(version)
24+
25+
26+
@pytest.mark.parametrize(
27+
"version,op,versionb,raises",
28+
[
29+
["1", operator.eq, "1", False],
30+
["1", operator.eq, "1.0", False],
31+
["1", operator.eq, "1.0.0", False],
32+
["1", operator.gt, "1.0.0a", False],
33+
["1", operator.gt, "1.0.0b", False],
34+
["1", operator.lt, "1.0.0p1", False],
35+
["1", operator.lt, "1.0.0-openbsd", False],
36+
["1", operator.lt, "1", AssertionError],
37+
["1", operator.lt, "1", AssertionError],
38+
],
39+
)
40+
def test_version_compare(version, op, versionb, raises):
41+
raises_ctx = pytest.raises(raises) if raises else does_not_raise()
42+
with raises_ctx:
43+
assert op(LooseVersion(version), LooseVersion(versionb))

0 commit comments

Comments
 (0)