Skip to content

Commit 35f03c2

Browse files
committed
Subclassed version of Version that supports -platform
1 parent d104717 commit 35f03c2

File tree

1 file changed

+110
-75
lines changed

1 file changed

+110
-75
lines changed

libtmux/_compat.py

+110-75
Original file line numberDiff line numberDiff line change
@@ -28,104 +28,139 @@ def str_from_console(s):
2828

2929

3030
try:
31+
import collections
32+
import functools
3133
import re
3234
from typing import Iterator, List, Tuple
3335

34-
from packaging.version import Version, _BaseVersion
36+
from packaging import version as V
37+
from packaging.version import VERSION_PATTERN, Version
3538

3639
###
3740
### Legacy support for LooseVersion / LegacyVersion, e.g. 2.4-openbsd
3841
### https://github.com/pypa/packaging/blob/21.3/packaging/version.py#L106-L115
3942
### License: BSD, Accessed: Jan 14th, 2022
4043
###
4144

42-
LegacyCmpKey = Tuple[int, Tuple[str, ...]]
43-
44-
_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE)
45-
_legacy_version_replacement_map = {
46-
"pre": "c",
47-
"preview": "c",
48-
"-": "final-",
49-
"rc": "c",
50-
"dev": "@",
51-
}
45+
@functools.total_ordering
46+
class _VersionCmpMixin:
47+
# Credit: @layday
48+
# Link: https://github.com/pypa/packaging/issues/465#issuecomment-1013715662
49+
def __eq__(self, other: object) -> bool:
50+
if isinstance(other, str):
51+
other = self.__class__(other)
52+
return super().__eq__(other)
5253

53-
def _parse_version_parts(s: str) -> Iterator[str]:
54-
for part in _legacy_version_component_re.split(s):
55-
part = _legacy_version_replacement_map.get(part, part)
54+
def __lt__(self, other: object) -> bool:
55+
if isinstance(other, str):
56+
other = self.__class__(other)
57+
return super().__lt__(other)
5658

57-
if not part or part == ".":
58-
continue
59+
_Version = collections.namedtuple(
60+
"_Version", ["epoch", "release", "dev", "pre", "post", "local", "platform"]
61+
)
62+
63+
def _cmpkey(
64+
epoch, # type: int
65+
release, # type: Tuple[int, ...]
66+
pre, # type: Optional[Tuple[str, int]]
67+
post, # type: Optional[Tuple[str, int]]
68+
dev, # type: Optional[Tuple[str, int]]
69+
local, # type: Optional[Tuple[SubLocalType]]
70+
platform, # type: Optional[Tuple[SubLocalType]]
71+
):
72+
return V._cmpkey(epoch, release, pre, post, dev, local) + (
73+
tuple(
74+
(i, "") if isinstance(i, int) else (V.NegativeInfinity, i)
75+
for i in platform
76+
)
77+
)
78+
79+
class LegacyVersion(Version):
80+
_regex = re.compile
81+
_regex = re.compile(
82+
r"^\s*"
83+
+ VERSION_PATTERN
84+
+ r"(?:(?P<platform>(?:[-_\.][a-z0-9]+)*))? # platform version"
85+
+ r"\s*$",
86+
re.VERBOSE | re.IGNORECASE,
87+
)
88+
89+
def __init__(self, version):
90+
# type: (str) -> None
91+
92+
# Validate the version and parse it into pieces
93+
match = self._regex.search(version)
94+
if not match:
95+
raise V.InvalidVersion("Invalid version: '{0}'".format(version))
96+
97+
# Store the parsed out pieces of the version
98+
self._version = _Version(
99+
epoch=int(match.group("epoch")) if match.group("epoch") else 0,
100+
release=tuple(int(i) for i in match.group("release").split(".")),
101+
pre=V._parse_letter_version(match.group("pre_l"), match.group("pre_n")),
102+
post=V._parse_letter_version(
103+
match.group("post_l"),
104+
match.group("post_n1") or match.group("post_n2"),
105+
),
106+
dev=V._parse_letter_version(match.group("dev_l"), match.group("dev_n")),
107+
local=V._parse_local_version(match.group("local")),
108+
platform=str(match.group('platform'))
109+
if match.group("platform")
110+
else "",
111+
)
112+
113+
# Generate a key which will be used for sorting
114+
self._key = _cmpkey(
115+
self._version.epoch,
116+
self._version.release,
117+
self._version.pre,
118+
self._version.post,
119+
self._version.dev,
120+
self._version.local,
121+
self._version.platform,
122+
)
59123

60-
if part[:1] in "0123456789":
61-
# pad for numeric comparison
62-
yield part.zfill(8)
124+
@property
125+
def platform(self):
126+
# type: () -> Optional[str]
127+
if self._version.platform:
128+
return ".".join(str(x) for x in self._version.platform)
63129
else:
64-
yield "*" + part
130+
return None
65131

66-
# ensure that alpha/beta/candidate are before final
67-
yield "*final"
132+
def __str__(self):
133+
# type: () -> str
134+
parts = []
68135

69-
def _legacy_cmpkey(version: str) -> LegacyCmpKey:
70-
# We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch
71-
# greater than or equal to 0. This will effectively put the LegacyVersion,
72-
# which uses the defacto standard originally implemented by setuptools,
73-
# as before all PEP 440 versions.
74-
epoch = -1
136+
# Epoch
137+
if self.epoch != 0:
138+
parts.append("{0}!".format(self.epoch))
75139

76-
# This scheme is taken from pkg_resources.parse_version setuptools prior to
77-
# it's adoption of the packaging library.
78-
parts: List[str] = []
79-
for part in _parse_version_parts(version.lower()):
80-
if part.startswith("*"):
81-
# remove "-" before a prerelease tag
82-
if part < "*final":
83-
while parts and parts[-1] == "*final-":
84-
parts.pop()
140+
# Release segment
141+
parts.append(".".join(str(x) for x in self.release))
85142

86-
# remove trailing zeros from each series of numeric parts
87-
while parts and parts[-1] == "00000000":
88-
parts.pop()
143+
# Pre-release
144+
if self.pre is not None:
145+
parts.append("".join(str(x) for x in self.pre))
89146

90-
parts.append(part)
147+
# Post-release
148+
if self.post is not None:
149+
parts.append(".post{0}".format(self.post))
91150

92-
return epoch, tuple(parts)
151+
# Development release
152+
if self.dev is not None:
153+
parts.append(".dev{0}".format(self.dev))
93154

94-
class LegacyVersion(_BaseVersion):
95-
def __init__(self, version: str) -> None:
96-
self._version = str(version)
97-
self._key = _legacy_cmpkey(self._version)
155+
# Local version segment
156+
if self.local is not None:
157+
parts.append("+{0}".format(self.local))
98158

99-
def __str__(self) -> str:
100-
return self._version
159+
# Platform version segment
160+
if self.platform is not None:
161+
parts.append("_{0}".format(self.platform))
101162

102-
def __lt__(self, other):
103-
if isinstance(other, str):
104-
other = LegacyVersion(other)
105-
return super().__lt__(other)
106-
107-
def __eq__(self, other) -> bool:
108-
if isinstance(other, str):
109-
other = LegacyVersion(other)
110-
if not isinstance(other, LegacyVersion):
111-
return NotImplemented
112-
113-
return self._key == other._key
114-
115-
def __repr__(self) -> str:
116-
return "<LegacyVersion({0})>".format(repr(str(self)))
117-
118-
@property
119-
def public(self) -> str:
120-
return self._version
121-
122-
@property
123-
def base_version(self) -> str:
124-
return self._version
125-
126-
@property
127-
def epoch(self) -> int:
128-
return -1
163+
return "".join(parts)
129164

130165
LooseVersion = LegacyVersion
131166
except ImportError:

0 commit comments

Comments
 (0)