|  | #!/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 |