Add comparision between VersionInfo objects

This commit is contained in:
Karol Werner 2017-05-19 23:02:03 +02:00
parent c679d138ab
commit a69b632f65
2 changed files with 159 additions and 63 deletions

161
semver.py

@ -4,7 +4,6 @@ Python helper for Semantic Versioning (http://semver.org/)
import collections
import re
import sys
__version__ = '2.7.6'
@ -59,26 +58,59 @@ def parse(version):
return version_parts
VersionInfo = collections.namedtuple(
'VersionInfo', 'major minor patch prerelease build')
class VersionInfo(collections.namedtuple(
'VersionInfo', 'major minor patch prerelease build')):
"""
:param int major: version when you make incompatible API changes.
:param int minor: version when you add functionality in
a backwards-compatible manner.
:param int patch: version when you make backwards-compatible bug fixes.
:param str prerelease: an optional prerelease string
:param str build: an optional build string
# Only change it for Python > 3 as it is readonly
# for version 2
if sys.version_info >= (3, 5):
VersionInfo.__doc__ = """
:param int major: version when you make incompatible API changes.
:param int minor: version when you add functionality in
a backwards-compatible manner.
:param int patch: version when you make backwards-compatible bug fixes.
:param str prerelease: an optional prerelease string
:param str build: an optional build string
>>> import semver
>>> ver = semver.parse('3.4.5-pre.2+build.4')
>>> ver
{'build': 'build.4', 'major': 3, 'minor': 4, 'patch': 5,
'prerelease': 'pre.2'}
"""
__slots__ = ()
>>> import semver
>>> ver = semver.parse('3.4.5-pre.2+build.4')
>>> ver
{'build': 'build.4', 'major': 3, 'minor': 4, 'patch': 5,
'prerelease': 'pre.2'}
"""
def __eq__(self, other):
if not isinstance(other, (VersionInfo, dict)):
return NotImplemented
return _compare_by_keys(self._asdict(), _to_dict(other)) == 0
def __ne__(self, other):
if not isinstance(other, (VersionInfo, dict)):
return NotImplemented
return _compare_by_keys(self._asdict(), _to_dict(other)) != 0
def __lt__(self, other):
if not isinstance(other, (VersionInfo, dict)):
return NotImplemented
return _compare_by_keys(self._asdict(), _to_dict(other)) < 0
def __le__(self, other):
if not isinstance(other, (VersionInfo, dict)):
return NotImplemented
return _compare_by_keys(self._asdict(), _to_dict(other)) <= 0
def __gt__(self, other):
if not isinstance(other, (VersionInfo, dict)):
return NotImplemented
return _compare_by_keys(self._asdict(), _to_dict(other)) > 0
def __ge__(self, other):
if not isinstance(other, (VersionInfo, dict)):
return NotImplemented
return _compare_by_keys(self._asdict(), _to_dict(other)) >= 0
def _to_dict(obj):
if isinstance(obj, VersionInfo):
return obj._asdict()
return obj
def parse_version_info(version):
@ -96,6 +128,52 @@ def parse_version_info(version):
return version_info
def _nat_cmp(a, b):
def convert(text):
return int(text) if re.match('[0-9]+', text) else text
def split_key(key):
return [convert(c) for c in key.split('.')]
def cmp_prerelease_tag(a, b):
if isinstance(a, int) and isinstance(b, int):
return cmp(a, b)
elif isinstance(a, int):
return -1
elif isinstance(b, int):
return 1
else:
return cmp(a, b)
a, b = a or '', b or ''
a_parts, b_parts = split_key(a), split_key(b)
for sub_a, sub_b in zip(a_parts, b_parts):
cmp_result = cmp_prerelease_tag(sub_a, sub_b)
if cmp_result != 0:
return cmp_result
else:
return cmp(len(a), len(b))
def _compare_by_keys(d1, d2):
for key in ['major', 'minor', 'patch']:
v = cmp(d1.get(key), d2.get(key))
if v:
return v
rc1, rc2 = d1.get('prerelease'), d2.get('prerelease')
rccmp = _nat_cmp(rc1, rc2)
if not rccmp:
return 0
if not rc1:
return 1
elif not rc2:
return -1
return rccmp
def compare(ver1, ver2):
"""Compare two versions
@ -105,53 +183,10 @@ def compare(ver1, ver2):
zero if ver1 == ver2 and strictly positive if ver1 > ver2
:rtype: int
"""
def nat_cmp(a, b):
def convert(text):
return int(text) if re.match('[0-9]+', text) else text
def split_key(key):
return [convert(c) for c in key.split('.')]
def cmp_prerelease_tag(a, b):
if isinstance(a, int) and isinstance(b, int):
return cmp(a, b)
elif isinstance(a, int):
return -1
elif isinstance(b, int):
return 1
else:
return cmp(a, b)
a, b = a or '', b or ''
a_parts, b_parts = split_key(a), split_key(b)
for sub_a, sub_b in zip(a_parts, b_parts):
cmp_result = cmp_prerelease_tag(sub_a, sub_b)
if cmp_result != 0:
return cmp_result
else:
return cmp(len(a), len(b))
def compare_by_keys(d1, d2):
for key in ['major', 'minor', 'patch']:
v = cmp(d1.get(key), d2.get(key))
if v:
return v
rc1, rc2 = d1.get('prerelease'), d2.get('prerelease')
rccmp = nat_cmp(rc1, rc2)
if not rccmp:
return 0
if not rc1:
return 1
elif not rc2:
return -1
return rccmp
v1, v2 = parse(ver1), parse(ver2)
return compare_by_keys(v1, v2)
return _compare_by_keys(v1, v2)
def match(version, match_expr):

@ -11,6 +11,7 @@ from semver import bump_prerelease
from semver import bump_build
from semver import min_ver
from semver import max_ver
from semver import VersionInfo
SEMVERFUNCS = [
@ -270,3 +271,63 @@ def test_should_bump_build():
assert bump_build('3.4.5-rc.1+0009.dev') == '3.4.5-rc.1+0010.dev'
assert bump_build('3.4.5-rc.1') == '3.4.5-rc.1+build.1'
assert bump_build('3.4.5') == '3.4.5+build.1'
def test_should_compare_version_info_objects():
v1 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None)
v2 = VersionInfo(
major=0, minor=10, patch=4, prerelease='beta.1', build=None)
# use `not` to enforce using comparision operators
assert v1 != v2
assert v1 > v2
assert v1 >= v2
assert not(v1 < v2)
assert not(v1 <= v2)
assert not(v1 == v2)
v3 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None)
assert not(v1 != v3)
assert not(v1 > v3)
assert v1 >= v3
assert not(v1 < v3)
assert v1 <= v3
assert v1 == v3
v4 = VersionInfo(major=0, minor=10, patch=5, prerelease=None, build=None)
assert v1 != v4
assert not(v1 > v4)
assert not(v1 >= v4)
assert v1 < v4
assert v1 <= v4
assert not(v1 == v4)
def test_should_compare_version_dictionaries():
v1 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None)
v2 = dict(major=0, minor=10, patch=4, prerelease='beta.1', build=None)
assert v1 != v2
assert v1 > v2
assert v1 >= v2
assert not(v1 < v2)
assert not(v1 <= v2)
assert not(v1 == v2)
v3 = dict(major=0, minor=10, patch=4, prerelease=None, build=None)
assert not(v1 != v3)
assert not(v1 > v3)
assert v1 >= v3
assert not(v1 < v3)
assert v1 <= v3
assert v1 == v3
v4 = dict(major=0, minor=10, patch=5, prerelease=None, build=None)
assert v1 != v4
assert not(v1 > v4)
assert not(v1 >= v4)
assert v1 < v4
assert v1 <= v4
assert not(v1 == v4)