| #!/usr/bin/env python |
| # Copyright 2017 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import sys |
| |
| if sys.version_info.major != 2: |
| unicode = str |
| |
| |
| def compare_int(left, right): |
| if left == right: |
| return 0 |
| return -1 if left < right else 1 |
| |
| def compare_char(left, right): |
| # 'man deb-version' specifies that characters are compared using |
| # their ASCII values, except alphabetic characters come before |
| # special characters and ~ comes before everything else. |
| table = '~$ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-.:' |
| left = table.find(left) |
| right = table.find(right) |
| assert left >= 0 and right >= 0 |
| return compare_int(left, right) |
| |
| def compare_part(left, right): |
| if isinstance(left, int) and isinstance(right, int): |
| return compare_int(left, right) |
| assert isinstance(left, (str, unicode)) and isinstance(right, (str, unicode)) |
| # 'man deb-version' specifies that '~' should be matched before the |
| # empty string. Add a '$' to the end of the strings to make this |
| # comparison easier. |
| left += '$' |
| right += '$' |
| i = 0 |
| while i < len(left) and i < len(right): |
| comp = compare_char(left[i], right[i]) |
| if comp != 0: |
| return comp |
| i += 1 |
| return compare_int(len(left), len(right)) |
| |
| def get_parts_from_component(component): |
| # 'man deb-version' specifies that components should be compared |
| # part-by-part, where parts are either strings or numbers. Strings |
| # are compared lexicographically while numbers are compared using |
| # magnitude. Components must start with a string part and end with |
| # a number part. The empty string will be prepended and 0 will be |
| # appended to satisfy this requirement. For example, the component |
| # '1.2.3' will be expanded to ['', 1, '.', 2, '.', 3], and the empty |
| # component will be expanded to ['', 0]. |
| part_is_string = True |
| parts = [] |
| part = '' |
| for c in component: |
| char_is_string = not c.isdigit() |
| if char_is_string != part_is_string: |
| parts.append(part if part_is_string else int(part)) |
| part = '' |
| part_is_string = char_is_string |
| part += c |
| if part_is_string: |
| parts.append(part) |
| part = '' |
| parts.append(0 if part == '' else int(part)) |
| return parts |
| |
| def compare_component(left, right): |
| i = 0 |
| left = get_parts_from_component(left) |
| right = get_parts_from_component(right) |
| while i < len(left) and i < len(right): |
| comp = compare_part(left[i], right[i]) |
| if comp != 0: |
| return comp |
| i += 1 |
| return compare_int(len(left), len(right)) |
| |
| class DebVersion: |
| def __init__(self, version_string): |
| self.version_string = version_string |
| self.epoch = 0 |
| self.upstream_version = '' |
| self.debian_revision = None |
| |
| colon = version_string.find(':') |
| if colon >= 0: |
| self.epoch = int(version_string[:colon]) |
| hyphen = version_string.rfind('-') |
| if hyphen >= 0: |
| self.debian_revision = version_string[hyphen + 1:] |
| upstream_version_start = colon + 1 |
| upstream_version_end = hyphen if hyphen >= 0 else len(version_string) |
| self.upstream_version = version_string[upstream_version_start: |
| upstream_version_end] |
| |
| def __str__(self): |
| return self.version_string |
| |
| # Comparison algorithm is specified in 'man deb-version'. |
| def __cmp__(self, other): |
| assert(isinstance(other, DebVersion)) |
| |
| # Epoch comparison. |
| if self.epoch != other.epoch: |
| return 1 if self.epoch > other.epoch else -1 |
| |
| # Upstream version comparison. |
| upstream_version_cmp = compare_component(self.upstream_version, |
| other.upstream_version) |
| if upstream_version_cmp != 0: |
| return upstream_version_cmp |
| |
| # Debian revision comparison. |
| if self.debian_revision == None and other.debian_revision == None: |
| return 0 |
| if self.debian_revision == None: |
| return -1 |
| if other.debian_revision == None: |
| return 1 |
| return compare_component(self.debian_revision, other.debian_revision) |
| |
| def __lt__(self, other): |
| return self.__cmp__(other) == -1 |
| |
| def __eq__(self, other): |
| return self.__cmp__(other) == 0 |