vFW and vDNS support added to azure-plugin
[multicloud/azure.git] / azure / aria / aria-extension-cloudify / src / aria / aria / utils / versions.py
1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements.  See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License.  You may obtain a copy of the License at
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 """
17 Verion string utilities.
18 """
19
20 import re
21
22
23 _INF = float('inf')
24
25 _NULL = (), _INF
26
27 _DIGITS_RE = re.compile(r'^\d+$')
28
29 _PREFIXES = {
30     'dev':   0.0001,
31     'alpha': 0.001,
32     'beta':  0.01,
33     'rc':    0.1
34 }
35
36
37 class VersionString(unicode):
38     """
39     Version string that can be compared, sorted, made unique in a set, and used as a unique dict
40     key.
41
42     The primary part of the string is one or more dot-separated natural numbers. Trailing zeroes
43     are treated as redundant, e.g. "1.0.0" == "1.0" == "1".
44
45     An optional qualifier can be added after a "-". The qualifier can be a natural number or a
46     specially treated prefixed natural number, e.g. "1.1-beta1" > "1.1-alpha2". The case of the
47     prefix is ignored.
48
49     Numeric qualifiers will always be greater than prefixed integer qualifiers, e.g. "1.1-1" >
50     "1.1-beta1".
51
52     Versions without a qualifier will always be greater than their equivalents with a qualifier,
53     e.g. e.g. "1.1" > "1.1-1".
54
55     Any value that does not conform to this format will be treated as a zero version, which would
56     be lesser than any non-zero version.
57
58     For efficient list sorts use the ``key`` property, e.g.::
59
60         sorted(versions, key=lambda x: x.key)
61     """
62
63     NULL = None # initialized below
64
65     def __init__(self, value=None):
66         if value is not None:
67             super(VersionString, self).__init__(value)
68         self.key = parse_version_string(self)
69
70     def __eq__(self, version):
71         if not isinstance(version, VersionString):
72             version = VersionString(version)
73         return self.key == version.key
74
75     def __lt__(self, version):
76         if not isinstance(version, VersionString):
77             version = VersionString(version)
78         return self.key < version.key
79
80     def __hash__(self):
81         return self.key.__hash__()
82
83
84 def parse_version_string(version): # pylint: disable=too-many-branches
85     """
86     Parses a version string.
87
88     :param version: version string
89     :returns: primary tuple and qualifier float
90     :rtype: ((:obj:`int`), :obj:`float`)
91     """
92
93     if version is None:
94         return _NULL
95     version = unicode(version)
96
97     # Split to primary and qualifier on '-'
98     split = version.split('-', 1)
99     if len(split) == 2:
100         primary, qualifier = split
101     else:
102         primary = split[0]
103         qualifier = None
104
105     # Parse primary
106     split = primary.split('.')
107     primary = []
108     for element in split:
109         if _DIGITS_RE.match(element) is None:
110             # Invalid version string
111             return _NULL
112         try:
113             element = int(element)
114         except ValueError:
115             # Invalid version string
116             return _NULL
117         primary.append(element)
118
119     # Remove redundant zeros
120     for element in reversed(primary):
121         if element == 0:
122             primary.pop()
123         else:
124             break
125     primary = tuple(primary)
126
127     # Parse qualifier
128     if qualifier is not None:
129         if _DIGITS_RE.match(qualifier) is not None:
130             # Integer qualifier
131             try:
132                 qualifier = float(int(qualifier))
133             except ValueError:
134                 # Invalid version string
135                 return _NULL
136         else:
137             # Prefixed integer qualifier
138             value = None
139             qualifier = qualifier.lower()
140             for prefix, factor in _PREFIXES.iteritems():
141                 if qualifier.startswith(prefix):
142                     value = qualifier[len(prefix):]
143                     if _DIGITS_RE.match(value) is None:
144                         # Invalid version string
145                         return _NULL
146                     try:
147                         value = float(int(value)) * factor
148                     except ValueError:
149                         # Invalid version string
150                         return _NULL
151                     break
152             if value is None:
153                 # Invalid version string
154                 return _NULL
155             qualifier = value
156     else:
157         # Version strings with no qualifiers are higher
158         qualifier = _INF
159
160     return primary, qualifier
161
162
163 VersionString.NULL = VersionString()