Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_pep440.py487 linesDownload Raw Back to _utils
1"""Utility to compare pep440 compatible version strings.
2
3The LooseVersion and StrictVersion classes that distutils provides don't
4work; they don't recognize anything like alpha/beta/rc/dev versions.
5"""
6
7# Copyright (c) Donald Stufft and individual contributors.
8# All rights reserved.
9
10# Redistribution and use in source and binary forms, with or without
11# modification, are permitted provided that the following conditions are met:
12
13#     1. Redistributions of source code must retain the above copyright notice,
14#        this list of conditions and the following disclaimer.
15
16#     2. Redistributions in binary form must reproduce the above copyright
17#        notice, this list of conditions and the following disclaimer in the
18#        documentation and/or other materials provided with the distribution.
19
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30# POSSIBILITY OF SUCH DAMAGE.
31
32import collections
33import itertools
34import re
35
36__all__ = [
37    "parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN",
38]
39
40
41# BEGIN packaging/_structures.py
42
43
44class Infinity:
45    def __repr__(self):
46        return "Infinity"
47
48    def __hash__(self):
49        return hash(repr(self))
50
51    def __lt__(self, other):
52        return False
53
54    def __le__(self, other):
55        return False
56
57    def __eq__(self, other):
58        return isinstance(other, self.__class__)
59
60    def __ne__(self, other):
61        return not isinstance(other, self.__class__)
62
63    def __gt__(self, other):
64        return True
65
66    def __ge__(self, other):
67        return True
68
69    def __neg__(self):
70        return NegativeInfinity
71
72
73Infinity = Infinity()
74
75
76class NegativeInfinity:
77    def __repr__(self):
78        return "-Infinity"
79
80    def __hash__(self):
81        return hash(repr(self))
82
83    def __lt__(self, other):
84        return True
85
86    def __le__(self, other):
87        return True
88
89    def __eq__(self, other):
90        return isinstance(other, self.__class__)
91
92    def __ne__(self, other):
93        return not isinstance(other, self.__class__)
94
95    def __gt__(self, other):
96        return False
97
98    def __ge__(self, other):
99        return False
100
101    def __neg__(self):
102        return Infinity
103
104
105# BEGIN packaging/version.py
106
107
108NegativeInfinity = NegativeInfinity()
109
110_Version = collections.namedtuple(
111    "_Version",
112    ["epoch", "release", "dev", "pre", "post", "local"],
113)
114
115
116def parse(version):
117    """
118    Parse the given version string and return either a :class:`Version` object
119    or a :class:`LegacyVersion` object depending on if the given version is
120    a valid PEP 440 version or a legacy version.
121    """
122    try:
123        return Version(version)
124    except InvalidVersion:
125        return LegacyVersion(version)
126
127
128class InvalidVersion(ValueError):
129    """
130    An invalid version was found, users should refer to PEP 440.
131    """
132
133
134class _BaseVersion:
135
136    def __hash__(self):
137        return hash(self._key)
138
139    def __lt__(self, other):
140        return self._compare(other, lambda s, o: s < o)
141
142    def __le__(self, other):
143        return self._compare(other, lambda s, o: s <= o)
144
145    def __eq__(self, other):
146        return self._compare(other, lambda s, o: s == o)
147
148    def __ge__(self, other):
149        return self._compare(other, lambda s, o: s >= o)
150
151    def __gt__(self, other):
152        return self._compare(other, lambda s, o: s > o)
153
154    def __ne__(self, other):
155        return self._compare(other, lambda s, o: s != o)
156
157    def _compare(self, other, method):
158        if not isinstance(other, _BaseVersion):
159            return NotImplemented
160
161        return method(self._key, other._key)
162
163
164class LegacyVersion(_BaseVersion):
165
166    def __init__(self, version):
167        self._version = str(version)
168        self._key = _legacy_cmpkey(self._version)
169
170    def __str__(self):
171        return self._version
172
173    def __repr__(self):
174        return f"<LegacyVersion({str(self)!r})>"
175
176    @property
177    def public(self):
178        return self._version
179
180    @property
181    def base_version(self):
182        return self._version
183
184    @property
185    def local(self):
186        return None
187
188    @property
189    def is_prerelease(self):
190        return False
191
192    @property
193    def is_postrelease(self):
194        return False
195
196
197_legacy_version_component_re = re.compile(
198    r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
199)
200
201_legacy_version_replacement_map = {
202    "pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@",
203}
204
205
206def _parse_version_parts(s):
207    for part in _legacy_version_component_re.split(s):
208        part = _legacy_version_replacement_map.get(part, part)
209
210        if not part or part == ".":
211            continue
212
213        if part[:1] in "0123456789":
214            # pad for numeric comparison
215            yield part.zfill(8)
216        else:
217            yield "*" + part
218
219    # ensure that alpha/beta/candidate are before final
220    yield "*final"
221
222
223def _legacy_cmpkey(version):
224    # We hardcode an epoch of -1 here. A PEP 440 version can only have an epoch
225    # greater than or equal to 0. This will effectively put the LegacyVersion,
226    # which uses the defacto standard originally implemented by setuptools,
227    # as before all PEP 440 versions.
228    epoch = -1
229
230    # This scheme is taken from pkg_resources.parse_version setuptools prior to
231    # its adoption of the packaging library.
232    parts = []
233    for part in _parse_version_parts(version.lower()):
234        if part.startswith("*"):
235            # remove "-" before a prerelease tag
236            if part < "*final":
237                while parts and parts[-1] == "*final-":
238                    parts.pop()
239
240            # remove trailing zeros from each series of numeric parts
241            while parts and parts[-1] == "00000000":
242                parts.pop()
243
244        parts.append(part)
245    parts = tuple(parts)
246
247    return epoch, parts
248
249
250# Deliberately not anchored to the start and end of the string, to make it
251# easier for 3rd party code to reuse
252VERSION_PATTERN = r"""
253    v?
254    (?:
255        (?:(?P<epoch>[0-9]+)!)?                           # epoch
256        (?P<release>[0-9]+(?:\.[0-9]+)*)                  # release segment
257        (?P<pre>                                          # pre-release
258            [-_\.]?
259            (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
260            [-_\.]?
261            (?P<pre_n>[0-9]+)?
262        )?
263        (?P<post>                                         # post release
264            (?:-(?P<post_n1>[0-9]+))
265            |
266            (?:
267                [-_\.]?
268                (?P<post_l>post|rev|r)
269                [-_\.]?
270                (?P<post_n2>[0-9]+)?
271            )
272        )?
273        (?P<dev>                                          # dev release
274            [-_\.]?
275            (?P<dev_l>dev)
276            [-_\.]?
277            (?P<dev_n>[0-9]+)?
278        )?
279    )
280    (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
281"""
282
283
284class Version(_BaseVersion):
285
286    _regex = re.compile(
287        r"^\s*" + VERSION_PATTERN + r"\s*$",
288        re.VERBOSE | re.IGNORECASE,
289    )
290
291    def __init__(self, version):
292        # Validate the version and parse it into pieces
293        match = self._regex.search(version)
294        if not match:
295            raise InvalidVersion(f"Invalid version: '{version}'")
296
297        # Store the parsed out pieces of the version
298        self._version = _Version(
299            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
300            release=tuple(int(i) for i in match.group("release").split(".")),
301            pre=_parse_letter_version(
302                match.group("pre_l"),
303                match.group("pre_n"),
304            ),
305            post=_parse_letter_version(
306                match.group("post_l"),
307                match.group("post_n1") or match.group("post_n2"),
308            ),
309            dev=_parse_letter_version(
310                match.group("dev_l"),
311                match.group("dev_n"),
312            ),
313            local=_parse_local_version(match.group("local")),
314        )
315
316        # Generate a key which will be used for sorting
317        self._key = _cmpkey(
318            self._version.epoch,
319            self._version.release,
320            self._version.pre,
321            self._version.post,
322            self._version.dev,
323            self._version.local,
324        )
325
326    def __repr__(self):
327        return f"<Version({str(self)!r})>"
328
329    def __str__(self):
330        parts = []
331
332        # Epoch
333        if self._version.epoch != 0:
334            parts.append(f"{self._version.epoch}!")
335
336        # Release segment
337        parts.append(".".join(str(x) for x in self._version.release))
338
339        # Pre-release
340        if self._version.pre is not None:
341            parts.append("".join(str(x) for x in self._version.pre))
342
343        # Post-release
344        if self._version.post is not None:
345            parts.append(f".post{self._version.post[1]}")
346
347        # Development release
348        if self._version.dev is not None:
349            parts.append(f".dev{self._version.dev[1]}")
350
351        # Local version segment
352        if self._version.local is not None:
353            parts.append(
354                f"+{'.'.join(str(x) for x in self._version.local)}"
355            )
356
357        return "".join(parts)
358
359    @property
360    def public(self):
361        return str(self).split("+", 1)[0]
362
363    @property
364    def base_version(self):
365        parts = []
366
367        # Epoch
368        if self._version.epoch != 0:
369            parts.append(f"{self._version.epoch}!")
370
371        # Release segment
372        parts.append(".".join(str(x) for x in self._version.release))
373
374        return "".join(parts)
375
376    @property
377    def local(self):
378        version_string = str(self)
379        if "+" in version_string:
380            return version_string.split("+", 1)[1]
381
382    @property
383    def is_prerelease(self):
384        return bool(self._version.dev or self._version.pre)
385
386    @property
387    def is_postrelease(self):
388        return bool(self._version.post)
389
390
391def _parse_letter_version(letter, number):
392    if letter:
393        # We assume there is an implicit 0 in a pre-release if there is
394        # no numeral associated with it.
395        if number is None:
396            number = 0
397
398        # We normalize any letters to their lower-case form
399        letter = letter.lower()
400
401        # We consider some words to be alternate spellings of other words and
402        # in those cases we want to normalize the spellings to our preferred
403        # spelling.
404        if letter == "alpha":
405            letter = "a"
406        elif letter == "beta":
407            letter = "b"
408        elif letter in ["c", "pre", "preview"]:
409            letter = "rc"
410        elif letter in ["rev", "r"]:
411            letter = "post"
412
413        return letter, int(number)
414    if not letter and number:
415        # We assume that if we are given a number but not given a letter,
416        # then this is using the implicit post release syntax (e.g., 1.0-1)
417        letter = "post"
418
419        return letter, int(number)
420
421
422_local_version_seperators = re.compile(r"[\._-]")
423
424
425def _parse_local_version(local):
426    """
427    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
428    """
429    if local is not None:
430        return tuple(
431            part.lower() if not part.isdigit() else int(part)
432            for part in _local_version_seperators.split(local)
433        )
434
435
436def _cmpkey(epoch, release, pre, post, dev, local):
437    # When we compare a release version, we want to compare it with all of the
438    # trailing zeros removed. So we'll use a reverse the list, drop all the now
439    # leading zeros until we come to something non-zero, then take the rest,
440    # re-reverse it back into the correct order, and make it a tuple and use
441    # that for our sorting key.
442    release = tuple(
443        reversed(list(
444            itertools.dropwhile(
445                lambda x: x == 0,
446                reversed(release),
447            )
448        ))
449    )
450
451    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
452    # We'll do this by abusing the pre-segment, but we _only_ want to do this
453    # if there is no pre- or a post-segment. If we have one of those, then
454    # the normal sorting rules will handle this case correctly.
455    if pre is None and post is None and dev is not None:
456        pre = -Infinity
457    # Versions without a pre-release (except as noted above) should sort after
458    # those with one.
459    elif pre is None:
460        pre = Infinity
461
462    # Versions without a post-segment should sort before those with one.
463    if post is None:
464        post = -Infinity
465
466    # Versions without a development segment should sort after those with one.
467    if dev is None:
468        dev = Infinity
469
470    if local is None:
471        # Versions without a local segment should sort before those with one.
472        local = -Infinity
473    else:
474        # Versions with a local segment need that segment parsed to implement
475        # the sorting rules in PEP440.
476        # - Alphanumeric segments sort before numeric segments
477        # - Alphanumeric segments sort lexicographically
478        # - Numeric segments sort numerically
479        # - Shorter versions sort before longer versions when the prefixes
480        #   match exactly
481        local = tuple(
482            (i, "") if isinstance(i, int) else (-Infinity, i)
483            for i in local
484        )
485
486    return epoch, release, pre, post, dev, local
487 
codekingpro/portable-devtools · Team Ai