'''
Synapse utilites for dealing with Semvar versioning.
This includes the Synapse version information.
'''
import string
import regex
import packaging.version as p_version
import packaging.specifiers as p_specifiers
# This module is imported during synapse.__init__. As such, we can't pull
# arbitrary modules from Synapse here. synapse.exc is currently safe,
# but we should not add other modules to this module.
import synapse.exc as s_exc
vseps = ('.', '-', '_', '+')
mask20 = 0xFFFFF
mask60 = 0xFFFFFFFFFFFFFFF
semverstr = r'''^(?P<maj>(0(?![0-9])|[1-9][0-9]*))\.(?P<min>(0(?![0-9])|[1-9][0-9]*))\.(?P<pat>(0(?![0-9])|[1-9][0-9]*))(\-(?P<pre>([0-9A-Za-z\-\.]+)))?(\+(?P<bld>([0-9A-Za-z\.\-]+)))?$'''
semver_re = regex.compile(semverstr)
[docs]
def parseSemver(text):
'''
Parse a Semantic Version string into is component parts.
Args:
text (str): A text string to parse into semver components. This string has whitespace and leading 'v'
characters stripped off of it.
Examples:
Parse a string into it semvar parts::
parts = parseSemver('v1.2.3')
Returns:
dict: The dictionary will contain the keys 'major', 'minor' and 'patch' pointing to integer values.
The dictionary may also contain keys for 'build' and 'pre' information if that data is parsed out
of a semver string. None is returned if the string is not a valid Semver string.
'''
# eat whitespace and leading chars common on version strings
txt = text.strip().lstrip('vV')
ret = {}
m = semver_re.match(txt)
if not m:
return None
d = m.groupdict()
ret['major'] = int(d.get('maj'))
ret['minor'] = int(d.get('min'))
ret['patch'] = int(d.get('pat'))
pre = d.get('pre')
bld = d.get('bld')
if pre:
# Validate pre
parts = pre.split('.')
for part in parts:
if not part:
return None
try:
int(part)
except ValueError:
continue
else:
if part[0] == '0' and len(part) > 1:
return None
ret['pre'] = pre
if bld:
# Validate bld
parts = bld.split('.')
for part in parts:
if not part:
return None
ret['build'] = bld
return ret
[docs]
def packVersion(major, minor=0, patch=0):
'''
Pack a set of major/minor/patch integers into a single integer for storage.
Args:
major (int): Major version level integer.
minor (int): Minor version level integer.
patch (int): Patch version level integer.
Returns:
int: System normalized integer value to represent a software version.
'''
ret = patch & mask20
ret = ret | (minor & mask20) << 20
ret = ret | (major & mask20) << 20 * 2
return ret
[docs]
def unpackVersion(ver):
'''
Unpack a system normalized integer representing a softare version into its component parts.
Args:
ver (int): System normalized integer value to unpack into a tuple.
Returns:
(int, int, int): A tuple containing the major, minor and patch values shifted out of the integer.
'''
major = (ver >> 20 * 2) & mask20
minor = (ver >> 20) & mask20
patch = ver & mask20
return major, minor, patch
[docs]
def fmtVersion(*vsnparts):
'''
Join a string of parts together with a . separator.
Args:
*vsnparts:
Returns:
'''
if len(vsnparts) < 1:
raise s_exc.BadTypeValu(valu=repr(vsnparts), name='fmtVersion',
mesg='Not enough version parts to form a version string with.',)
ret = '.'.join([str(part).lower() for part in vsnparts])
return ret
[docs]
def parseVersionParts(text, seps=vseps):
'''
Extract a list of major/minor/version integer strings from a string.
Args:
text (str): String to parse
seps (tuple): A tuple or list of separators to use when parsing the version string.
Examples:
Parse a simple version string into a major and minor parts::
parts = parseVersionParts('1.2')
Parse a complex version string into a major and minor parts::
parts = parseVersionParts('wowsoft_1.2')
Parse a simple version string into a major, minor and patch parts. Parts after the "3." are dropped from the
results::
parts = parseVersionParts('1.2.3.4.5')
Notes:
This attempts to brute force out integers from the version string by stripping any leading ascii letters and
part separators, and then regexing out numeric parts optionally followed by part separators. It will stop at
the first mixed-character part encountered. For example, "1.2-3a" would only parse out the "1" and "2" from
the string.
Returns:
dict: Either a empty dictionary or dictionary containing up to three keys, 'major', 'minor' and 'patch'.
'''
# Join seps together
seps = ''.join(seps)
# Strip whitespace
text = text.strip()
# Strip off leading chars
text = text.lstrip(string.ascii_letters)
# Strip off any leading separator which may be present
text = text.lstrip(seps)
pattern = r'^(\d+)([{}]+|$)'.format(regex.escape(seps))
parts = []
ret = {}
off = 0
while True:
m = regex.search(pattern, text[off:])
if not m:
break
off += m.end()
p, s = m.groups()
parts.append(int(p))
if not parts:
return None
keys = ('major', 'minor', 'patch')
ret.update(zip(keys, parts))
return ret
[docs]
def matches(vers, cmprvers):
'''
Check if a version string matches a version comparison string.
'''
spec = p_specifiers.SpecifierSet(cmprvers)
return p_version.Version(vers) in spec
[docs]
def reqVersion(valu, reqver,
exc=s_exc.BadVersion,
mesg='Provided version does not match required version.'):
'''
Require a given version tuple is valid for a given requirements string.
Args:
valu Optional[Tuple[int, int, int]]: Major, minor and patch value to check.
reqver (str): A requirements version string.
exc (s_exc.SynErr): The synerr class to raise.
mesg (str): The message to pass in the exception.
Returns:
None: If the value is in bounds of minver and maxver.
Raises:
s_exc.BadVersion: If a precondition is incorrect or a version value is out of bounds.
'''
if valu is None:
mesg = 'Version value is missing. ' + mesg
raise exc(mesg=mesg, valu=valu, reqver=reqver)
spec = p_specifiers.SpecifierSet(reqver)
verstr = fmtVersion(*valu)
vers = p_version.Version(verstr)
if vers not in spec:
raise exc(mesg=mesg, valu=valu, verstr=verstr, reqver=reqver)
##############################################################################
# The following are touched during the release process by bumpversion.
# Do not modify these directly.
version = (2, 188, 1)
verstring = '.'.join([str(x) for x in version])
commit = ''