170 lines
4.5 KiB
Python
170 lines
4.5 KiB
Python
"""
|
|
Python helper for Semantic Versioning (http://semver.org/)
|
|
"""
|
|
import re
|
|
|
|
__version__ = '2.6.0'
|
|
__author__ = 'Konstantine Rybnikov'
|
|
__author_email__ = 'k-bx@k-bx.com'
|
|
|
|
_REGEX = re.compile('^(?P<major>(?:0|[1-9][0-9]*))'
|
|
'\.(?P<minor>(?:0|[1-9][0-9]*))'
|
|
'\.(?P<patch>(?:0|[1-9][0-9]*))'
|
|
'(\-(?P<prerelease>[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?'
|
|
'(\+(?P<build>[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$')
|
|
|
|
_LAST_NUMBER = re.compile(r'(?:[^\d]*(\d+)[^\d]*)+')
|
|
|
|
if not hasattr(__builtins__, 'cmp'):
|
|
def cmp(a, b):
|
|
return (a > b) - (a < b)
|
|
|
|
|
|
def parse(version):
|
|
"""
|
|
Parse version to major, minor, patch, pre-release, build parts.
|
|
"""
|
|
match = _REGEX.match(version)
|
|
if match is None:
|
|
raise ValueError('%s is not valid SemVer string' % version)
|
|
|
|
verinfo = match.groupdict()
|
|
|
|
verinfo['major'] = int(verinfo['major'])
|
|
verinfo['minor'] = int(verinfo['minor'])
|
|
verinfo['patch'] = int(verinfo['patch'])
|
|
|
|
return verinfo
|
|
|
|
|
|
def compare(ver1, ver2):
|
|
def nat_cmp(a, b):
|
|
def convert(text):
|
|
return (2, int(text)) if re.match('[0-9]+', text) else (1, text)
|
|
|
|
def split_key(key):
|
|
return [convert(c) for c in key.split('.')]
|
|
|
|
a, b = a or '', b or ''
|
|
return cmp(split_key(a), split_key(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)
|
|
|
|
|
|
def match(version, match_expr):
|
|
prefix = match_expr[:2]
|
|
if prefix in ('>=', '<=', '==', '!='):
|
|
match_version = match_expr[2:]
|
|
elif prefix and prefix[0] in ('>', '<'):
|
|
prefix = prefix[0]
|
|
match_version = match_expr[1:]
|
|
else:
|
|
raise ValueError("match_expr parameter should be in format <op><ver>, "
|
|
"where <op> is one of "
|
|
"['<', '>', '==', '<=', '>=', '!=']. "
|
|
"You provided: %r" % match_expr)
|
|
|
|
possibilities_dict = {
|
|
'>': (1,),
|
|
'<': (-1,),
|
|
'==': (0,),
|
|
'!=': (-1, 1),
|
|
'>=': (0, 1),
|
|
'<=': (-1, 0)
|
|
}
|
|
|
|
possibilities = possibilities_dict[prefix]
|
|
cmp_res = compare(version, match_version)
|
|
|
|
return cmp_res in possibilities
|
|
|
|
|
|
def max_ver(ver1, ver2):
|
|
cmp_res = compare(ver1, ver2)
|
|
if cmp_res == 0 or cmp_res == 1:
|
|
return ver1
|
|
else:
|
|
return ver2
|
|
|
|
|
|
def min_ver(ver1, ver2):
|
|
cmp_res = compare(ver1, ver2)
|
|
if cmp_res == 0 or cmp_res == -1:
|
|
return ver1
|
|
else:
|
|
return ver2
|
|
|
|
|
|
def format_version(major, minor, patch, prerelease=None, build=None):
|
|
version = "%d.%d.%d" % (major, minor, patch)
|
|
if prerelease is not None:
|
|
version = version + "-%s" % prerelease
|
|
|
|
if build is not None:
|
|
version = version + "+%s" % build
|
|
|
|
return version
|
|
|
|
|
|
def _increment_string(string):
|
|
"""
|
|
Look for the last sequence of number(s) in a string and increment, from:
|
|
http://code.activestate.com/recipes/442460-increment-numbers-in-a-string/#c1
|
|
"""
|
|
match = _LAST_NUMBER.search(string)
|
|
if match:
|
|
next_ = str(int(match.group(1)) + 1)
|
|
start, end = match.span(1)
|
|
string = string[:max(end - len(next_), start)] + next_ + string[end:]
|
|
return string
|
|
|
|
|
|
def bump_major(version):
|
|
verinfo = parse(version)
|
|
return format_version(verinfo['major'] + 1, 0, 0)
|
|
|
|
|
|
def bump_minor(version):
|
|
verinfo = parse(version)
|
|
return format_version(verinfo['major'], verinfo['minor'] + 1, 0)
|
|
|
|
|
|
def bump_patch(version):
|
|
verinfo = parse(version)
|
|
return format_version(verinfo['major'], verinfo['minor'],
|
|
verinfo['patch'] + 1)
|
|
|
|
|
|
def bump_prerelease(version):
|
|
verinfo = parse(version)
|
|
verinfo['prerelease'] = _increment_string(verinfo['prerelease'] or 'rc.0')
|
|
return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'],
|
|
verinfo['prerelease'])
|
|
|
|
|
|
def bump_build(version):
|
|
verinfo = parse(version)
|
|
verinfo['build'] = _increment_string(verinfo['build'] or 'build.0')
|
|
return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'],
|
|
verinfo['prerelease'], verinfo['build'])
|