semver.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. """
  2. Python helper for Semantic Versioning (http://semver.org/)
  3. """
  4. import re
  5. __version__ = '2.6.0'
  6. __author__ = 'Konstantine Rybnikov'
  7. __author_email__ = 'k-bx@k-bx.com'
  8. _REGEX = re.compile('^(?P<major>(?:0|[1-9][0-9]*))'
  9. '\.(?P<minor>(?:0|[1-9][0-9]*))'
  10. '\.(?P<patch>(?:0|[1-9][0-9]*))'
  11. '(\-(?P<prerelease>[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?'
  12. '(\+(?P<build>[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$')
  13. _LAST_NUMBER = re.compile(r'(?:[^\d]*(\d+)[^\d]*)+')
  14. if not hasattr(__builtins__, 'cmp'):
  15. def cmp(a, b):
  16. return (a > b) - (a < b)
  17. def parse(version):
  18. """
  19. Parse version to major, minor, patch, pre-release, build parts.
  20. """
  21. match = _REGEX.match(version)
  22. if match is None:
  23. raise ValueError('%s is not valid SemVer string' % version)
  24. verinfo = match.groupdict()
  25. verinfo['major'] = int(verinfo['major'])
  26. verinfo['minor'] = int(verinfo['minor'])
  27. verinfo['patch'] = int(verinfo['patch'])
  28. return verinfo
  29. def compare(ver1, ver2):
  30. def nat_cmp(a, b):
  31. def convert(text):
  32. return (2, int(text)) if re.match('[0-9]+', text) else (1, text)
  33. def split_key(key):
  34. return [convert(c) for c in key.split('.')]
  35. a, b = a or '', b or ''
  36. return cmp(split_key(a), split_key(b))
  37. def compare_by_keys(d1, d2):
  38. for key in ['major', 'minor', 'patch']:
  39. v = cmp(d1.get(key), d2.get(key))
  40. if v:
  41. return v
  42. rc1, rc2 = d1.get('prerelease'), d2.get('prerelease')
  43. rccmp = nat_cmp(rc1, rc2)
  44. if not rccmp:
  45. return 0
  46. if not rc1:
  47. return 1
  48. elif not rc2:
  49. return -1
  50. return rccmp
  51. v1, v2 = parse(ver1), parse(ver2)
  52. return compare_by_keys(v1, v2)
  53. def match(version, match_expr):
  54. prefix = match_expr[:2]
  55. if prefix in ('>=', '<=', '==', '!='):
  56. match_version = match_expr[2:]
  57. elif prefix and prefix[0] in ('>', '<'):
  58. prefix = prefix[0]
  59. match_version = match_expr[1:]
  60. else:
  61. raise ValueError("match_expr parameter should be in format <op><ver>, "
  62. "where <op> is one of "
  63. "['<', '>', '==', '<=', '>=', '!=']. "
  64. "You provided: %r" % match_expr)
  65. possibilities_dict = {
  66. '>': (1,),
  67. '<': (-1,),
  68. '==': (0,),
  69. '!=': (-1, 1),
  70. '>=': (0, 1),
  71. '<=': (-1, 0)
  72. }
  73. possibilities = possibilities_dict[prefix]
  74. cmp_res = compare(version, match_version)
  75. return cmp_res in possibilities
  76. def max_ver(ver1, ver2):
  77. cmp_res = compare(ver1, ver2)
  78. if cmp_res == 0 or cmp_res == 1:
  79. return ver1
  80. else:
  81. return ver2
  82. def min_ver(ver1, ver2):
  83. cmp_res = compare(ver1, ver2)
  84. if cmp_res == 0 or cmp_res == -1:
  85. return ver1
  86. else:
  87. return ver2
  88. def format_version(major, minor, patch, prerelease=None, build=None):
  89. version = "%d.%d.%d" % (major, minor, patch)
  90. if prerelease is not None:
  91. version = version + "-%s" % prerelease
  92. if build is not None:
  93. version = version + "+%s" % build
  94. return version
  95. def _increment_string(string):
  96. """
  97. Look for the last sequence of number(s) in a string and increment, from:
  98. http://code.activestate.com/recipes/442460-increment-numbers-in-a-string/#c1
  99. """
  100. match = _LAST_NUMBER.search(string)
  101. if match:
  102. next_ = str(int(match.group(1)) + 1)
  103. start, end = match.span(1)
  104. string = string[:max(end - len(next_), start)] + next_ + string[end:]
  105. return string
  106. def bump_major(version):
  107. verinfo = parse(version)
  108. return format_version(verinfo['major'] + 1, 0, 0)
  109. def bump_minor(version):
  110. verinfo = parse(version)
  111. return format_version(verinfo['major'], verinfo['minor'] + 1, 0)
  112. def bump_patch(version):
  113. verinfo = parse(version)
  114. return format_version(verinfo['major'], verinfo['minor'],
  115. verinfo['patch'] + 1)
  116. def bump_prerelease(version):
  117. verinfo = parse(version)
  118. verinfo['prerelease'] = _increment_string(verinfo['prerelease'] or 'rc.0')
  119. return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'],
  120. verinfo['prerelease'])
  121. def bump_build(version):
  122. verinfo = parse(version)
  123. verinfo['build'] = _increment_string(verinfo['build'] or 'build.0')
  124. return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'],
  125. verinfo['prerelease'], verinfo['build'])