| |
@@ -30,22 +30,45 @@
|
| |
|
| |
To use this class, create a Version object from the upstream version number:
|
| |
|
| |
- >>> v = Version('v1.2.3')
|
| |
+ >>> v = Version('v1.2.3')
|
| |
|
| |
Version objects can be compared as though they were numbers:
|
| |
|
| |
- >>> w = Version('v1.2.3b')
|
| |
- >>> x = Version('v1.2.3~2')
|
| |
- >>> v < w
|
| |
- True
|
| |
- >>> v < x
|
| |
- False
|
| |
+ >>> w = Version('v1.2.3b')
|
| |
+ >>> x = Version('v1.2.3~2')
|
| |
+ >>> v < w
|
| |
+ True
|
| |
+ >>> v < x
|
| |
+ False
|
| |
+
|
| |
"""
|
| |
+ from __future__ import annotations
|
| |
|
| |
+ from enum import IntEnum
|
| |
from itertools import zip_longest
|
| |
+ from typing import List, Tuple, Literal, Optional
|
| |
import re
|
| |
|
| |
- def char_class(char):
|
| |
+
|
| |
+ class CharClass(IntEnum):
|
| |
+ """Character classes inside a version string"""
|
| |
+ TILDE = 0
|
| |
+ EMPTY = 1
|
| |
+ LETTER = 2
|
| |
+ REST = 3
|
| |
+
|
| |
+
|
| |
+ def str_to_int(number_or_not: Optional[str],
|
| |
+ fallback: int=0) -> int:
|
| |
+ if number_or_not is None:
|
| |
+ return fallback
|
| |
+ try:
|
| |
+ return int(number_or_not)
|
| |
+ except ValueError:
|
| |
+ return fallback
|
| |
+
|
| |
+
|
| |
+ def char_class(char: Optional[str]) -> CharClass:
|
| |
"""Determine which class this character belongs to:
|
| |
0: tilde
|
| |
1: empty
|
| |
@@ -53,12 +76,13 @@
|
| |
3: anything else
|
| |
"""
|
| |
if char == '~':
|
| |
- return 0
|
| |
- if char == '':
|
| |
- return 1
|
| |
+ return CharClass.TILDE
|
| |
+ if char == '' or char is None:
|
| |
+ return CharClass.EMPTY
|
| |
if char.isalpha():
|
| |
- return 2
|
| |
- return 3
|
| |
+ return CharClass.LETTER
|
| |
+ return CharClass.REST
|
| |
+
|
| |
|
| |
class Version:
|
| |
"""Representation of a package version.
|
| |
@@ -67,9 +91,9 @@
|
| |
|
| |
__slots__ = ['elems']
|
| |
|
| |
- def __init__(self, verstring):
|
| |
+ def __init__(self, verstring: str) -> None:
|
| |
"""Initialize a version object from a version string."""
|
| |
- self.elems = []
|
| |
+ self.elems: List[str] = []
|
| |
while verstring:
|
| |
digits = re.search(r'\d+', verstring)
|
| |
if digits:
|
| |
@@ -81,9 +105,9 @@
|
| |
verstring = verstring[end:]
|
| |
else:
|
| |
self.elems.append(verstring)
|
| |
- verstring = None
|
| |
+ break
|
| |
|
| |
- def compare(self, other):
|
| |
+ def compare(self, other: Version) -> Literal[1, -1, 0]:
|
| |
"""Compare two versions.
|
| |
|
| |
Returns a negative value, zero, or a positive value as the first
|
| |
@@ -91,19 +115,19 @@
|
| |
respectively.
|
| |
"""
|
| |
digit = False
|
| |
- comparison = 0
|
| |
- for el1, el2 in zip_longest(self.elems, other.elems, fillvalue=''):
|
| |
+ comparison: Literal[1, -1, 0] = 0
|
| |
+ for el1, el2 in zip_longest(self.elems, other.elems):
|
| |
if digit:
|
| |
- if int(el1) < int(el2):
|
| |
+ int_el1, int_el2 = str_to_int(el1), str_to_int(el2)
|
| |
+ if int_el1 < int_el2:
|
| |
comparison = -1
|
| |
break
|
| |
- if int(el1) > int(el2):
|
| |
+ if int_el1 > int_el2:
|
| |
comparison = 1
|
| |
break
|
| |
else:
|
| |
- for ch1, ch2 in zip_longest(el1, el2, fillvalue=''):
|
| |
- cl1 = char_class(ch1)
|
| |
- cl2 = char_class(ch2)
|
| |
+ for ch1, ch2 in zip_longest(el1 or '', el2 or ''):
|
| |
+ cl1, cl2 = char_class(ch1), char_class(ch2)
|
| |
if cl1 < cl2:
|
| |
comparison = -1
|
| |
break
|
| |
@@ -116,6 +140,10 @@
|
| |
if ch1 > ch2:
|
| |
comparison = 1
|
| |
break
|
| |
+ # need to break out of the outer loop as well if we found a
|
| |
+ # valid comparison
|
| |
+ if comparison != 0:
|
| |
+ break
|
| |
digit = not digit
|
| |
return comparison
|
| |
|
| |
@@ -141,11 +169,11 @@
|
| |
return ''.join(self.elems)
|
| |
|
| |
@staticmethod
|
| |
- def _to_fedora(elems):
|
| |
+ def _to_fedora(elems: List[str]) -> Tuple[str, str]:
|
| |
"""Convert elems to Fedora version and release numbers."""
|
| |
ver = str(elems.pop(0))
|
| |
while elems and \
|
| |
- (elems[0] == '.' or elems[0] == '_' or elems[0].startswith('~')):
|
| |
+ (elems[0] == '.' or elems[0] == '_' or elems[0].startswith('~')):
|
| |
dot = elems.pop(0)
|
| |
if dot == '_':
|
| |
dot = '.'
|
| |
@@ -156,7 +184,7 @@
|
| |
ver += '.0'
|
| |
return ver, '0.1.'.join(elems) if elems else '1'
|
| |
|
| |
- def to_fedora(self):
|
| |
+ def to_fedora(self) -> Tuple[str, str]:
|
| |
"""Convert this version number to Fedora version and release numbers."""
|
| |
if not self.elems:
|
| |
return '0', '0.1'
|
| |
This pull request adds type hints, unit tests and a small bugfix to the version module