1 """Functions for processing version numbers.
2 @since: 1.13
3 """
4
5
6
7
8 import re
9
10 from zeroinstall import SafeException, _
11
12 _version_mod_to_value = {
13 'pre': -2,
14 'rc': -1,
15 '': 0,
16 'post': 1,
17 }
18
19
20 _version_value_to_mod = {}
21 for x in _version_mod_to_value: _version_value_to_mod[_version_mod_to_value[x]] = x
22 del x
23
24 _version_re = re.compile('-([a-z]*)')
25
27 """Convert a version string to an internal representation.
28 The parsed format can be compared quickly using the standard Python functions.
29 - Version := DottedList ("-" Mod DottedList?)*
30 - DottedList := (Integer ("." Integer)*)
31 @type version_string: str
32 @rtype: tuple (opaque)
33 @raise SafeException: if the string isn't a valid version
34 @since: 0.24 (moved from L{reader}, from where it is still available):"""
35 if version_string is None: return None
36 parts = _version_re.split(version_string)
37 if parts[-1] == '':
38 del parts[-1]
39 else:
40 parts.append('')
41 if not parts:
42 raise SafeException(_("Empty version string!"))
43 l = len(parts)
44 try:
45 for x in range(0, l, 2):
46 part = parts[x]
47 if part:
48 parts[x] = list(map(int, parts[x].split('.')))
49 else:
50 parts[x] = []
51 for x in range(1, l, 2):
52 parts[x] = _version_mod_to_value[parts[x]]
53 return parts
54 except ValueError as ex:
55 raise SafeException(_("Invalid version format in '%(version_string)s': %(exception)s") % {'version_string': version_string, 'exception': ex})
56 except KeyError as ex:
57 raise SafeException(_("Invalid version modifier in '%(version_string)s': %(exception)s") % {'version_string': version_string, 'exception': str(ex).strip("u")})
58
72
73
75 """Parse a range expression.
76 @param r: the range expression
77 @type r: str
78 @return: a function which returns whether a parsed version is in the range
79 @rtype: parsed_version -> bool
80 @since: 1.13"""
81 parts = r.split('..', 1)
82 if len(parts) == 1:
83 if r.startswith('!'):
84 v = parse_version(r[1:])
85 return lambda x: x != v
86 else:
87 v = parse_version(r)
88 return lambda x: x == v
89
90 start, end = parts
91 if start:
92 start = parse_version(start)
93 else:
94 start = None
95 if end:
96 if not end.startswith('!'):
97 raise SafeException("End of range must be exclusive (use '..!{end}', not '..{end}')".format(end = end))
98 end = parse_version(end[1:])
99 else:
100 end = None
101
102 def test(v):
103 if start is not None and v < start: return False
104 if end is not None and v >= end: return False
105 return True
106
107 return test
108
110 """Parse an expression of the form "RANGE | RANGE | ...".
111 @param expr: the expression to parse
112 @type expr: str
113 @return: a function which tests whether a parsed version is in the range
114 @rtype: parsed_version -> bool
115 @since: 1.13"""
116 tests = [parse_version_range(r.strip()) for r in expr.split('|')]
117 return lambda v: any(test(v) for test in tests)
118