From cf6e0129fb48b15915485ff35962b8256a0db467 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Mon, 1 Dec 2008 16:04:04 +0100 Subject: [PATCH] [IMP] use the function from setuptools for compare the versions bzr revid: christophe@tinyerp.com-20081201150404-if1qmgfl0f54po91 --- bin/addons/base/module/module.py | 119 ++---------------------- bin/addons/base/module/parse_version.py | 96 +++++++++++++++++++ 2 files changed, 102 insertions(+), 113 deletions(-) create mode 100644 bin/addons/base/module/parse_version.py diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py index d3a3c1596ca..71005bcec34 100644 --- a/bin/addons/base/module/module.py +++ b/bin/addons/base/module/module.py @@ -35,112 +35,7 @@ import addons import pooler import netsvc -ver_regexp = re.compile("^(\\d+)((\\.\\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\\d*)*)(-r(\\d+))?$") -suffix_regexp = re.compile("^(alpha|beta|rc|pre|p)(\\d*)$") - -def vercmp(ver1, ver2): - """ - Compare two versions - Take from portage_versions.py - @param ver1: version to compare with - @type ver1: string (example "1.2-r3") - @param ver2: version to compare again - @type ver2: string (example "2.1-r1") - @rtype: None or float - @return: - 1. position if ver1 is greater than ver2 - 2. negative if ver1 is less than ver2 - 3. 0 if ver1 equals ver2 - 4. None if ver1 or ver2 are invalid - """ - - match1 = ver_regexp.match(ver1) - match2 = ver_regexp.match(ver2) - - if not match1 or not match1.groups(): - return None - if not match2 or not match2.groups(): - return None - - list1 = [int(match1.group(1))] - list2 = [int(match2.group(1))] - - if len(match1.group(2)) or len(match2.group(2)): - vlist1 = match1.group(2)[1:].split(".") - vlist2 = match2.group(2)[1:].split(".") - for i in range(0, max(len(vlist1), len(vlist2))): - # Implicit .0 is given -1, so 1.0.0 > 1.0 - # would be ambiguous if two versions that aren't literally equal - # are given the same value (in sorting, for example). - if len(vlist1) <= i or len(vlist1[i]) == 0: - list1.append(-1) - list2.append(int(vlist2[i])) - elif len(vlist2) <= i or len(vlist2[i]) == 0: - list1.append(int(vlist1[i])) - list2.append(-1) - # Let's make life easy and use integers unless we're forced to use floats - elif (vlist1[i][0] != "0" and vlist2[i][0] != "0"): - list1.append(int(vlist1[i])) - list2.append(int(vlist2[i])) - # now we have to use floats so 1.02 compares correctly against 1.1 - else: - list1.append(float("0."+vlist1[i])) - list2.append(float("0."+vlist2[i])) - # and now the final letter - if len(match1.group(4)): - list1.append(ord(match1.group(4))) - if len(match2.group(4)): - list2.append(ord(match2.group(4))) - - for i in range(0, max(len(list1), len(list2))): - if len(list1) <= i: - return -1 - elif len(list2) <= i: - return 1 - elif list1[i] != list2[i]: - return list1[i] - list2[i] - - # main version is equal, so now compare the _suffix part - list1 = match1.group(5).split("_")[1:] - list2 = match2.group(5).split("_")[1:] - - for i in range(0, max(len(list1), len(list2))): - # Implicit _p0 is given a value of -1, so that 1 < 1_p0 - if len(list1) <= i: - s1 = ("p","-1") - else: - s1 = suffix_regexp.match(list1[i]).groups() - if len(list2) <= i: - s2 = ("p","-1") - else: - s2 = suffix_regexp.match(list2[i]).groups() - if s1[0] != s2[0]: - return suffix_value[s1[0]] - suffix_value[s2[0]] - if s1[1] != s2[1]: - # it's possible that the s(1|2)[1] == '' - # in such a case, fudge it. - try: - r1 = int(s1[1]) - except ValueError: - r1 = 0 - try: - r2 = int(s2[1]) - except ValueError: - r2 = 0 - if r1 - r2: - return r1 - r2 - - # the suffix part is equal to, so finally check the revision - if match1.group(9): - r1 = int(match1.group(9)) - else: - r1 = 0 - if match2.group(9): - r2 = int(match2.group(9)) - else: - r2 = 0 - return r1 - r2 - +from parse_version import parse_version class module_repository(osv.osv): _name = "ir.module.repository" @@ -403,7 +298,7 @@ class module(osv.osv): terp = self.get_module_info(mod_name) if terp.get('installable', True) and mod.state == 'uninstallable': self.write(cr, uid, id, {'state': 'uninstalled'}) - if vercmp(terp.get('version', ''), mod.latest_version or '0') > 0: + if parse_version(terp.get('version', '')) > parse_version(mod.latest_version or ''): self.write(cr, uid, id, { 'latest_version': terp.get('version'), 'url': ''}) @@ -472,7 +367,7 @@ class module(osv.osv): if version == 'x': # 'x' version was a mistake version = '0' if name in mod_sort: - if vercmp(version, mod_sort[name][0]) <= 0: + if parse_version(version) <= parse_version(mod_sort[name][0]): continue mod_sort[name] = [version, extension] for name in mod_sort.keys(): @@ -494,8 +389,7 @@ class module(osv.osv): ['latest_version'] if latest_version == 'x': # 'x' version was a mistake latest_version = '0' - c = vercmp(version, latest_version) - if c > 0: + if parse_version(version) > parse_version(latest_version): self.write(cr, uid, id, {'latest_version': version, 'url': url}) res[0] += 1 @@ -503,8 +397,7 @@ class module(osv.osv): ['published_version'] if published_version == 'x' or not published_version: published_version = '0' - c = vercmp(version, published_version) - if c > 0: + if parse_version(version) > parse_version(published_version): self.write(cr, uid, id, {'published_version': version}) return res @@ -518,7 +411,7 @@ class module(osv.osv): version = '0' if match: version = match.group(1) - if vercmp(mod.installed_version or '0', version) >= 0: + if parse_version(mod.installed_version or '0') >= parse_version(version): continue res.append(mod.url) if not download: diff --git a/bin/addons/base/module/parse_version.py b/bin/addons/base/module/parse_version.py new file mode 100644 index 00000000000..e6a86a0cd46 --- /dev/null +++ b/bin/addons/base/module/parse_version.py @@ -0,0 +1,96 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2008 Tiny SPRL (). All Rights Reserved +# $Id$ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## + +## this functions are taken from the setuptools package (version 0.6c8) +## http://peak.telecommunity.com/DevCenter/PkgResources#parsing-utilities + +import re + +component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE) +replace = {'pre':'c', 'preview':'c','-':'final-','rc':'c','dev':'@'}.get + +def _parse_version_parts(s): + for part in component_re.split(s): + part = replace(part,part) + if not part or part=='.': + continue + if part[:1] in '0123456789': + yield part.zfill(8) # pad for numeric comparison + else: + yield '*'+part + + yield '*final' # ensure that alpha/beta/candidate are before final + +def parse_version(s): + """Convert a version string to a chronologically-sortable key + + This is a rough cross between distutils' StrictVersion and LooseVersion; + if you give it versions that would work with StrictVersion, then it behaves + the same; otherwise it acts like a slightly-smarter LooseVersion. It is + *possible* to create pathological version coding schemes that will fool + this parser, but they should be very rare in practice. + + The returned value will be a tuple of strings. Numeric portions of the + version are padded to 8 digits so they will compare numerically, but + without relying on how numbers compare relative to strings. Dots are + dropped, but dashes are retained. Trailing zeros between alpha segments + or dashes are suppressed, so that e.g. "2.4.0" is considered the same as + "2.4". Alphanumeric parts are lower-cased. + + The algorithm assumes that strings like "-" and any alpha string that + alphabetically follows "final" represents a "patch level". So, "2.4-1" + is assumed to be a branch or patch of "2.4", and therefore "2.4.1" is + considered newer than "2.4-1", whic in turn is newer than "2.4". + + Strings like "a", "b", "c", "alpha", "beta", "candidate" and so on (that + come before "final" alphabetically) are assumed to be pre-release versions, + so that the version "2.4" is considered newer than "2.4a1". + + Finally, to handle miscellaneous cases, the strings "pre", "preview", and + "rc" are treated as if they were "c", i.e. as though they were release + candidates, and therefore are not as new as a version string that does not + contain them. + """ + parts = [] + for part in _parse_version_parts(s.lower()): + if part.startswith('*'): + if part<'*final': # remove '-' before a prerelease tag + while parts and parts[-1]=='*final-': parts.pop() + # remove trailing zeros from each series of numeric parts + while parts and parts[-1]=='00000000': + parts.pop() + parts.append(part) + return tuple(parts) + +if __name__ == '__main__': + pvs = [] + for v in ('0', '4.2', '4.2.3.4', '5.0.0-alpha', '5.0.0-rc1', '5.0.0-rc1.1', '5.0.0'): + pv = parse_version(v) + print v, pv + pvs.append(pv) + + def cmp(a, b): + assert(a < b) + return b + + reduce(cmp, pvs) +