version_comparator.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. '''
  5. This is a really crummy, slow Python implementation of the Mozilla
  6. platform's nsIVersionComparator interface:
  7. https://developer.mozilla.org/En/NsIVersionComparator
  8. For more information, also see:
  9. http://mxr.mozilla.org/mozilla/source/xpcom/glue/nsVersionComparator.cpp
  10. '''
  11. import re
  12. import sys
  13. class VersionPart(object):
  14. '''
  15. Examples:
  16. >>> VersionPart('1')
  17. (1, None, 0, None)
  18. >>> VersionPart('1pre')
  19. (1, 'pre', 0, None)
  20. >>> VersionPart('1pre10')
  21. (1, 'pre', 10, None)
  22. >>> VersionPart('1pre10a')
  23. (1, 'pre', 10, 'a')
  24. >>> VersionPart('1+')
  25. (2, 'pre', 0, None)
  26. >>> VersionPart('*').numA == sys.maxint
  27. True
  28. >>> VersionPart('1') < VersionPart('2')
  29. True
  30. >>> VersionPart('2') > VersionPart('1')
  31. True
  32. >>> VersionPart('1') == VersionPart('1')
  33. True
  34. >>> VersionPart('1pre') > VersionPart('1')
  35. False
  36. >>> VersionPart('1') < VersionPart('1pre')
  37. False
  38. >>> VersionPart('1pre1') < VersionPart('1pre2')
  39. True
  40. >>> VersionPart('1pre10b') > VersionPart('1pre10a')
  41. True
  42. >>> VersionPart('1pre10b') == VersionPart('1pre10b')
  43. True
  44. >>> VersionPart('1pre10a') < VersionPart('1pre10b')
  45. True
  46. >>> VersionPart('1') > VersionPart('')
  47. True
  48. '''
  49. _int_part = re.compile('[+-]?(\d*)(.*)')
  50. _num_chars = '0123456789+-'
  51. def __init__(self, part):
  52. self.numA = 0
  53. self.strB = None
  54. self.numC = 0
  55. self.extraD = None
  56. if not part:
  57. return
  58. if part == '*':
  59. self.numA = sys.maxint
  60. else:
  61. match = self._int_part.match(part)
  62. self.numA = int(match.group(1))
  63. self.strB = match.group(2) or None
  64. if self.strB == '+':
  65. self.strB = 'pre'
  66. self.numA += 1
  67. elif self.strB:
  68. i = 0
  69. num_found = -1
  70. for char in self.strB:
  71. if char in self._num_chars:
  72. num_found = i
  73. break
  74. i += 1
  75. if num_found != -1:
  76. match = self._int_part.match(self.strB[num_found:])
  77. self.numC = int(match.group(1))
  78. self.extraD = match.group(2) or None
  79. self.strB = self.strB[:num_found]
  80. def _strcmp(self, str1, str2):
  81. # Any string is *before* no string.
  82. if str1 is None:
  83. if str2 is None:
  84. return 0
  85. else:
  86. return 1
  87. if str2 is None:
  88. return -1
  89. return cmp(str1, str2)
  90. def __cmp__(self, other):
  91. r = cmp(self.numA, other.numA)
  92. if r:
  93. return r
  94. r = self._strcmp(self.strB, other.strB)
  95. if r:
  96. return r
  97. r = cmp(self.numC, other.numC)
  98. if r:
  99. return r
  100. return self._strcmp(self.extraD, other.extraD)
  101. def __repr__(self):
  102. return repr((self.numA, self.strB, self.numC, self.extraD))
  103. def compare(a, b):
  104. '''
  105. Examples:
  106. >>> compare('1', '2')
  107. -1
  108. >>> compare('1', '1')
  109. 0
  110. >>> compare('2', '1')
  111. 1
  112. >>> compare('1.0pre1', '1.0pre2')
  113. -1
  114. >>> compare('1.0pre2', '1.0')
  115. -1
  116. >>> compare('1.0', '1.0.0')
  117. 0
  118. >>> compare('1.0.0', '1.0.0.0')
  119. 0
  120. >>> compare('1.0.0.0', '1.1pre')
  121. -1
  122. >>> compare('1.1pre', '1.1pre0')
  123. 0
  124. >>> compare('1.1pre0', '1.0+')
  125. 0
  126. >>> compare('1.0+', '1.1pre1a')
  127. -1
  128. >>> compare('1.1pre1a', '1.1pre1')
  129. -1
  130. >>> compare('1.1pre1', '1.1pre10a')
  131. -1
  132. >>> compare('1.1pre10a', '1.1pre10')
  133. -1
  134. >>> compare('1.1pre10a', '1.*')
  135. -1
  136. '''
  137. a_parts = a.split('.')
  138. b_parts = b.split('.')
  139. if len(a_parts) < len(b_parts):
  140. a_parts.extend([''] * (len(b_parts) - len(a_parts)))
  141. else:
  142. b_parts.extend([''] * (len(a_parts) - len(b_parts)))
  143. for a_part, b_part in zip(a_parts, b_parts):
  144. r = cmp(VersionPart(a_part), VersionPart(b_part))
  145. if r:
  146. return r
  147. return 0
  148. if __name__ == '__main__':
  149. import doctest
  150. doctest.testmod(verbose=True)