[Sugar-devel] [PATCH] Add new numbering scheme #2425

Simon Schampijer simon at schampijer.de
Mon Nov 1 14:03:22 EDT 2010


- add class NormalizedVersion to parse and compare the new activity versions
- change the bundlebuilder and activitybundle to use the new scheme, instead
of an int version we expect a string that matches the format defined in
NormalizedVersion
---
 src/sugar/activity/bundlebuilder.py |    6 +-
 src/sugar/bundle/Makefile.am        |    1 +
 src/sugar/bundle/activitybundle.py  |   14 ++--
 src/sugar/bundle/bundleversion.py   |  157 +++++++++++++++++++++++++++++++++++
 4 files changed, 169 insertions(+), 9 deletions(-)
 create mode 100644 src/sugar/bundle/bundleversion.py

diff --git a/src/sugar/activity/bundlebuilder.py b/src/sugar/activity/bundlebuilder.py
index fc8ebc8..d9dd96d 100644
--- a/src/sugar/activity/bundlebuilder.py
+++ b/src/sugar/activity/bundlebuilder.py
@@ -86,13 +86,13 @@ class Config(object):
         self.bundle_id = bundle.get_bundle_id()
         self.bundle_name = reduce(lambda x, y: x+y, self.activity_name.split())
         self.bundle_root_dir = self.bundle_name + '.activity'
-        self.tar_root_dir = '%s-%d' % (self.bundle_name, self.version)
+        self.tar_root_dir = '%s-%s' % (self.bundle_name, self.version)
 
         if self.dist_name:
             self.xo_name = self.tar_name = self.dist_name
         else:
-            self.xo_name = '%s-%d.xo' % (self.bundle_name, self.version)
-            self.tar_name = '%s-%d.tar.bz2' % (self.bundle_name, self.version)
+            self.xo_name = '%s-%s.xo' % (self.bundle_name, self.version)
+            self.tar_name = '%s-%s.tar.bz2' % (self.bundle_name, self.version)
 
 
 class Builder(object):
diff --git a/src/sugar/bundle/Makefile.am b/src/sugar/bundle/Makefile.am
index f1af791..50c93de 100644
--- a/src/sugar/bundle/Makefile.am
+++ b/src/sugar/bundle/Makefile.am
@@ -3,4 +3,5 @@ sugar_PYTHON =				\
 	__init__.py			\
 	bundle.py			\
 	activitybundle.py		\
+	bundleversion.py		\
 	contentbundle.py
diff --git a/src/sugar/bundle/activitybundle.py b/src/sugar/bundle/activitybundle.py
index 3bbc250..f70afaa 100644
--- a/src/sugar/bundle/activitybundle.py
+++ b/src/sugar/bundle/activitybundle.py
@@ -32,7 +32,8 @@ from sugar import env
 from sugar import util
 from sugar.bundle.bundle import Bundle, \
     MalformedBundleException, NotInstalledException
-
+from sugar.bundle.bundleversion import NormalizedVersion
+from sugar.bundle.bundleversion import InvalidVersionError
 
 class ActivityBundle(Bundle):
     """A Sugar activity bundle
@@ -59,7 +60,7 @@ class ActivityBundle(Bundle):
         self._mime_types = None
         self._show_launcher = True
         self._tags = None
-        self._activity_version = 0
+        self._activity_version = '0'
         self._installation_time = os.stat(path).st_mtime
         self._manifest = None
 
@@ -197,11 +198,12 @@ class ActivityBundle(Bundle):
         if cp.has_option(section, 'activity_version'):
             version = cp.get(section, 'activity_version')
             try:
-                self._activity_version = int(version)
-            except ValueError:
-                raise MalformedBundleException(
-                    'Activity bundle %s has invalid version number %s' %
+                NormalizedVersion(version)
+            except InvalidVersionError:
+                raise MalformedBundleException( 
+                    'Activity bundle %s has invalid version number %s' % 
                     (self._path, version))
+            self._activity_version = version
 
     def _get_linfo_file(self):
         lang = locale.getdefaultlocale()[0]
diff --git a/src/sugar/bundle/bundleversion.py b/src/sugar/bundle/bundleversion.py
new file mode 100644
index 0000000..e763231
--- /dev/null
+++ b/src/sugar/bundle/bundleversion.py
@@ -0,0 +1,157 @@
+# Copyright (C) 2010, OLPC
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+#
+# Based on the implementation of PEP 386, but adapted to our
+# numeration schema.
+#
+
+import re
+
+
+class InvalidVersionError(Exception):
+    """The passed activity version can not be normalized."""
+    pass
+
+VERSION_RE = re.compile(r'''
+    ^
+    (?P<version>\d+)               # minimum 'N'
+    (?P<extraversion>(?:\.\d+)*)   # any number of extra '.N' segments
+    (?:
+    (?P<local>\-[a-zA-Z]*)         # ignore any string in the comparison
+    )?
+    $''', re.VERBOSE)
+
+
+class NormalizedVersion(object):
+    """A normalized version.
+
+    Good:
+        1
+        1.2
+        1.2.3
+        1.2.3-peru
+
+    Bad:
+        1.2peru        # must be separated with -
+        1.2.           # can't end with '.'
+        1.02.5         # can't have a leading zero
+
+    """
+
+    def __init__(self, activity_version):
+        """Create a NormalizedVersion instance from a version string.
+
+        Keyword arguments:
+        activity_version -- The version string
+
+        """
+        self._activity_version = activity_version
+        self.parts = []
+        self._local = None
+
+        if not isinstance(self._activity_version, str):
+            raise InvalidVersionError(self._activity_version)
+
+        match = VERSION_RE.search(self._activity_version)
+        if not match:
+            raise InvalidVersionError(self._activity_version)
+
+        groups = match.groupdict()
+
+        version = self._parse_version(groups['version'])
+        self.parts.append(version)
+
+        if groups['extraversion'] not in ('', None):
+            versions = self._parse_extraversions(groups['extraversion'][1:])
+            self.parts.extend(versions)
+
+        self._local = groups['local']
+
+    def _parse_version(self, version_string):
+        """Verify that there is no leading zero and convert to integer.
+
+        Keyword arguments:
+        version -- string to be parsed
+
+        Return: Version
+
+        """
+        if len(version_string) > 1 and version_string[0] == '0':
+            raise InvalidVersionError("Can not have leading zero in segment"
+                                      " %s in %r" % (version_string,
+                                      self._activity_version))
+
+        return int(version_string)
+
+    def _parse_extraversions(self, extraversion_string):
+        """Split into N versions and convert them to integers, verify
+        that there are no leading zeros and drop trailing zeros.
+
+        Keyword arguments:
+        extraversion -- 'N.N.N...' sequence to be parsed
+
+        Return: List of extra versions
+
+        """
+        nums = []
+        for n in extraversion_string.split("."):
+            if len(n) > 1 and n[0] == '0':
+                raise InvalidVersionError("Can not have leading zero in "
+                                          "segment %s in %r" % (n,
+                                          self._activity_version))
+            nums.append(int(n))
+
+        while nums and nums[-1] == 0:
+            nums.pop()
+
+        return nums
+
+    def __str__(self):
+        version_string = '.'.join(str(v) for v in self.parts)
+        if self._local != None:
+            version_string += self._local
+        return version_string
+
+    def __repr__(self):
+        return "%s('%s')" % (self.__class__.__name__, self)
+
+    def _cannot_compare(self, other):
+        raise TypeError("Can not compare %s and %s"
+                % (type(self).__name__, type(other).__name__))
+
+    def __eq__(self, other):
+        if not isinstance(other, NormalizedVersion):
+            self._cannot_compare(other)
+        return self.parts == other.parts
+
+    def __lt__(self, other):
+        if not isinstance(other, NormalizedVersion):
+            self._cannot_compare(other)
+        return self.parts < other.parts
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __gt__(self, other):
+        return not (self.__lt__(other) or self.__eq__(other))
+
+    def __le__(self, other):
+        return self.__eq__(other) or self.__lt__(other)
+
+    def __ge__(self, other):
+        return self.__eq__(other) or self.__gt__(other)
-- 
1.7.2.3



More information about the Sugar-devel mailing list