vFW and vDNS support added to azure-plugin
[multicloud/azure.git] / azure / aria / aria-extension-cloudify / src / aria / aria / utils / versions.py
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/utils/versions.py b/azure/aria/aria-extension-cloudify/src/aria/aria/utils/versions.py
new file mode 100644 (file)
index 0000000..521004c
--- /dev/null
@@ -0,0 +1,163 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Verion string utilities.
+"""
+
+import re
+
+
+_INF = float('inf')
+
+_NULL = (), _INF
+
+_DIGITS_RE = re.compile(r'^\d+$')
+
+_PREFIXES = {
+    'dev':   0.0001,
+    'alpha': 0.001,
+    'beta':  0.01,
+    'rc':    0.1
+}
+
+
+class VersionString(unicode):
+    """
+    Version string that can be compared, sorted, made unique in a set, and used as a unique dict
+    key.
+
+    The primary part of the string is one or more dot-separated natural numbers. Trailing zeroes
+    are treated as redundant, e.g. "1.0.0" == "1.0" == "1".
+
+    An optional qualifier can be added after a "-". The qualifier can be a natural number or a
+    specially treated prefixed natural number, e.g. "1.1-beta1" > "1.1-alpha2". The case of the
+    prefix is ignored.
+
+    Numeric qualifiers will always be greater than prefixed integer qualifiers, e.g. "1.1-1" >
+    "1.1-beta1".
+
+    Versions without a qualifier will always be greater than their equivalents with a qualifier,
+    e.g. e.g. "1.1" > "1.1-1".
+
+    Any value that does not conform to this format will be treated as a zero version, which would
+    be lesser than any non-zero version.
+
+    For efficient list sorts use the ``key`` property, e.g.::
+
+        sorted(versions, key=lambda x: x.key)
+    """
+
+    NULL = None # initialized below
+
+    def __init__(self, value=None):
+        if value is not None:
+            super(VersionString, self).__init__(value)
+        self.key = parse_version_string(self)
+
+    def __eq__(self, version):
+        if not isinstance(version, VersionString):
+            version = VersionString(version)
+        return self.key == version.key
+
+    def __lt__(self, version):
+        if not isinstance(version, VersionString):
+            version = VersionString(version)
+        return self.key < version.key
+
+    def __hash__(self):
+        return self.key.__hash__()
+
+
+def parse_version_string(version): # pylint: disable=too-many-branches
+    """
+    Parses a version string.
+
+    :param version: version string
+    :returns: primary tuple and qualifier float
+    :rtype: ((:obj:`int`), :obj:`float`)
+    """
+
+    if version is None:
+        return _NULL
+    version = unicode(version)
+
+    # Split to primary and qualifier on '-'
+    split = version.split('-', 1)
+    if len(split) == 2:
+        primary, qualifier = split
+    else:
+        primary = split[0]
+        qualifier = None
+
+    # Parse primary
+    split = primary.split('.')
+    primary = []
+    for element in split:
+        if _DIGITS_RE.match(element) is None:
+            # Invalid version string
+            return _NULL
+        try:
+            element = int(element)
+        except ValueError:
+            # Invalid version string
+            return _NULL
+        primary.append(element)
+
+    # Remove redundant zeros
+    for element in reversed(primary):
+        if element == 0:
+            primary.pop()
+        else:
+            break
+    primary = tuple(primary)
+
+    # Parse qualifier
+    if qualifier is not None:
+        if _DIGITS_RE.match(qualifier) is not None:
+            # Integer qualifier
+            try:
+                qualifier = float(int(qualifier))
+            except ValueError:
+                # Invalid version string
+                return _NULL
+        else:
+            # Prefixed integer qualifier
+            value = None
+            qualifier = qualifier.lower()
+            for prefix, factor in _PREFIXES.iteritems():
+                if qualifier.startswith(prefix):
+                    value = qualifier[len(prefix):]
+                    if _DIGITS_RE.match(value) is None:
+                        # Invalid version string
+                        return _NULL
+                    try:
+                        value = float(int(value)) * factor
+                    except ValueError:
+                        # Invalid version string
+                        return _NULL
+                    break
+            if value is None:
+                # Invalid version string
+                return _NULL
+            qualifier = value
+    else:
+        # Version strings with no qualifiers are higher
+        qualifier = _INF
+
+    return primary, qualifier
+
+
+VersionString.NULL = VersionString()