[IMP] Added missing vim mode lines
[odoo/odoo.git] / openerp / tools / parse_version.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #    
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.     
19 #
20 ##############################################################################
21
22 ## this functions are taken from the setuptools package (version 0.6c8)
23 ## http://peak.telecommunity.com/DevCenter/PkgResources#parsing-utilities
24
25 import re
26
27 component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE)
28 replace = {'pre':'c', 'preview':'c','-':'final-','_':'final-','rc':'c','dev':'@'}.get
29
30 def _parse_version_parts(s):
31     for part in component_re.split(s):
32         part = replace(part,part)
33         if not part or part=='.':
34             continue
35         if part[:1] in '0123456789':
36             yield part.zfill(8)    # pad for numeric comparison
37         else:
38             yield '*'+part
39
40     yield '*final'  # ensure that alpha/beta/candidate are before final
41
42 def parse_version(s):
43     """Convert a version string to a chronologically-sortable key
44
45     This is a rough cross between distutils' StrictVersion and LooseVersion;
46     if you give it versions that would work with StrictVersion, then it behaves
47     the same; otherwise it acts like a slightly-smarter LooseVersion. It is
48     *possible* to create pathological version coding schemes that will fool
49     this parser, but they should be very rare in practice.
50
51     The returned value will be a tuple of strings.  Numeric portions of the
52     version are padded to 8 digits so they will compare numerically, but
53     without relying on how numbers compare relative to strings.  Dots are
54     dropped, but dashes are retained.  Trailing zeros between alpha segments
55     or dashes are suppressed, so that e.g. "2.4.0" is considered the same as
56     "2.4". Alphanumeric parts are lower-cased.
57
58     The algorithm assumes that strings like "-" and any alpha string that
59     alphabetically follows "final"  represents a "patch level".  So, "2.4-1"
60     is assumed to be a branch or patch of "2.4", and therefore "2.4.1" is
61     considered newer than "2.4-1", whic in turn is newer than "2.4".
62
63     Strings like "a", "b", "c", "alpha", "beta", "candidate" and so on (that
64     come before "final" alphabetically) are assumed to be pre-release versions,
65     so that the version "2.4" is considered newer than "2.4a1".
66
67     Finally, to handle miscellaneous cases, the strings "pre", "preview", and
68     "rc" are treated as if they were "c", i.e. as though they were release
69     candidates, and therefore are not as new as a version string that does not
70     contain them.
71     """
72     parts = []
73     for part in _parse_version_parts((s or '0.1').lower()):
74         if part.startswith('*'):
75             if part<'*final':   # remove '-' before a prerelease tag
76                 while parts and parts[-1]=='*final-': parts.pop()
77             # remove trailing zeros from each series of numeric parts
78             while parts and parts[-1]=='00000000':
79                 parts.pop()
80         parts.append(part)
81     return tuple(parts)
82
83 if __name__ == '__main__':
84         
85         def cmp(a, b):
86             msg = '%s < %s == %s' % (a, b, a < b)
87             assert a < b, msg
88             return b
89
90         def chk(lst, verbose=False):
91             pvs = []
92             for v in lst:
93                 pv = parse_version(v)
94                 pvs.append(pv)
95                 if verbose:
96                     print v, pv
97             reduce(cmp, pvs)
98         
99         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)
100         chk(('5.0.0-0_rc3', '5.0.0-1dev', '5.0.0-1'), False) 
101         
102
103 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: