Viewing file: version.py (11.91 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
import re from typing import List, Optional, Union
from .empty_constraint import EmptyConstraint from .exceptions import ParseVersionError from .patterns import COMPLETE_VERSION from .version_constraint import VersionConstraint from .version_range import VersionRange from .version_union import VersionUnion
class Version(VersionRange): """ A parsed semantic version number. """
def __init__( self, major, # type: int minor=None, # type: Optional[int] patch=None, # type: Optional[int] rest=None, # type: Optional[int] pre=None, # type: Optional[str] build=None, # type: Optional[str] text=None, # type: Optional[str] precision=None, # type: Optional[int] ): # type: (...) -> None self._major = int(major) self._precision = None if precision is None: self._precision = 1
if minor is None: minor = 0 else: if self._precision is not None: self._precision += 1
self._minor = int(minor)
if patch is None: patch = 0 else: if self._precision is not None: self._precision += 1
if rest is None: rest = 0 else: if self._precision is not None: self._precision += 1
if precision is not None: self._precision = precision
self._patch = int(patch) self._rest = int(rest)
if text is None: parts = [str(major)] if self._precision >= 2 or minor != 0: parts.append(str(minor))
if self._precision >= 3 or patch != 0: parts.append(str(patch))
if self._precision >= 4 or rest != 0: parts.append(str(rest))
text = ".".join(parts) if pre: text += "-{}".format(pre)
if build: text += "+{}".format(build)
self._text = text
pre = self._normalize_prerelease(pre)
self._prerelease = [] if pre is not None: self._prerelease = self._split_parts(pre)
build = self._normalize_build(build)
self._build = [] if build is not None: if build.startswith(("-", "+")): build = build[1:]
self._build = self._split_parts(build)
@property def major(self): # type: () -> int return self._major
@property def minor(self): # type: () -> int return self._minor
@property def patch(self): # type: () -> int return self._patch
@property def rest(self): # type: () -> int return self._rest
@property def prerelease(self): # type: () -> List[str] return self._prerelease
@property def build(self): # type: () -> List[str] return self._build
@property def text(self): return self._text
@property def precision(self): # type: () -> int return self._precision
@property def stable(self): if not self.is_prerelease(): return self
return self.next_patch
@property def next_major(self): # type: () -> Version if self.is_prerelease() and self.minor == 0 and self.patch == 0: return Version(self.major, self.minor, self.patch)
return self._increment_major()
@property def next_minor(self): # type: () -> Version if self.is_prerelease() and self.patch == 0: return Version(self.major, self.minor, self.patch)
return self._increment_minor()
@property def next_patch(self): # type: () -> Version if self.is_prerelease(): return Version(self.major, self.minor, self.patch)
return self._increment_patch()
@property def next_breaking(self): # type: () -> Version if self.major == 0: if self.minor != 0: return self._increment_minor()
if self._precision == 1: return self._increment_major() elif self._precision == 2: return self._increment_minor()
return self._increment_patch()
return self._increment_major()
@property def first_prerelease(self): # type: () -> Version return Version.parse("{}.{}.{}-alpha.0".format(self.major, self.minor, self.patch))
@property def min(self): return self
@property def max(self): return self
@property def full_max(self): return self
@property def include_min(self): return True
@property def include_max(self): return True
@classmethod def parse(cls, text): # type: (str) -> Version try: match = COMPLETE_VERSION.match(text) except TypeError: match = None
if match is None: raise ParseVersionError('Unable to parse "{}".'.format(text))
text = text.rstrip(".")
major = int(match.group(1)) minor = int(match.group(2)) if match.group(2) else None patch = int(match.group(3)) if match.group(3) else None rest = int(match.group(4)) if match.group(4) else None
pre = match.group(5) build = match.group(6)
if build: build = build.lstrip("+")
return Version(major, minor, patch, rest, pre, build, text)
def is_any(self): return False
def is_empty(self): return False
def is_prerelease(self): # type: () -> bool return len(self._prerelease) > 0
def allows(self, version): # type: (Version) -> bool return self == version
def allows_all(self, other): # type: (VersionConstraint) -> bool return other.is_empty() or other == self
def allows_any(self, other): # type: (VersionConstraint) -> bool return other.allows(self)
def intersect(self, other): # type: (VersionConstraint) -> VersionConstraint if other.allows(self): return self
return EmptyConstraint()
def union(self, other): # type: (VersionConstraint) -> VersionConstraint from .version_range import VersionRange
if other.allows(self): return other
if isinstance(other, VersionRange): if other.min == self: return VersionRange( other.min, other.max, include_min=True, include_max=other.include_max, )
if other.max == self: return VersionRange( other.min, other.max, include_min=other.include_min, include_max=True, )
return VersionUnion.of(self, other)
def difference(self, other): # type: (VersionConstraint) -> VersionConstraint if other.allows(self): return EmptyConstraint()
return self
def equals_without_prerelease(self, other): # type: (Version) -> bool return self.major == other.major and self.minor == other.minor and self.patch == other.patch
def _increment_major(self): # type: () -> Version return Version(self.major + 1, 0, 0, precision=self._precision)
def _increment_minor(self): # type: () -> Version return Version(self.major, self.minor + 1, 0, precision=self._precision)
def _increment_patch(self): # type: () -> Version return Version(self.major, self.minor, self.patch + 1, precision=self._precision)
def _normalize_prerelease(self, pre): # type: (str) -> str if not pre: return
m = re.match(r"(?i)^(a|alpha|b|beta|c|pre|rc|dev)[-.]?(\d+)?$", pre) if not m: return
modifier = m.group(1) number = m.group(2)
if number is None: number = 0
if modifier == "a": modifier = "alpha" elif modifier == "b": modifier = "beta" elif modifier in {"c", "pre"}: modifier = "rc" elif modifier == "dev": modifier = "alpha"
return "{}.{}".format(modifier, number)
def _normalize_build(self, build): # type: (str) -> str if not build: return
if build.startswith("post"): build = build.lstrip("post")
if not build: return
return build
def _split_parts(self, text): # type: (str) -> List[Union[str, int]] parts = text.split(".")
for i, part in enumerate(parts): try: parts[i] = int(part) except (TypeError, ValueError): continue
return parts
def __lt__(self, other): return self._cmp(other) < 0
def __le__(self, other): return self._cmp(other) <= 0
def __gt__(self, other): return self._cmp(other) > 0
def __ge__(self, other): return self._cmp(other) >= 0
def _cmp(self, other): if not isinstance(other, VersionConstraint): return NotImplemented
if not isinstance(other, Version): return -other._cmp(self)
if self.major != other.major: return self._cmp_parts(self.major, other.major)
if self.minor != other.minor: return self._cmp_parts(self.minor, other.minor)
if self.patch != other.patch: return self._cmp_parts(self.patch, other.patch)
if self.rest != other.rest: return self._cmp_parts(self.rest, other.rest)
# Pre-releases always come before no pre-release string. if not self.is_prerelease() and other.is_prerelease(): return 1
if not other.is_prerelease() and self.is_prerelease(): return -1
comparison = self._cmp_lists(self.prerelease, other.prerelease) if comparison != 0: return comparison
# Builds always come after no build string. if not self.build and other.build: return -1
if not other.build and self.build: return 1
return self._cmp_lists(self.build, other.build)
def _cmp_parts(self, a, b): if a < b: return -1 elif a > b: return 1
return 0
def _cmp_lists(self, a, b): # type: (List, List) -> int for i in range(max(len(a), len(b))): a_part = None if i < len(a): a_part = a[i]
b_part = None if i < len(b): b_part = b[i]
if a_part == b_part: continue
# Missing parts come after present ones. if a_part is None: return -1
if b_part is None: return 1
if isinstance(a_part, int): if isinstance(b_part, int): return self._cmp_parts(a_part, b_part)
return -1 else: if isinstance(b_part, int): return 1
return self._cmp_parts(a_part, b_part)
return 0
def __eq__(self, other): # type: (Version) -> bool if not isinstance(other, Version): return NotImplemented
return ( self._major == other.major and self._minor == other.minor and self._patch == other.patch and self._rest == other.rest and self._prerelease == other.prerelease and self._build == other.build )
def __ne__(self, other): return not self == other
def __str__(self): return self._text
def __repr__(self): return "<Version {}>".format(str(self))
def __hash__(self): return hash( ( self.major, self.minor, self.patch, ".".join(str(p) for p in self.prerelease), ".".join(str(p) for p in self.build), ) )
|