1516N/A#!/usr/bin/python
3N/A#
3N/A# CDDL HEADER START
3N/A#
3N/A# The contents of this file are subject to the terms of the
3N/A# Common Development and Distribution License (the "License").
3N/A# You may not use this file except in compliance with the License.
3N/A#
3N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
3N/A# or http://www.opensolaris.org/os/licensing.
3N/A# See the License for the specific language governing permissions
3N/A# and limitations under the License.
3N/A#
3N/A# When distributing Covered Code, include this CDDL HEADER in each
3N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
3N/A# If applicable, add the following below this CDDL HEADER, with the
3N/A# fields enclosed by brackets "[]" replaced with your own identifying
3N/A# information: Portions Copyright [yyyy] [name of copyright owner]
3N/A#
3N/A# CDDL HEADER END
3N/A#
3N/A
3N/A#
3158N/A# Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
3N/A#
3N/A
852N/Aimport calendar
59N/Aimport datetime
59N/Aimport time
1698N/Aimport weakref
3N/A
3234N/Afrom six.moves import zip
2639N/A
30N/ACONSTRAINT_NONE = 0
175N/ACONSTRAINT_AUTO = 50
30N/A
30N/ACONSTRAINT_RELEASE = 100
30N/ACONSTRAINT_RELEASE_MAJOR = 101
30N/ACONSTRAINT_RELEASE_MINOR = 102
30N/A
30N/ACONSTRAINT_BRANCH = 200
30N/ACONSTRAINT_BRANCH_MAJOR = 101
30N/ACONSTRAINT_BRANCH_MINOR = 102
30N/A
30N/ACONSTRAINT_SEQUENCE = 300
30N/A
933N/Aclass VersionError(Exception):
933N/A """Base exception class for all version errors."""
933N/A
416N/A def __init__(self, *args):
933N/A Exception.__init__(self, *args)
933N/A
3N/A
933N/Aclass IllegalDotSequence(VersionError):
933N/A """Used to indicate that the specified DotSequence is not valid."""
852N/A
416N/Aclass DotSequence(list):
3N/A """A DotSequence is the typical "x.y.z" string used in software
3N/A versioning. We define the "major release" value and the "minor release"
3N/A value as the first two numbers in the sequence."""
3N/A
1698N/A #
1698N/A # We employ the Flyweight design pattern for dotsequences, since they
1698N/A # are used immutably, are highly repetitive (0.5.11 over and over) and,
1698N/A # for what they contain, are relatively expensive memory-wise.
1698N/A #
1698N/A __dotseq_pool = weakref.WeakValueDictionary()
1698N/A
526N/A @staticmethod
852N/A def dotsequence_val(elem):
526N/A # Do this first; if the string is zero chars or non-numeric
526N/A # chars, this will throw.
526N/A x = int(elem)
526N/A if elem[0] == "-":
3194N/A raise ValueError("Negative number")
526N/A if x > 0 and elem[0] == "0":
3194N/A raise ValueError("Zero padded number")
526N/A return x
526N/A
1698N/A def __new__(cls, dotstring):
2639N/A ds = DotSequence.__dotseq_pool.get(dotstring)
2639N/A if ds is None:
2639N/A cls.__dotseq_pool[dotstring] = ds = \
2639N/A list.__new__(cls)
1698N/A return ds
1698N/A
3N/A def __init__(self, dotstring):
1698N/A # Was I already initialized? See __new__ above.
1698N/A if len(self) != 0:
1698N/A return
1698N/A
146N/A try:
526N/A list.__init__(self,
3234N/A list(map(DotSequence.dotsequence_val,
3234N/A dotstring.split("."))))
146N/A except ValueError:
146N/A raise IllegalDotSequence(dotstring)
3N/A
565N/A if len(self) == 0:
526N/A raise IllegalDotSequence("Empty DotSequence")
526N/A
3N/A def __str__(self):
416N/A return ".".join(map(str, self))
3N/A
429N/A def __hash__(self):
429N/A return hash(tuple(self))
429N/A
30N/A def is_subsequence(self, other):
30N/A """Return true if self is a "subsequence" of other, meaning that
30N/A other and self have identical components, up to the length of
30N/A self's sequence."""
30N/A
416N/A if len(self) > len(other):
30N/A return False
30N/A
3234N/A for a, b in zip(self, other):
175N/A if a != b:
30N/A return False
30N/A return True
30N/A
3N/A def is_same_major(self, other):
416N/A """ Test if DotSequences have the same major number """
416N/A return self[0] == other[0]
3N/A
3N/A def is_same_minor(self, other):
416N/A """ Test if DotSequences have the same major and minor num """
416N/A return self[0] == other[0] and self[1] == other[1]
3N/A
3N/A
852N/Aclass MatchingDotSequence(DotSequence):
852N/A """A subclass of DotSequence with (much) weaker rules about its format.
852N/A This is intended to accept user input with wildcard characters."""
852N/A
2224N/A #
2224N/A # We employ the Flyweight design pattern for dotsequences, since they
2224N/A # are used immutably, are highly repetitive (0.5.11 over and over) and,
2224N/A # for what they contain, are relatively expensive memory-wise.
2224N/A #
2224N/A __matching_dotseq_pool = weakref.WeakValueDictionary()
2224N/A
852N/A @staticmethod
852N/A def dotsequence_val(elem):
852N/A # Do this first; if the string is zero chars or non-numeric
852N/A # chars (other than "*"), an exception will be raised.
852N/A if elem == "*":
852N/A return elem
852N/A
852N/A return DotSequence.dotsequence_val(elem)
852N/A
2224N/A def __new__(cls, dotstring):
2224N/A ds = MatchingDotSequence.__matching_dotseq_pool.get(dotstring,
2224N/A None)
2224N/A if ds is not None:
2224N/A return ds
2224N/A
2224N/A ds = list.__new__(cls)
2224N/A cls.__matching_dotseq_pool[dotstring] = ds
2224N/A return ds
2224N/A
852N/A def __init__(self, dotstring):
852N/A try:
852N/A list.__init__(self,
3234N/A list(map(self.dotsequence_val,
3234N/A dotstring.split("."))))
852N/A except ValueError:
852N/A raise IllegalDotSequence(dotstring)
852N/A
852N/A if len(self) == 0:
852N/A raise IllegalDotSequence("Empty MatchingDotSequence")
852N/A
852N/A def __ne__(self, other):
852N/A if not isinstance(other, DotSequence):
852N/A return True
852N/A
852N/A ls = len(self)
852N/A lo = len(other)
852N/A for i in range(max(ls, lo)):
852N/A try:
852N/A if self[i] != other[i] and ("*" not in (self[i],
852N/A other[i])):
852N/A return True
852N/A except IndexError:
852N/A if ls < (i + 1) and "*" not in (self[-1],
852N/A other[i]):
852N/A return True
852N/A if lo < (i + 1) and "*" not in (self[i],
852N/A other[-1]):
852N/A return True
852N/A return False
852N/A
852N/A def __eq__(self, other):
852N/A if not isinstance(other, DotSequence):
852N/A return False
852N/A
852N/A ls = len(self)
852N/A lo = len(other)
852N/A for i in range(max(ls, lo)):
852N/A try:
852N/A if self[i] != other[i] and ("*" not in (self[i],
852N/A other[i])):
852N/A return False
852N/A except IndexError:
852N/A if ls < (i + 1) and "*" not in (self[-1],
852N/A other[i]):
852N/A return False
852N/A if lo < (i + 1) and "*" not in (self[i],
852N/A other[-1]):
852N/A return False
852N/A return True
852N/A
3245N/A __hash__ = DotSequence.__hash__
3245N/A
1537N/A def is_subsequence(self, other):
1537N/A """Return true if self is a "subsequence" of other, meaning that
1537N/A other and self have identical components, up to the length of
1537N/A self's sequence or self or other is '*'."""
1537N/A
1537N/A if str(self) == "*" or str(other) == "*":
1537N/A return True
1537N/A
1537N/A if len(self) > len(other):
1537N/A return False
1537N/A
3234N/A for a, b in zip(self, other):
1537N/A if a != b:
1537N/A return False
1537N/A return True
1537N/A
1537N/A def is_same_major(self, other):
1537N/A """Test if DotSequences have the same major number, or major
1537N/A is '*'."""
1537N/A return self[0] == "*" or other[0] == "*" or self[0] == other[0]
1537N/A
1537N/A def is_same_minor(self, other):
1537N/A """ Test if DotSequences have the same major and minor num."""
1537N/A return self[0] == "*" or other[0] == "*" or self[1] == "*" or \
1537N/A other[1] == "*" or \
1537N/A (self[0] == other[0] and self[1] == other[1])
1537N/A
852N/A
933N/Aclass IllegalVersion(VersionError):
933N/A """Used to indicate that the specified version string is not valid."""
852N/A
3N/Aclass Version(object):
59N/A """Version format is release[,build_release]-branch:datetime, which we
416N/A decompose into three DotSequences and a date string. Time
59N/A representation is in the ISO8601-compliant form "YYYYMMDDTHHMMSSZ",
59N/A referring to the UTC time associated with the version. The release and
59N/A branch DotSequences are interpreted normally, where v1 < v2 implies that
59N/A v2 is a later release or branch. The build_release DotSequence records
2958N/A the system on which the package binaries were constructed."""
3N/A
1698N/A __slots__ = ["release", "branch", "build_release", "timestr"]
1698N/A
2958N/A def __init__(self, version_string, build_string=None):
3N/A # XXX If illegally formatted, raise exception.
59N/A
416N/A if not version_string:
3194N/A raise IllegalVersion("Version cannot be empty")
416N/A
526N/A #
526N/A # Locate and extract the time, branch, and build strings,
526N/A # if specified. Error checking happens in the second half of
526N/A # the routine. In the event that a given part of the input is
526N/A # signalled but empty (for example: '0.3-' or '0.3-3.0:',
526N/A # we'll produce an empty (but not None) string for that portion.
526N/A #
526N/A
526N/A # Locate and extract the time string, if specified.
416N/A timeidx = version_string.find(":")
416N/A if timeidx != -1:
146N/A timestr = version_string[timeidx + 1:]
416N/A else:
146N/A timeidx = None
146N/A timestr = None
59N/A
526N/A # Locate and extract the branch string, if specified.
416N/A branchidx = version_string.find("-")
416N/A if branchidx != -1:
146N/A branch = version_string[branchidx + 1:timeidx]
416N/A else:
146N/A branchidx = timeidx
146N/A branch = None
45N/A
526N/A # Locate and extract the build string, if specified.
416N/A buildidx = version_string.find(",")
416N/A if buildidx != -1:
146N/A build = version_string[buildidx + 1:branchidx]
416N/A else:
146N/A buildidx = branchidx
146N/A build = None
45N/A
175N/A if buildidx == 0:
3194N/A raise IllegalVersion("Versions must have a release value")
175N/A
526N/A #
526N/A # Error checking and conversion from strings to objects
526N/A # begins here.
526N/A #
565N/A try:
565N/A self.release = DotSequence(version_string[:buildidx])
146N/A
565N/A if branch is not None:
565N/A self.branch = DotSequence(branch)
565N/A else:
565N/A self.branch = None
22N/A
565N/A if build is not None:
565N/A self.build_release = DotSequence(build)
565N/A else:
856N/A if build_string is None:
2958N/A build_string = "5.11"
565N/A self.build_release = DotSequence(build_string)
526N/A
3171N/A except IllegalDotSequence as e:
3158N/A raise IllegalVersion("Bad Version: {0}".format(e))
59N/A
416N/A #
416N/A # In 99% of the cases in which we use date and time, it's solely
416N/A # for comparison. Since the ISO date string lexicographically
416N/A # collates in date order, we just hold onto the string-
416N/A # converting it to anything else is expensive.
416N/A #
526N/A if timestr is not None:
416N/A if len(timestr) != 16 or timestr[8] != "T" \
416N/A or timestr[15] != "Z":
3194N/A raise IllegalVersion("Time must be ISO8601 format.")
416N/A try:
416N/A dateint = int(timestr[0:8])
416N/A timeint = int(timestr[9:15])
3194N/A datetime.datetime(dateint // 10000,
3194N/A (dateint // 100) % 100,
416N/A dateint % 100,
3194N/A timeint // 10000,
3194N/A (timeint // 100) % 100,
416N/A timeint % 100)
576N/A except ValueError:
3194N/A raise IllegalVersion("Time must be ISO8601 format.")
416N/A
416N/A self.timestr = timestr
146N/A else:
416N/A self.timestr = None
3N/A
2690N/A @staticmethod
2690N/A def getstate(obj, je_state=None):
2690N/A """Returns the serialized state of this object in a format
2690N/A that that can be easily stored using JSON, pickle, etc."""
2690N/A return str(obj)
2690N/A
2690N/A @staticmethod
2690N/A def fromstate(state, jd_state=None):
2690N/A """Allocate a new object using previously serialized state
2690N/A obtained via getstate()."""
2690N/A return Version(state, None)
2690N/A
3N/A def __str__(self):
416N/A outstr = str(self.release) + "," + str(self.build_release)
175N/A if self.branch:
416N/A outstr += "-" + str(self.branch)
416N/A if self.timestr:
416N/A outstr += ":" + self.timestr
416N/A return outstr
3N/A
419N/A def __repr__(self):
3158N/A return "<pkg.fmri.Version '{0}' at {1:#x}>".format(self,
3158N/A id(self))
419N/A
2958N/A def get_version(self, include_build=True):
2958N/A if include_build:
2958N/A outstr = str(self.release) + "," + str(self.build_release)
2958N/A else:
2958N/A outstr = str(self.release)
2958N/A if self.branch:
2958N/A outstr += "-" + str(self.branch)
2958N/A if self.timestr:
2958N/A outstr += ":" + self.timestr
2958N/A return outstr
2958N/A
23N/A def get_short_version(self):
175N/A branch_str = ""
852N/A if self.branch is not None:
3158N/A branch_str = "-{0}".format(self.branch)
3158N/A return "{0}{1}".format(self.release, branch_str)
23N/A
416N/A def set_timestamp(self, timestamp=datetime.datetime.utcnow()):
416N/A assert type(timestamp) == datetime.datetime
416N/A assert timestamp.tzname() == None or timestamp.tzname() == "UTC"
416N/A self.timestr = timestamp.strftime("%Y%m%dT%H%M%SZ")
32N/A
313N/A def get_timestamp(self):
416N/A if not self.timestr:
416N/A return None
416N/A t = time.strptime(self.timestr, "%Y%m%dT%H%M%SZ")
416N/A return datetime.datetime.utcfromtimestamp(calendar.timegm(t))
32N/A
3N/A def __ne__(self, other):
852N/A if not isinstance(other, Version):
22N/A return True
22N/A
3N/A if self.release == other.release and \
3N/A self.branch == other.branch and \
416N/A self.timestr == other.timestr:
3N/A return False
3N/A return True
3N/A
3N/A def __eq__(self, other):
852N/A if not isinstance(other, Version):
22N/A return False
22N/A
3N/A if self.release == other.release and \
3N/A self.branch == other.branch and \
416N/A self.timestr == other.timestr:
3N/A return True
3N/A return False
3N/A
3N/A def __lt__(self, other):
175N/A """Returns True if 'self' comes before 'other', and vice versa.
175N/A
175N/A If exactly one of the release values of the versions is None,
175N/A then that version is less than the other. The same applies to
175N/A the branch and timestamp components.
175N/A """
852N/A if not isinstance(other, Version):
258N/A return False
258N/A
416N/A if self.release < other.release:
416N/A return True
416N/A if self.release != other.release:
3N/A return False
175N/A
416N/A if self.branch != other.branch:
3245N/A if self.branch is None and other.branch:
3245N/A return True
3245N/A if self.branch and other.branch is None:
3245N/A return False
3245N/A if self.branch < other.branch:
3245N/A return True
3N/A return False
175N/A
3245N/A if self.timestr != other.timestr:
3245N/A if self.timestr is None and other.timestr:
3245N/A return True
3245N/A if self.timestr and other.timestr is None:
3245N/A return False
3245N/A if self.timestr < other.timestr:
3245N/A return True
3245N/A return False
3N/A
3N/A def __gt__(self, other):
175N/A """Returns True if 'self' comes after 'other', and vice versa.
175N/A
175N/A If exactly one of the release values of the versions is None,
175N/A then that version is less than the other. The same applies to
175N/A the branch and timestamp components.
175N/A """
852N/A if not isinstance(other, Version):
416N/A return True
258N/A
416N/A if self.release > other.release:
3N/A return True
416N/A if self.release != other.release:
3N/A return False
175N/A
416N/A if self.branch != other.branch:
3245N/A if self.branch and other.branch is None:
3245N/A return True
3245N/A if self.branch is None and other.branch:
3245N/A return False
3245N/A if self.branch > other.branch:
3245N/A return True
3N/A return False
175N/A
3245N/A if self.timestr != other.timestr:
3245N/A if self.timestr and other.timestr is None:
3245N/A return True
3245N/A if self.timestr is None and other.timestr:
3245N/A return False
3245N/A if self.timestr > other.timestr:
3245N/A return True
3245N/A return False
3N/A
3245N/A def __le__(self, other):
3245N/A return not self > other
416N/A
3245N/A def __ge__(self, other):
3245N/A return not self < other
24N/A
429N/A def __hash__(self):
526N/A # If a timestamp is present, it's enough to hash on, and is
526N/A # nicely unique. If not, use release and branch, which are
526N/A # not very unique.
526N/A if self.timestr:
526N/A return hash(self.timestr)
526N/A else:
526N/A return hash((self.release, self.branch))
429N/A
30N/A def is_successor(self, other, constraint):
30N/A """Evaluate true if self is a successor version to other.
30N/A
30N/A The loosest constraint is CONSTRAINT_NONE (None is treated
30N/A equivalently, which is a simple test for self > other. As we
30N/A proceed through the policies we get stricter, depending on the
30N/A selected constraint.
30N/A
175N/A Slightly less loose is CONSTRAINT_AUTO. In this case, if any of
175N/A the release, branch, or timestamp components is None, it acts as
175N/A a "don't care" value -- a versioned component always succeeds
175N/A None.
175N/A
30N/A For CONSTRAINT_RELEASE, self is a successor to other if all of
30N/A the components of other's release match, and there are later
59N/A components of self's version. The branch and datetime
30N/A components are ignored.
30N/A
30N/A For CONSTRAINT_RELEASE_MAJOR and CONSTRAINT_RELEASE_MINOR, other
30N/A is effectively truncated to [other[0]] and [other[0], other[1]]
30N/A prior to being treated as for CONSTRAINT_RELEASE.
30N/A
30N/A Similarly for CONSTRAINT_BRANCH, the release fields of other and
30N/A self are expected to be identical, and then the branches are
30N/A compared as releases were for the CONSTRAINT_RELEASE* policies.
30N/A """
30N/A
30N/A if constraint == None or constraint == CONSTRAINT_NONE:
30N/A return self > other
30N/A
1537N/A if constraint == CONSTRAINT_AUTO and \
1537N/A type(other) == MatchingVersion:
175N/A if other.release and self.release:
1537N/A if not other.release.is_subsequence(
1537N/A self.release):
1537N/A return False
2224N/A elif other.release and str(other.release) != "*":
1537N/A return False
175N/A
175N/A if other.branch and self.branch:
1537N/A if not other.branch.is_subsequence(self.branch):
1537N/A return False
2224N/A elif other.branch and str(other.branch) != "*":
1537N/A return False
175N/A
416N/A if self.timestr and other.timestr:
1537N/A if not (other.timestr == self.timestr or \
1537N/A other.timestr == "*"):
1537N/A return False
2224N/A elif other.timestr and str(other.timestr) != "*":
1537N/A return False
175N/A
1537N/A return True
1537N/A elif constraint == CONSTRAINT_AUTO:
1537N/A if other.release and self.release:
1537N/A if not other.release.is_subsequence(
1537N/A self.release):
1537N/A return False
1537N/A elif other.release:
1537N/A return False
1537N/A
1537N/A if other.branch and self.branch:
1537N/A if not other.branch.is_subsequence(self.branch):
1537N/A return False
1537N/A elif other.branch:
1537N/A return False
1537N/A
1537N/A if self.timestr and other.timestr:
1537N/A if other.timestr != self.timestr:
1537N/A return False
1537N/A elif other.timestr:
1537N/A return False
1537N/A
1537N/A return True
175N/A
30N/A if constraint == CONSTRAINT_RELEASE:
36N/A return other.release.is_subsequence(self.release)
30N/A
30N/A if constraint == CONSTRAINT_RELEASE_MAJOR:
30N/A return other.release.is_same_major(self.release)
30N/A
30N/A if constraint == CONSTRAINT_RELEASE_MINOR:
30N/A return other.release.is_same_minor(self.release)
30N/A
30N/A if constraint == CONSTRAINT_BRANCH:
30N/A return other.branch.is_subsequence(self.branch)
30N/A
30N/A if constraint == CONSTRAINT_BRANCH_MAJOR:
30N/A return other.branch.is_same_major(self.branch)
30N/A
30N/A if constraint == CONSTRAINT_BRANCH_MINOR:
30N/A return other.branch.is_same_minor(self.branch)
30N/A
3194N/A raise ValueError("constraint has unknown value")
30N/A
1537N/A @classmethod
1537N/A def split(self, ver):
2199N/A """Takes an assumed valid version string and splits it into
1537N/A its components as a tuple of the form ((release, build_release,
1537N/A branch, timestr), short_ver)."""
1537N/A
1537N/A # Locate and extract the time string.
1537N/A timeidx = ver.find(":")
1537N/A if timeidx != -1:
1537N/A timestr = ver[timeidx + 1:]
1537N/A else:
1537N/A timeidx = None
1537N/A timestr = None
1537N/A
1537N/A # Locate and extract the branch string.
1537N/A branchidx = ver.find("-")
1537N/A if branchidx != -1:
1537N/A branch = ver[branchidx + 1:timeidx]
1537N/A else:
1537N/A branchidx = timeidx
1537N/A branch = None
1537N/A
1537N/A # Locate and extract the build string.
1537N/A buildidx = ver.find(",")
1537N/A if buildidx != -1:
1537N/A build = ver[buildidx + 1:branchidx]
1537N/A else:
1537N/A buildidx = branchidx
1537N/A build = None
1537N/A
1537N/A release = ver[:buildidx]
1537N/A
1537N/A build_release = ""
1537N/A if build is not None:
1537N/A build_release = build
1537N/A
1537N/A if branch is not None:
1537N/A short_ver = release + "-" + branch
1537N/A else:
1537N/A short_ver = release
1537N/A return (release, build_release, branch, timestr), short_ver
1537N/A
852N/A
852N/Aclass MatchingVersion(Version):
852N/A """An alternative for Version with (much) weaker rules about its format.
852N/A This is intended to accept user input with globbing characters."""
852N/A
2224N/A
2224N/A __slots__ = ["match_latest", "__original"]
2224N/A
2958N/A def __init__(self, version_string, build_string=None):
852N/A if version_string is None or not len(version_string):
3194N/A raise IllegalVersion("Version cannot be empty")
852N/A
2224N/A if version_string == "latest":
2224N/A # Treat special "latest" syntax as equivalent to '*' for
2224N/A # version comparison purposes.
2224N/A self.match_latest = True
2224N/A version_string = "*"
2224N/A else:
2224N/A self.match_latest = False
852N/A
2199N/A (release, build_release, branch, timestr), ignored = \
2199N/A self.split(version_string)
2199N/A if not build_release:
2199N/A build_release = build_string
852N/A
852N/A #
852N/A # Error checking and conversion from strings to objects
852N/A # begins here.
852N/A #
852N/A try:
852N/A #
852N/A # Every component of the version (after the first) is
852N/A # optional, if not provided, assume "*" (wildcard).
852N/A #
852N/A for attr, vals in (
852N/A ('release', (release,)),
2199N/A ('build_release', (build_release, "*")),
852N/A ('branch', (branch, "*")),
852N/A ('timestr', (timestr, "*"))):
852N/A for val in vals:
2199N/A if not val:
852N/A continue
852N/A if attr != 'timestr':
852N/A val = MatchingDotSequence(val)
852N/A setattr(self, attr, val)
852N/A break
3171N/A except IllegalDotSequence as e:
3158N/A raise IllegalVersion("Bad Version: {0}".format(e))
852N/A
852N/A outstr = str(release)
852N/A if build_release is not None:
852N/A outstr += "," + str(build_release)
852N/A if branch is not None:
852N/A outstr += "-" + str(branch)
852N/A if timestr is not None:
852N/A outstr += ":" + timestr
852N/A
852N/A # Store the re-constructed input value for use as a string
852N/A # representation of this object.
852N/A self.__original = outstr
852N/A
852N/A def __str__(self):
2224N/A if self.match_latest:
2224N/A return "latest"
852N/A return self.__original
852N/A
852N/A def get_timestamp(self):
852N/A if self.timestr == "*":
852N/A return "*"
852N/A return Version.get_timestamp(self)
852N/A
852N/A def __ne__(self, other):
852N/A if not isinstance(other, Version):
852N/A return True
852N/A
852N/A if self.release == other.release and \
852N/A self.build_release == other.build_release and \
852N/A self.branch == other.branch and \
852N/A ((self.timestr == other.timestr) or ("*" in (self.timestr,
852N/A other.timestr))):
852N/A return False
852N/A return True
852N/A
852N/A def __eq__(self, other):
852N/A if not isinstance(other, Version):
852N/A return False
852N/A
852N/A if self.release == other.release and \
852N/A self.build_release == other.build_release and \
852N/A self.branch == other.branch and \
852N/A ((self.timestr == other.timestr) or ("*" in (self.timestr,
852N/A other.timestr))):
852N/A return True
852N/A return False
852N/A
852N/A def __lt__(self, other):
852N/A """Returns True if 'self' comes before 'other', and vice versa.
852N/A
852N/A If exactly one of the release values of the versions is None or
852N/A "*", then that version is less than the other. The same applies
852N/A to the branch and timestamp components.
852N/A """
852N/A if not isinstance(other, Version):
852N/A return False
852N/A
2224N/A if str(self.release) == "*" and str(other.release) != "*":
852N/A return True
852N/A if self.release < other.release:
852N/A return True
852N/A if self.release != other.release:
852N/A return False
852N/A
2224N/A if str(self.build_release) == "*" and \
2224N/A str(other.build_release) != "*":
852N/A return True
852N/A if self.build_release < other.build_release:
852N/A return True
852N/A if self.build_release != other.build_release:
852N/A return False
852N/A
2224N/A if str(self.branch) == "*" and str(other.branch) != "*":
852N/A return True
852N/A if self.branch < other.branch:
852N/A return True
852N/A if self.branch != other.branch:
852N/A return False
852N/A
852N/A if self.timestr == "*" and other.timestr != "*":
852N/A return True
852N/A
852N/A return self.timestr < other.timestr
852N/A
852N/A def __gt__(self, other):
852N/A """Returns True if 'self' comes after 'other', and vice versa.
852N/A
852N/A If exactly one of the release values of the versions is None or
852N/A "*", then that version is less than the other. The same applies
852N/A to the branch and timestamp components.
852N/A """
852N/A if not isinstance(other, Version):
852N/A return True
852N/A
2224N/A if str(self.release) == "*" and str(other.release) != "*":
852N/A return False
852N/A if self.release > other.release:
852N/A return True
852N/A if self.release != other.release:
852N/A return False
852N/A
2224N/A if str(self.build_release) == "*" and \
2224N/A str(other.build_release) != "*":
852N/A return False
852N/A if self.build_release > other.build_release:
852N/A return True
852N/A if self.build_release != other.build_release:
852N/A return False
852N/A
2224N/A if str(self.branch) == "*" and str(other.branch) != "*":
852N/A return False
852N/A if self.branch > other.branch:
852N/A return True
852N/A if self.branch != other.branch:
852N/A return False
852N/A
852N/A if self.timestr == "*" and other.timestr != "*":
852N/A return False
852N/A
852N/A return self.timestr > other.timestr
852N/A
852N/A def __hash__(self):
852N/A # If a timestamp is present, it's enough to hash on, and is
852N/A # nicely unique. If not, use release and branch, which are
852N/A # not very unique.
852N/A if self.timestr and self.timestr != "*":
852N/A return hash(self.timestr)
852N/A else:
852N/A return hash((self.release, self.branch))