Ticket #2425: 0001-Add-class-to-parse-and-compare-the-bundles-version-n.patch

File 0001-Add-class-to-parse-and-compare-the-bundles-version-n.patch, 8.6 KB (added by godiard, 14 years ago)
  • src/sugar/bundle/Makefile.am

    From d5aeffc702e62916e3af783fa26996172e8ef778 Mon Sep 17 00:00:00 2001
    From: Gonzalo Odiard <gonzalo@f11-sugar-devel.(none)>
    Date: Wed, 29 Sep 2010 16:31:37 -0300
    Subject: [PATCH 1/2] Add class to parse and compare the bundles version numbers
    
    It's bassed in PEP386 implementation but adapted to our nuber schema.
    ---
     src/sugar/bundle/Makefile.am      |    1 +
     src/sugar/bundle/bundleversion.py |  154 +++++++++++++++++++++++++++++++++++++
     tests/lib/test_versions.py        |   65 ++++++++++++++++
     3 files changed, 220 insertions(+), 0 deletions(-)
     create mode 100644 src/sugar/bundle/bundleversion.py
     create mode 100644 tests/lib/test_versions.py
    
    diff --git a/src/sugar/bundle/Makefile.am b/src/sugar/bundle/Makefile.am
    index f1af791..50c93de 100644
    a b sugar_PYTHON = \ 
    33        __init__.py                     \
    44        bundle.py                       \
    55        activitybundle.py               \
     6        bundleversion.py                \
    67        contentbundle.py
  • new file src/sugar/bundle/bundleversion.py

    diff --git a/src/sugar/bundle/bundleversion.py b/src/sugar/bundle/bundleversion.py
    new file mode 100644
    index 0000000..166356d
    - +  
     1# Copyright (C) 2010, OLPC
     2#
     3# This library is free software; you can redistribute it and/or
     4# modify it under the terms of the GNU Lesser General Public
     5# License as published by the Free Software Foundation; either
     6# version 2 of the License, or (at your option) any later version.
     7#
     8# This library is distributed in the hope that it will be useful,
     9# but WITHOUT ANY WARRANTY; without even the implied warranty of
     10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     11# Lesser General Public License for more details.
     12#
     13# You should have received a copy of the GNU Lesser General Public
     14# License along with this library; if not, write to the
     15# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
     16# Boston, MA 02111-1307, USA.
     17
     18#
     19# Based in the implementation of PEP 386, but adapted to our numeration schema
     20#
     21
     22import re
     23
     24class InvalidVersionError(Exception):
     25    """This is an not normalized version."""
     26    pass
     27
     28VERSION_RE = re.compile(r'''
     29    ^
     30    (?P<version>\d+)               # minimum 'N'
     31    (?P<extraversion>(?:\.\d+)*)   # any number of extra '.N' segments
     32    (?:
     33    (?P<local>\-[a-zA-Z]*)         # any string, will be ignored in the comparison
     34    )?
     35    $''', re.VERBOSE)
     36
     37class NormalizedVersion(object):
     38    """A normalized version.
     39
     40    Good:
     41        1
     42        1.2
     43        1.2.3
     44        1.2.3-peru
     45
     46    Bad:
     47        1.2peru        # must separate with -
     48        1.2.           # can't end with .
     49        1.02.5         # can't have a leading zero
     50    """
     51    def __init__(self, s, error_on_huge_major_num=True):
     52        """Create a NormalizedVersion instance from a version string.
     53
     54        @param s {str} The version string.
     55        @param error_on_huge_major_num {bool} Whether to consider an
     56            apparent use of a year or full date as the major version number
     57            an error. Default True. One of the observed patterns on PyPI before
     58            the introduction of `NormalizedVersion` was version numbers like this:
     59                2009.01.03
     60                20040603
     61                2005.01
     62            This guard is here to strongly encourage the package author to
     63            use an alternate version, because a release deployed into PyPI
     64            and, e.g. downstream Linux package managers, will forever remove
     65            the possibility of using a version number like "1.0" (i.e.
     66            where the major number is less than that huge major number).
     67        """
     68        match = VERSION_RE.search(s)
     69        if not match:
     70            raise InvalidVersionError(s)
     71
     72        groups = match.groupdict()
     73        parts = []
     74
     75        # main version
     76        block = self._parse_numdots(groups['version'], s, False, 1)
     77        extraversion = groups.get('extraversion')
     78        if extraversion not in ('', None):
     79            block += self._parse_numdots(extraversion[1:], s)
     80        parts.extend(block)
     81        self._parts = parts
     82        self._local = groups['local']
     83
     84    def _parse_numdots(self, s, full_ver_str, drop_trailing_zeros=True,
     85                       pad_zeros_length=0):
     86        """Parse 'N.N.N' sequences, return a list of ints.
     87
     88        @param s {str} 'N.N.N..." sequence to be parsed
     89        @param full_ver_str {str} The full version string from which this
     90            comes. Used for error strings.
     91        @param drop_trailing_zeros {bool} Whether to drop trailing zeros
     92            from the returned list. Default True.
     93        @param pad_zeros_length {int} The length to which to pad the
     94            returned list with zeros, if necessary. Default 0.
     95        """
     96        nums = []
     97        for n in s.split("."):
     98            if len(n) > 1 and n[0] == '0':
     99                raise InvalidVersionError("cannot have leading zero in "
     100                    "version number segment: '%s' in %r" % (n, full_ver_str))
     101            nums.append(int(n))
     102        if drop_trailing_zeros:
     103            while nums and nums[-1] == 0:
     104                nums.pop()
     105        while len(nums) < pad_zeros_length:
     106            nums.append(0)
     107        return nums
     108
     109    def get_parts(self):
     110        return self._parts
     111
     112    def __str__(self):
     113        s = self._parts_to_str(self._parts)
     114        if self._local != None:
     115            s = s + self._local
     116        return s
     117
     118    @classmethod
     119    def _parts_to_str(cls, parts):
     120        """Transforms a version expressed in tuple into its string
     121        representation."""
     122        main = parts
     123        s = '.'.join(str(v) for v in main)
     124        return s
     125
     126    def __repr__(self):
     127        return "%s('%s')" % (self.__class__.__name__, self)
     128
     129    def _cannot_compare(self, other):
     130        raise TypeError("cannot compare %s and %s"
     131                % (type(self).__name__, type(other).__name__))
     132
     133    def __eq__(self, other):
     134        if not isinstance(other, NormalizedVersion):
     135            self._cannot_compare(other)
     136        return self._parts == other.get_parts()
     137
     138    def __lt__(self, other):
     139        if not isinstance(other, NormalizedVersion):
     140            self._cannot_compare(other)
     141        return self._parts < other.get_parts()
     142
     143    def __ne__(self, other):
     144        return not self.__eq__(other)
     145
     146    def __gt__(self, other):
     147        return not (self.__lt__(other) or self.__eq__(other))
     148
     149    def __le__(self, other):
     150        return self.__eq__(other) or self.__lt__(other)
     151
     152    def __ge__(self, other):
     153        return self.__eq__(other) or self.__gt__(other)
     154
  • new file tests/lib/test_versions.py

    diff --git a/tests/lib/test_versions.py b/tests/lib/test_versions.py
    new file mode 100644
    index 0000000..2e43e35
    - +  
     1"""Tests for sugar.bundle.version."""
     2import unittest
     3import doctest
     4import os
     5
     6from sugar.bundle.bundleversion import NormalizedVersion as V
     7from sugar.bundle.bundleversion import InvalidVersionError
     8
     9class VersionTestCase(unittest.TestCase):
     10
     11    versions = ((V('1.0'), '1'),
     12                (V('1.1'), '1.1'),
     13                (V('1.2.3'), '1.2.3'),
     14                (V('1.2'), '1.2'),
     15                (V('1.2.3'), '1.2.3'),
     16                (V('1.2-peru'), '1.2-peru'),
     17                (V('1.2.0.0.0'), '1.2'))
     18
     19    def test_basic_versions(self):
     20
     21        for v, s in self.versions:
     22            self.assertEquals(str(v), s)
     23
     24
     25    def test_not_normalized_versions(self):
     26
     27        irrational = ('1.', '1.2arg', '1-2-3','1.02.5')
     28
     29        for s in irrational:
     30            self.assertRaises(InvalidVersionError, V, s)
     31
     32    def test_comparison(self):
     33        """
     34        >>> V('1.2.0') == '1.2'
     35        Traceback (most recent call last):
     36        ...
     37        TypeError: cannot compare NormalizedVersion and str
     38        >>> V('12') == V('12')
     39        True
     40        >>> V('1.2.0') == V('1.2')
     41        True
     42        >>> V('1.2.0') == V('1.2.3')
     43        False
     44        >>> V('1.2.0') < V('1.2.3')
     45        True
     46        >>> V('1.2-arg') == V('1.2')
     47        True
     48        >>> V('1.2-arg') == V('1.2-peru')
     49        True
     50        >>> V('1.2') < V('1.2.1')
     51        True
     52        """
     53        # must be a simpler way to call the docstrings
     54        doctest.run_docstring_examples(self.test_comparison, globals(),
     55                                       name='test_comparison')
     56
     57def test_suite():
     58    #README = os.path.join(os.path.dirname(__file__), 'README.txt')
     59    #suite = [doctest.DocFileSuite(README), unittest.makeSuite(VersionTestCase)]
     60    suite = [unittest.makeSuite(VersionTestCase)]
     61    return unittest.TestSuite(suite)
     62
     63if __name__ == "__main__":
     64    unittest.main(defaultTest="test_suite")
     65