michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: ''' michael@0: This is a really crummy, slow Python implementation of the Mozilla michael@0: platform's nsIVersionComparator interface: michael@0: michael@0: https://developer.mozilla.org/En/NsIVersionComparator michael@0: michael@0: For more information, also see: michael@0: michael@0: http://mxr.mozilla.org/mozilla/source/xpcom/glue/nsVersionComparator.cpp michael@0: ''' michael@0: michael@0: import re michael@0: import sys michael@0: michael@0: class VersionPart(object): michael@0: ''' michael@0: Examples: michael@0: michael@0: >>> VersionPart('1') michael@0: (1, None, 0, None) michael@0: michael@0: >>> VersionPart('1pre') michael@0: (1, 'pre', 0, None) michael@0: michael@0: >>> VersionPart('1pre10') michael@0: (1, 'pre', 10, None) michael@0: michael@0: >>> VersionPart('1pre10a') michael@0: (1, 'pre', 10, 'a') michael@0: michael@0: >>> VersionPart('1+') michael@0: (2, 'pre', 0, None) michael@0: michael@0: >>> VersionPart('*').numA == sys.maxint michael@0: True michael@0: michael@0: >>> VersionPart('1') < VersionPart('2') michael@0: True michael@0: michael@0: >>> VersionPart('2') > VersionPart('1') michael@0: True michael@0: michael@0: >>> VersionPart('1') == VersionPart('1') michael@0: True michael@0: michael@0: >>> VersionPart('1pre') > VersionPart('1') michael@0: False michael@0: michael@0: >>> VersionPart('1') < VersionPart('1pre') michael@0: False michael@0: michael@0: >>> VersionPart('1pre1') < VersionPart('1pre2') michael@0: True michael@0: michael@0: >>> VersionPart('1pre10b') > VersionPart('1pre10a') michael@0: True michael@0: michael@0: >>> VersionPart('1pre10b') == VersionPart('1pre10b') michael@0: True michael@0: michael@0: >>> VersionPart('1pre10a') < VersionPart('1pre10b') michael@0: True michael@0: michael@0: >>> VersionPart('1') > VersionPart('') michael@0: True michael@0: ''' michael@0: michael@0: _int_part = re.compile('[+-]?(\d*)(.*)') michael@0: _num_chars = '0123456789+-' michael@0: michael@0: def __init__(self, part): michael@0: self.numA = 0 michael@0: self.strB = None michael@0: self.numC = 0 michael@0: self.extraD = None michael@0: michael@0: if not part: michael@0: return michael@0: michael@0: if part == '*': michael@0: self.numA = sys.maxint michael@0: else: michael@0: match = self._int_part.match(part) michael@0: self.numA = int(match.group(1)) michael@0: self.strB = match.group(2) or None michael@0: if self.strB == '+': michael@0: self.strB = 'pre' michael@0: self.numA += 1 michael@0: elif self.strB: michael@0: i = 0 michael@0: num_found = -1 michael@0: for char in self.strB: michael@0: if char in self._num_chars: michael@0: num_found = i michael@0: break michael@0: i += 1 michael@0: if num_found != -1: michael@0: match = self._int_part.match(self.strB[num_found:]) michael@0: self.numC = int(match.group(1)) michael@0: self.extraD = match.group(2) or None michael@0: self.strB = self.strB[:num_found] michael@0: michael@0: def _strcmp(self, str1, str2): michael@0: # Any string is *before* no string. michael@0: if str1 is None: michael@0: if str2 is None: michael@0: return 0 michael@0: else: michael@0: return 1 michael@0: michael@0: if str2 is None: michael@0: return -1 michael@0: michael@0: return cmp(str1, str2) michael@0: michael@0: def __cmp__(self, other): michael@0: r = cmp(self.numA, other.numA) michael@0: if r: michael@0: return r michael@0: michael@0: r = self._strcmp(self.strB, other.strB) michael@0: if r: michael@0: return r michael@0: michael@0: r = cmp(self.numC, other.numC) michael@0: if r: michael@0: return r michael@0: michael@0: return self._strcmp(self.extraD, other.extraD) michael@0: michael@0: def __repr__(self): michael@0: return repr((self.numA, self.strB, self.numC, self.extraD)) michael@0: michael@0: def compare(a, b): michael@0: ''' michael@0: Examples: michael@0: michael@0: >>> compare('1', '2') michael@0: -1 michael@0: michael@0: >>> compare('1', '1') michael@0: 0 michael@0: michael@0: >>> compare('2', '1') michael@0: 1 michael@0: michael@0: >>> compare('1.0pre1', '1.0pre2') michael@0: -1 michael@0: michael@0: >>> compare('1.0pre2', '1.0') michael@0: -1 michael@0: michael@0: >>> compare('1.0', '1.0.0') michael@0: 0 michael@0: michael@0: >>> compare('1.0.0', '1.0.0.0') michael@0: 0 michael@0: michael@0: >>> compare('1.0.0.0', '1.1pre') michael@0: -1 michael@0: michael@0: >>> compare('1.1pre', '1.1pre0') michael@0: 0 michael@0: michael@0: >>> compare('1.1pre0', '1.0+') michael@0: 0 michael@0: michael@0: >>> compare('1.0+', '1.1pre1a') michael@0: -1 michael@0: michael@0: >>> compare('1.1pre1a', '1.1pre1') michael@0: -1 michael@0: michael@0: >>> compare('1.1pre1', '1.1pre10a') michael@0: -1 michael@0: michael@0: >>> compare('1.1pre10a', '1.1pre10') michael@0: -1 michael@0: michael@0: >>> compare('1.1pre10a', '1.*') michael@0: -1 michael@0: ''' michael@0: michael@0: a_parts = a.split('.') michael@0: b_parts = b.split('.') michael@0: michael@0: if len(a_parts) < len(b_parts): michael@0: a_parts.extend([''] * (len(b_parts) - len(a_parts))) michael@0: else: michael@0: b_parts.extend([''] * (len(a_parts) - len(b_parts))) michael@0: michael@0: for a_part, b_part in zip(a_parts, b_parts): michael@0: r = cmp(VersionPart(a_part), VersionPart(b_part)) michael@0: if r: michael@0: return r michael@0: michael@0: return 0 michael@0: michael@0: if __name__ == '__main__': michael@0: import doctest michael@0: michael@0: doctest.testmod(verbose=True)