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