1# -*- coding: utf-8 -*-
2# Part of Odoo. See LICENSE file for full copyright and licensing details.
3
4## this functions are taken from the setuptools package (version 0.6c8)
5## http://peak.telecommunity.com/DevCenter/PkgResources#parsing-utilities
6
7from __future__ import print_function
8import re
9
10component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE)
11replace = {'pre':'c', 'preview':'c','-':'final-','_':'final-','rc':'c','dev':'@','saas':'','~':''}.get
12
13def _parse_version_parts(s):
14    for part in component_re.split(s):
15        part = replace(part,part)
16        if not part or part=='.':
17            continue
18        if part[:1] in '0123456789':
19            yield part.zfill(8)    # pad for numeric comparison
20        else:
21            yield '*'+part
22
23    yield '*final'  # ensure that alpha/beta/candidate are before final
24
25def parse_version(s):
26    """Convert a version string to a chronologically-sortable key
27
28    This is a rough cross between distutils' StrictVersion and LooseVersion;
29    if you give it versions that would work with StrictVersion, then it behaves
30    the same; otherwise it acts like a slightly-smarter LooseVersion. It is
31    *possible* to create pathological version coding schemes that will fool
32    this parser, but they should be very rare in practice.
33
34    The returned value will be a tuple of strings.  Numeric portions of the
35    version are padded to 8 digits so they will compare numerically, but
36    without relying on how numbers compare relative to strings.  Dots are
37    dropped, but dashes are retained.  Trailing zeros between alpha segments
38    or dashes are suppressed, so that e.g. "2.4.0" is considered the same as
39    "2.4". Alphanumeric parts are lower-cased.
40
41    The algorithm assumes that strings like "-" and any alpha string that
42    alphabetically follows "final"  represents a "patch level".  So, "2.4-1"
43    is assumed to be a branch or patch of "2.4", and therefore "2.4.1" is
44    considered newer than "2.4-1", which in turn is newer than "2.4".
45
46    Strings like "a", "b", "c", "alpha", "beta", "candidate" and so on (that
47    come before "final" alphabetically) are assumed to be pre-release versions,
48    so that the version "2.4" is considered newer than "2.4a1".
49
50    Finally, to handle miscellaneous cases, the strings "pre", "preview", and
51    "rc" are treated as if they were "c", i.e. as though they were release
52    candidates, and therefore are not as new as a version string that does not
53    contain them.
54    """
55    parts = []
56    for part in _parse_version_parts((s or '0.1').lower()):
57        if part.startswith('*'):
58            if part<'*final':   # remove '-' before a prerelease tag
59                while parts and parts[-1]=='*final-': parts.pop()
60            # remove trailing zeros from each series of numeric parts
61            while parts and parts[-1]=='00000000':
62                parts.pop()
63        parts.append(part)
64    return tuple(parts)
65
66if __name__ == '__main__':
67        def chk(lst, verbose=False):
68            pvs = []
69            for v in lst:
70                pv = parse_version(v)
71                pvs.append(pv)
72                if verbose:
73                    print(v, pv)
74
75            for a, b in zip(pvs, pvs[1:]):
76                assert a < b, '%s < %s == %s' % (a, b, a < b)
77
78        chk(('0', '4.2', '4.2.3.4', '5.0.0-alpha', '5.0.0-rc1', '5.0.0-rc1.1', '5.0.0_rc2', '5.0.0_rc3', '5.0.0'), False)
79        chk(('5.0.0-0_rc3', '5.0.0-1dev', '5.0.0-1'), False)
80
81