import re from distutils.version import Version, StrictVersion __all__ = ['CommonVersion', 'VersionPredicate', 'SplitProvision', 'SplitComparison', ] class CommonVersion(Version): """ Version numbering that handles most version numbering schemes. Implements the standard interface for version number classes as described by distutils.version.Version. A version consists of an alternating series of release numbers followed by an optional series of pre-release or post-release tags. A release number is a series of dot-separated numeric components. Release tags are a series of letters optionally followed by a release number. The pre-release tag name is alphabetically before "final". The post-release tag name is alphabetically greater than or equal to "final". For example, "1.0b2.dev-r41475" could denote Subversion revision 41475 of the in-development version of the second beta of release 1.0. Notice that "dev" is a pre-release tag, so this version is a lower version number than 1.0b2, which would be the actual second beta of release 1.0. But the "-r41475" is a post-release tag, so this version is newer than "1.0b2.dev". """ version_re = re.compile(r'\d+(\.\d+)*') tag_re = re.compile(r'[_.-]?([a-zA-Z]+)?(\d+(?:\.\d)*)?') # 'tag_aliases' maps release tags to the tag that should be used for # comparison purposes. tag_aliases = {'pr' : 'c', 'pre' : 'c', 'preview' : 'c', 'rc' : 'c', } def parse(self, vstring): # save the original string for use by __str__ self._original = vstring def versiontuple(vstring): """ Converts a dot-separated version number into a tuple of ints with any trailing zeros removed. """ version = map(int, vstring.split('.')) while version and not version[-1]: del version[-1] return tuple(version) # Get the version number match = self.version_re.match(vstring) if not match: raise ValueError("invalid version number: %r" % vstring) self.version = versiontuple(match.group()) # Check for pre- and post-release tags tags = [] start = match.end() end = len(vstring) while start < end: match = self.tag_re.match(vstring, start) if not match: raise ValueError("invalid release tag: %r" % vstring[start:]) tag, version = match.groups() tag = tag and tag.lower() if tag in self.tag_aliases: tag = self.tag_aliases[tag] if version: version = versiontuple(version) else: version = None tags.append((tag, version)) start = match.end() self.tags = tuple(tags) return def __str__(self): return self._original def __cmp__(self, other): if isinstance(other, str): other = self.__class__(other) compare = cmp(self.version, other.version) if compare == 0: compare = cmp(self.tags, other.tags) return compare try: from distutils.versionpredicate import VersionPredicate, \ split_provision as SplitProvision, \ splitUp as SplitComparison except ImportError: import operator re_validPackage = re.compile(r"(?i)^\s*([a-z_]\w*(?:\.[a-z_]\w*)*)(.*)") re_paren = re.compile(r"^\s*\((.*)\)\s*$") # (list) inside of parentheses re_provision = re.compile( "([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*)(?:\s*\(\s*([^)\s]+)\s*\))?$") re_splitComparison = re.compile(r"^\s*(<=|>=|<|>|!=|==)\s*([^\s,]+)\s*$") compmap = {"<": operator.lt, "<=": operator.le, "==": operator.eq, ">": operator.gt, ">=": operator.ge, "!=": operator.ne} class VersionPredicate: """ Parse and test package version predicates. """ def __init__(self, versionPredicateStr): """Parse a version predicate string.""" # Fields: # name: package name # pred: list of (comparison string, StrictVersion) versionPredicateStr = versionPredicateStr.strip() if not versionPredicateStr: raise ValueError("empty package restriction") match = re_validPackage.match(versionPredicateStr) if not match: raise ValueError("bad package name in %r" % versionPredicateStr) self.name, paren = match.groups() paren = paren.strip() if paren: match = re_paren.match(paren) if not match: raise ValueError("expected parenthesized list: %r" % paren) str = match.groups()[0] self.pred = [ SplitComparison(p) for p in str.split(",") ] if not self.pred: raise ValueError("empty parenthesized list in %r" % versionPredicateStr) else: self.pred = [] def __str__(self): if self.pred: seq = [cond + " " + str(ver) for cond, ver in self.pred] return self.name + " (" + ", ".join(seq) + ")" else: return self.name def satisfied_by(self, version): """True if version is compatible with all the predicates in self. The parameter version must be acceptable to the StrictVersion constructor. It may be either a string or StrictVersion. """ for cond, ver in self.pred: if not compmap[cond](version, ver): return False return True # originally distutils.versionpredicate.split_provision() def SplitProvision(value): """Return the name and optional version number of a provision. The version number, if given, will be returned as a `StrictVersion` instance, otherwise it will be `None`. """ value = value.strip() m = re_provision.match(value) if not m: raise ValueError("illegal provides specification: %r" % value) ver = m.group(2) or None if ver: ver = StrictVersion(ver) return m.group(1), ver # originally distutils.versionpredicate.splitUp() def SplitComparison(pred): """Parse a single version comparison. Return (comparison string, StrictVersion) """ res = re_splitComparison.match(pred) if not res: raise ValueError("bad package restriction syntax: %r" % pred) comp, verStr = res.groups() return (comp, StrictVersion(verStr))