#!/usr/bin/python ######################################################################## # Copyright (C) 2010-2018 VMWare, Inc. # # All Rights Reserved # ######################################################################## """This module contain a class to express and compare version information.""" import re __all__ = ['Version', 'FWVersion', 'VibVersion'] class Version(object): """A generic class for expressing a version. Attributes: * versionstring - A string containing the version. """ def __init__(self, versionstring=''): """Class constructor. Parameters: * versionstring - The string containing version information. Returns: A new Version object. """ self.versionstring = versionstring __str__ = lambda self: self.versionstring __nonzero__ = lambda self: bool(self.versionstring) __bool__ = __nonzero__ @staticmethod def _compare(x, y): return (x > y) - (x < y) @classmethod def fromstring(cls, versionstring): """Parses versionstring into appropriate fields, returning a Version object. (This method is more useful in child classes.""" return cls(versionstring) def __cmp__(self, other): """Provides a comparison function between two Version objects. Comparison is based solely on the 'versionstring' attribute, but the string is divided into fields by splitting at '.' characters. Each field is then further divided into a leading numeric portion and a trailing portion. Fields from each versionstring are compared left- to-right, by first doing a comparison between the numeric portions, then comparing the string portions of the numeric parts are equal. Comparison ends when one of the fields is greater or less than the corresponding field in the other version, or when we run out of fields to compare. This algorithm provides the least-surprising results. For example: * 4.2 is less than 4.10. (a stringwise comparision would provide the opposite result.) * 4.0 is less than 4.0a. (a numeric comparison could not account for the 'a'.) * 4 is less than 4.0. * 4.a is less than 4.0a. * 4.0 is less than 4a. Returns: -1, 0, or 1, if other is less than, equal to or greater than self. """ def splitfield(field): m = re.match(r'(\d*)(.*)', field) # NoneType cannot compare to Int in Python 3 # use -1 since None < 0 is true for Python 2 numeric = int(m.group(1)) if m.group(1) else -1 return (numeric, m.group(2)) mine = tuple(splitfield(f) for f in self.versionstring.split('.')) theirs = tuple(splitfield(f) for f in str(other).split('.')) return self._compare(mine, theirs) __lt__ = lambda self, other: self.__cmp__(other) < 0 __le__ = lambda self, other: self.__cmp__(other) <= 0 __eq__ = lambda self, other: self.__cmp__(other) == 0 __ne__ = lambda self, other: self.__cmp__(other) != 0 __ge__ = lambda self, other: self.__cmp__(other) >= 0 __gt__ = lambda self, other: self.__cmp__(other) > 0 class FWVersion(Version): """A class for representing a firmware version. Attributes: * majorversion - A positive integer expressing the major firmware version. May be None if all other version fields are None. * minorversion - A positive integer expressing the minor firmware version. May be None if revisionnumber and buildnumber are None. * revisionnumber - A positive integer expressing the firmware revision number. May be None if buildnumber is None. * buildnumber - A positive integer expressing the firmware build number. May be None. Properties: * versionstring - A read-only property expressing the version fields as a string. """ def __init__(self, majorversion=None, minorversion=None, revisionnumber=None, buildnumber=None): """Class constructor. Parameters: * majorversion - Sets attribute of same name. * minorversion - Sets attribute of same name. * revisionnumber - Sets attribute of same name. * buildnumber - Sets attribute of same name. """ self.majorversion = majorversion self.minorversion = minorversion self.revisionnumber = revisionnumber self.buildnumber = buildnumber def __cmp__(self, other): if isinstance(other, FWVersion): mine = (self.majorversion, self.minorversion, self.revisionnumber, self.buildnumber) theirs = (other.majorversion, other.minorversion, other.revisionnumber, other.buildnumber) mine = tuple([f for f in mine if f is not None]) theirs = tuple([f for f in theirs if f is not None]) return self._compare(mine, theirs) else: return Version.__cmp__(self, other) @property def versionstring(self): """Return firmware version as a string. All numeric fields are concatenated with a '.', and None values are not included. Returns the empty string if all values are None. """ attrs = (self.majorversion, self.minorversion, self.revisionnumber, self.buildnumber) return '.'.join([str(f) for f in attrs if f is not None]) @classmethod def fromstring(cls, versionstring): """Create a new FWVersion object from a string. Parameters: * versionstring - A properly formatted string consisting of zero to four decimal integers separated by '.' characters. Returns: A new FWVersion object. Raises: * ValueError - If string is not in the proper format. """ try: parameters = tuple(int(field) for field in versionstring.split('.')) return cls(*parameters) except Exception: raise ValueError("Could not parse '%s' into version fields." % versionstring) class VibVersion(Version): """A class to express a VIB version. Attributes: * version - A string giving the version. * release - A string giving the release. Properties: * versionstring - Represents VibVersion as a string. """ def __init__(self, version, release=''): """Class constructor. Parameters: * version - A string to assign to the version attribute. * release - A string to assign to the release attribute. Returns: A new VibVersion object. """ self.version = Version(version) self.release = Version(release) @property def versionstring(self): """Returns VibVersion information as a string.""" if self.release.versionstring: return "%s-%s" % (self.version.versionstring, self.release.versionstring) else: return self.version.versionstring @classmethod def fromstring(cls, versionstring): """Create an VibVersion object from a string. Parameters: * versionstring - Must be a string in either version-release or epoch:version-release format. Returns: A new VibVersion object. Raises: * ValueError - If string cannot be parsed into a Vib version. """ if versionstring.count('-'): try: (version, release) = versionstring.split('-') except Exception: msg = "Could not parse '%s' to a Vib version." % versionstring raise ValueError(msg) else: version = versionstring release = '' return cls(version, release) def __cmp__(self, other): # We don't universally apply the same condition to Version. if isinstance(other, VibVersion) or other.__class__.__name__ == 'VibVersion': mine = (self.version, self.release) theirs = (other.version, other.release) return self._compare(mine, theirs) elif isinstance(other, Version): return Version.__cmp__(self.version, other) else: raise TypeError("Type '%s' not valid for comparisons with VibVersion." % other.__class__.__name__)