Skip to content

Commit 56b85e8

Browse files
committed
Custom Version implementation
1 parent afb218e commit 56b85e8

File tree

2 files changed

+146
-0
lines changed

2 files changed

+146
-0
lines changed

src/libtmux/_compat.py

Lines changed: 103 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,105 @@ 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+
try:
38+
import re
39+
from typing import Iterator, List, Tuple
40+
41+
from packaging.version import Version
42+
43+
###
44+
### Legacy support for LooseVersion / LegacyVersion, e.g. 2.4-openbsd
45+
### https://github.com/pypa/packaging/blob/21.3/packaging/version.py#L106-L115
46+
### License: BSD, Accessed: Jan 14th, 2022
47+
###
48+
49+
LegacyCmpKey = Tuple[int, Tuple[str, ...]]
50+
51+
_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE)
52+
_legacy_version_replacement_map = {
53+
"pre": "c",
54+
"preview": "c",
55+
"-": "final-",
56+
"rc": "c",
57+
"dev": "@",
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+
def _legacy_cmpkey(version: str) -> LegacyCmpKey:
77+
# We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch
78+
# greater than or equal to 0. This will effectively put the LegacyVersion,
79+
# which uses the defacto standard originally implemented by setuptools,
80+
# as before all PEP 440 versions.
81+
epoch = -1
82+
83+
# This scheme is taken from pkg_resources.parse_version setuptools prior to
84+
# it's adoption of the packaging library.
85+
parts: List[str] = []
86+
for part in _parse_version_parts(version.lower()):
87+
if part.startswith("*"):
88+
# remove "-" before a prerelease tag
89+
if part < "*final":
90+
while parts and parts[-1] == "*final-":
91+
parts.pop()
92+
93+
# remove trailing zeros from each series of numeric parts
94+
while parts and parts[-1] == "00000000":
95+
parts.pop()
96+
97+
parts.append(part)
98+
99+
return epoch, tuple(parts)
100+
101+
@functools.total_ordering
102+
class LegacyVersion:
103+
_key = None # type: Union[CmpKey, LegacyCmpKey]
104+
105+
def __hash__(self) -> int:
106+
return hash(self._key)
107+
108+
def __init__(self, version: str) -> None:
109+
self._version = str(version)
110+
self._key = _legacy_cmpkey(self._version)
111+
112+
def __str__(self) -> str:
113+
return self._version
114+
115+
def __lt__(self, other):
116+
if isinstance(other, str):
117+
other = LegacyVersion(other)
118+
if not isinstance(other, LegacyVersion):
119+
return NotImplemented
120+
121+
return self._key < other._key
122+
123+
def __eq__(self, other) -> bool:
124+
if isinstance(other, str):
125+
other = LegacyVersion(other)
126+
if not isinstance(other, LegacyVersion):
127+
return NotImplemented
128+
129+
return self._key == other._key
130+
131+
def __repr__(self) -> str:
132+
return "<LegacyVersion({0})>".format(repr(str(self)))
133+
134+
LooseVersion = LegacyVersion
135+
except ImportError:
136+
from distutils.version import LooseVersion, Version

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)