diff options
-rw-r--r-- | doc/reference.rst | 8 | ||||
-rw-r--r-- | src/semantic_version/base.py | 41 | ||||
-rwxr-xr-x | tests/test_base.py | 102 | ||||
-rw-r--r-- | tests/test_django.py | 38 | ||||
-rwxr-xr-x | tests/test_match.py | 28 |
5 files changed, 99 insertions, 118 deletions
diff --git a/doc/reference.rst b/doc/reference.rst index 698aa93..6882988 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -236,19 +236,13 @@ rules apply: True * Setting a build separator without a build identifier (``>1.1.1+``) forces - satisfaction tests to include build identifiers:: + satisfaction tests to include both prerelease and build identifiers:: >>> Version('1.1.1+build2') in Spec('>1.1.1') False >>> Version('1.1.1+build2') in Spec('>1.1.1+') True -* Including both pre-release and build separators while omitting identifiers is - strictly equivalent to including only the build separator:: - - >>> Spec('>1.1.1-+') == Spec('>1.1.1+') - True - .. class:: Spec(spec_string) Stores a version specification, defined from a string:: diff --git a/src/semantic_version/base.py b/src/semantic_version/base.py index bec1a79..7b26981 100644 --- a/src/semantic_version/base.py +++ b/src/semantic_version/base.py @@ -58,7 +58,7 @@ def identifier_list_cmp(a, b): class Version(object): version_re = re.compile('^(\d+)\.(\d+)\.(\d+)(?:-([0-9a-zA-Z.-]+))?(?:\+([0-9a-zA-Z.-]+))?$') - partial_version_re = re.compile('^(\d+)(?:\.(\d+)(?:\.(\d+)(?:-([0-9a-zA-Z.-]+))?(?:\+([0-9a-zA-Z.-]+))?)?)?$') + partial_version_re = re.compile('^(\d+)\.(\d+)\.(\d+)(?:-([0-9a-zA-Z.-]*))?(?:\+([0-9a-zA-Z.-]*))?$') def __init__(self, version_string, partial=False): major, minor, patch, prerelease, build = self.parse(version_string, partial) @@ -100,11 +100,13 @@ class Version(object): patch = int(patch) if prerelease is None: - if partial and not build: + if partial and (build is None): # No build info, strip here return (major, minor, patch, None, None) else: prerelease = () + elif prerelease == '': + prerelease = () else: prerelease = tuple(prerelease.split('.')) @@ -113,6 +115,8 @@ class Version(object): build = None else: build = () + elif build == '': + build = () else: build = tuple(build.split('.')) @@ -122,16 +126,11 @@ class Version(object): return iter((self.major, self.minor, self.patch, self.prerelease, self.build)) def __str__(self): - if self.minor is None: - return '%d' % self.major - elif self.patch is None: - return '%d.%d' % (self.major, self.minor) - version = '%d.%d.%d' % (self.major, self.minor, self.patch) - if self.prerelease: + if self.prerelease or (self.partial and self.prerelease == () and self.build is None): version = '%s-%s' % (version, '.'.join(self.prerelease)) - if self.build: + if self.build or (self.partial and self.build == ()): version = '%s+%s' % (version, '.'.join(self.build)) return version @@ -255,19 +254,7 @@ class Spec(object): KIND_NEQ, ) - KIND_LTE_LOOSE = '<~' - KIND_EQ_LOOSE = '~=' - KIND_GTE_LOOSE = '>~' - KIND_NEQ_LOOSE = '!~' - - LOOSE_KINDS = ( - KIND_LTE_LOOSE, - KIND_EQ_LOOSE, - KIND_GTE_LOOSE, - KIND_NEQ_LOOSE, - ) - - re_spec = re.compile(r'^(<|<=|==|>=|>|!=|<~|>~|~=|!~)(\d.*)$') + re_spec = re.compile(r'^(<|<=|==|>=|>|!=)(\d.*)$') def __init__(self, requirement_string): kind, spec = self.parse(requirement_string) @@ -284,21 +271,21 @@ class Spec(object): raise ValueError("Invalid requirement specification: %r" % requirement_string) kind, version = match.groups() - spec = Version(version, partial=(kind in cls.LOOSE_KINDS)) + spec = Version(version, partial=True) return (kind, spec) def match(self, version): if self.kind == self.KIND_LT: return version < self.spec - elif self.kind in (self.KIND_LTE, self.KIND_LTE_LOOSE): + elif self.kind == self.KIND_LTE: return version <= self.spec - elif self.kind in (self.KIND_EQUAL, self.KIND_EQ_LOOSE): + elif self.kind == self.KIND_EQUAL: return version == self.spec - elif self.kind in (self.KIND_GTE, self.KIND_GTE_LOOSE): + elif self.kind == self.KIND_GTE: return version >= self.spec elif self.kind == self.KIND_GT: return version > self.spec - elif self.kind in (self.KIND_NEQ, self.KIND_NEQ_LOOSE): + elif self.kind == self.KIND_NEQ: return version != self.spec else: # pragma: no cover raise ValueError('Unexpected match kind: %r' % self.kind) diff --git a/tests/test_base.py b/tests/test_base.py index 894c4c3..49133c1 100755 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -101,22 +101,22 @@ class VersionTestCase(unittest.TestCase): self.assertNotEqual(text, base.Version(text)) partial_versions = { - '1.0': (1, 0, None, None, None), - '1': (1, None, None, None, None), '1.0.0-alpha': (1, 0, 0, ('alpha',), None), '1.0.0-alpha.1': (1, 0, 0, ('alpha', '1'), None), '1.0.0-beta.2': (1, 0, 0, ('beta', '2'), None), '1.0.0-beta.11': (1, 0, 0, ('beta', '11'), None), '1.0.0-rc.1': (1, 0, 0, ('rc', '1'), None), - '1.0.0-rc.1+build.1': (1, 0, 0, ('rc', '1'), ('build', '1')), '1.0.0': (1, 0, 0, None, None), + '1.1.1': (1, 1, 1, None, None), + '1.1.2': (1, 1, 2, None, None), + '1.1.3-rc4.5': (1, 1, 3, ('rc4', '5'), None), + '1.0.0-': (1, 0, 0, (), None), + '1.0.0+': (1, 0, 0, (), ()), + '1.0.0-rc.1+build.1': (1, 0, 0, ('rc', '1'), ('build', '1')), '1.0.0+0.3.7': (1, 0, 0, (), ('0', '3', '7')), '1.3.7+build': (1, 3, 7, (), ('build',)), '1.3.7+build.2.b8f12d7': (1, 3, 7, (), ('build', '2', 'b8f12d7')), '1.3.7+build.11.e0f985a': (1, 3, 7, (), ('build', '11', 'e0f985a')), - '1.1.1': (1, 1, 1, None, None), - '1.1.2': (1, 1, 2, None, None), - '1.1.3-rc4.5': (1, 1, 3, ('rc4', '5'), None), '1.1.3-rc42.3-14-15.24+build.2012-04-13.223': (1, 1, 3, ('rc42', '3-14-15', '24'), ('build', '2012-04-13', '223')), '1.1.3+build.2012-04-13.HUY.alpha-12.1': @@ -163,18 +163,17 @@ class VersionTestCase(unittest.TestCase): class SpecTestCase(unittest.TestCase): components = { - '~=0.1': (base.Spec.KIND_EQ_LOOSE, 0, 1, None, None, None), - '~=0.1.2-rc3': (base.Spec.KIND_EQ_LOOSE, 0, 1, 2, ('rc3',), None), - '~=0.1.2+build3.14': (base.Spec.KIND_EQ_LOOSE, 0, 1, 2, (), ('build3', '14')), - '<=0.1.1': (base.Spec.KIND_LTE, 0, 1, 1, (), ()), - '<0.1.1': (base.Spec.KIND_LT, 0, 1, 1, (), ()), - '<~0.1.1': (base.Spec.KIND_LTE_LOOSE, 0, 1, 1, None, None), - '<~0.1': (base.Spec.KIND_LTE_LOOSE, 0, 1, None, None, None), - '>=0.2.3-rc2': (base.Spec.KIND_GTE, 0, 2, 3, ('rc2',), ()), - '>0.2.3-rc2': (base.Spec.KIND_GT, 0, 2, 3, ('rc2',), ()), - '>~2': (base.Spec.KIND_GTE_LOOSE, 2, None, None, None, None), - '!=0.1.1': (base.Spec.KIND_NEQ, 0, 1, 1, (), ()), - '!~0.3': (base.Spec.KIND_NEQ_LOOSE, 0, 3, None, None, None), + '==0.1.0': (base.Spec.KIND_EQUAL, 0, 1, 0, None, None), + '==0.1.2-rc3': (base.Spec.KIND_EQUAL, 0, 1, 2, ('rc3',), None), + '==0.1.2+build3.14': (base.Spec.KIND_EQUAL, 0, 1, 2, (), ('build3', '14')), + '<=0.1.1+': (base.Spec.KIND_LTE, 0, 1, 1, (), ()), + '<0.1.1': (base.Spec.KIND_LT, 0, 1, 1, None, None), + '<=0.1.1': (base.Spec.KIND_LTE, 0, 1, 1, None, None), + '>=0.2.3-rc2': (base.Spec.KIND_GTE, 0, 2, 3, ('rc2',), None), + '>0.2.3-rc2+': (base.Spec.KIND_GT, 0, 2, 3, ('rc2',), ()), + '>=2.0.0': (base.Spec.KIND_GTE, 2, 0, 0, None, None), + '!=0.1.1+': (base.Spec.KIND_NEQ, 0, 1, 1, (), ()), + '!=0.3.0': (base.Spec.KIND_NEQ, 0, 3, 0, None, None), } def test_components(self): @@ -182,9 +181,6 @@ class SpecTestCase(unittest.TestCase): kind, major, minor, patch, prerelease, build = components spec = base.Spec(spec_text) - self.assertNotEqual(spec, spec_text) - self.assertEqual(spec_text, str(spec)) - self.assertEqual(kind, spec.kind) self.assertEqual(major, spec.spec.major) self.assertEqual(minor, spec.spec.minor) @@ -192,54 +188,57 @@ class SpecTestCase(unittest.TestCase): self.assertEqual(prerelease, spec.spec.prerelease) self.assertEqual(build, spec.spec.build) + self.assertNotEqual(spec, spec_text) + self.assertEqual(spec_text, str(spec)) + matches = { - '~=0.1': ( - ['0.1.0', '0.1.99', '0.1.0-rc1', '0.1.4-rc1+build2'], - ['0.0.1', '0.2.0'], + '==0.1.0': ( + ['0.1.0', '0.1.0-rc1', '0.1.0+build1', '0.1.0-rc1+build2'], + ['0.0.1', '0.2.0', '0.1.1'], ), - '~=0.1.2-rc3': ( + '==0.1.2-rc3': ( ['0.1.2-rc3+build1', '0.1.2-rc3+build4.5'], ['0.1.2-rc4', '0.1.2', '0.1.3'], ), - '~=0.1.2+build3.14': ( + '==0.1.2+build3.14': ( ['0.1.2+build3.14'], ['0.1.2-rc+build3.14', '0.1.2+build3.15'], ), '<=0.1.1': ( - ['0.0.0', '0.1.1-alpha1', '0.1.1'], - ['0.1.1+build2', '0.1.2'], + ['0.0.0', '0.1.1-alpha1', '0.1.1', '0.1.1+build2'], + ['0.1.2'], ), '<0.1.1': ( - ['0.1.0', '0.1.1-zzz+999', '0.0.0'], - ['0.1.1', '1.2.0', '0.1.1+build3'], + ['0.1.0', '0.0.0'], + ['0.1.1', '0.1.1-zzz+999', '1.2.0', '0.1.1+build3'], ), - '<~0.1.1': ( + '<=0.1.1': ( ['0.1.1+build4', '0.1.1-alpha', '0.1.0'], ['0.2.3', '1.1.1', '0.1.2'], ), - '<~0.1': ( - ['0.1.0', '0.1.1+4', '0.1.99', '0.1.0-alpha', '0.0.1'], - ['0.2.0', '1.0.0'], + '<0.1.1-': ( + ['0.1.0', '0.1.1-alpha', '0.1.1-alpha+4'], + ['0.2.0', '1.0.0', '0.1.1', '0.1.1+build1'], ), '>=0.2.3-rc2': ( ['0.2.3-rc3', '0.2.3', '0.2.3+1', '0.2.3-rc2', '0.2.3-rc2+1'], ['0.2.3-rc1', '0.2.2'], ), - '>0.2.3-rc2': ( + '>0.2.3-rc2+': ( ['0.2.3-rc3', '0.2.3', '0.2.3-rc2+1'], - ['0.2.3-rc1', '0.2.2'], + ['0.2.3-rc1', '0.2.2', '0.2.3-rc2'], ), - '>~2': ( - ['2.1.1', '2.0.0-alpha1', '3.1.4'], - ['1.9.9', '1.9.9999'], + '>2.0.0+': ( + ['2.1.1', '2.0.0+b1', '3.1.4'], + ['1.9.9', '1.9.9999', '2.0.0', '2.0.0-rc4'], ), '!=0.1.1': ( - ['0.1.1-alpha', '0.1.2', '0.1.0', '1.4.2'], - ['0.1.1'], + ['0.1.2', '0.1.0', '1.4.2'], + ['0.1.1', '0.1.1-alpha', '0.1.1+b1'], ), - '!~0.3': ( - ['0.4.0', '1.3.0'], - ['0.3.0', '0.3.99', '0.3.0-alpha', '0.3.999999+4'], + '!=0.3.4-': ( + ['0.4.0', '1.3.0', '0.3.4-alpha', '0.3.4-alpha+b1'], + ['0.3.4', '0.3.4+b1'], ), } @@ -279,7 +278,7 @@ class SpecTestCase(unittest.TestCase): class SpecListTestCase(unittest.TestCase): examples = { '>=0.1.1,<0.1.2': ['>=0.1.1', '<0.1.2'], - '>~0.1,!=0.1.3-rc1,<0.1.3': ['>~0.1', '!=0.1.3-rc1', '<0.1.3'], + '>=0.1.0,!=0.1.3-rc1,<0.1.3': ['>=0.1.0', '!=0.1.3-rc1', '<0.1.3'], } def test_parsing(self): @@ -295,7 +294,7 @@ class SpecListTestCase(unittest.TestCase): split_examples = { ('>=0.1.1', '<0.1.2', '!=0.1.1+build1'): ['>=0.1.1', '<0.1.2', '!=0.1.1+build1'], - ('>~0.1', '!=0.1.3-rc1,<0.1.3'): ['>~0.1', '!=0.1.3-rc1', '<0.1.3'], + ('>=0.1.0', '!=0.1.3-rc1,<0.1.3'): ['>=0.1.0', '!=0.1.3-rc1', '<0.1.3'], } def test_parsing_split(self): @@ -311,12 +310,13 @@ class SpecListTestCase(unittest.TestCase): matches = { '>=0.1.1,<0.1.2': ( - ['0.1.1', '0.1.2-alpha', '0.1.1+4'], - ['0.1.1-alpha', '0.1.2', '1.3.4'], + ['0.1.1', '0.1.1+4', '0.1.1-alpha'], + ['0.1.2-alpha', '0.1.2', '1.3.4'], ), - '>~0.1,!=0.1.3-rc1,<0.1.3': ( - ['0.1.1', '0.1.3-rc1+4', '0.1.3-alpha'], - ['0.0.1', '0.1.3', '0.2.2', '0.1.3-rc1'], + '>=0.1.0+,!=0.1.3-rc1,<0.1.4': ( + ['0.1.1', '0.1.0+b4', '0.1.2', '0.1.3-rc2'], + ['0.0.1', '0.1.4', '0.1.4-alpha', '0.1.3-rc1+4', + '0.1.0-alpha', '0.2.2', '0.1.4-rc1'], ), } diff --git a/tests/test_django.py b/tests/test_django.py index 79cd1c0..d8eef7d 100644 --- a/tests/test_django.py +++ b/tests/test_django.py @@ -35,40 +35,46 @@ if django_loaded: # pragma: no cover @unittest.skipIf(not django_loaded, "Django not installed") class DjangoFieldTestCase(unittest.TestCase): def test_version(self): - obj = models.VersionModel(version='0.1.1', spec='>0.1.0', speclist='~=0.1,!~0.1.1') + obj = models.VersionModel(version='0.1.1', spec='>0.1.0', speclist='==0.1.1,!=0.1.1-alpha') self.assertEqual(semantic_version.Version('0.1.1'), obj.version) self.assertEqual(semantic_version.Spec('>0.1.0'), obj.spec) - self.assertEqual(semantic_version.SpecList('~=0.1,!~0.1.1'), obj.speclist) + self.assertEqual(semantic_version.SpecList('==0.1.1,!=0.1.1-alpha'), obj.speclist) alt_obj = models.VersionModel(version=obj.version, spec=obj.spec, speclist=obj.speclist) self.assertEqual(semantic_version.Version('0.1.1'), alt_obj.version) self.assertEqual(semantic_version.Spec('>0.1.0'), alt_obj.spec) - self.assertEqual(semantic_version.SpecList('~=0.1,!~0.1.1'), alt_obj.speclist) + self.assertEqual(semantic_version.SpecList('==0.1.1,!=0.1.1-alpha'), alt_obj.speclist) self.assertEqual(obj.spec, alt_obj.spec) self.assertEqual(obj.version, alt_obj.version) self.assertEqual(obj.speclist, alt_obj.speclist) def test_invalid_input(self): self.assertRaises(ValueError, models.VersionModel, - version='0.1.1', spec='blah', speclist='~=0.1,!~0.1.1') + version='0.1.1', spec='blah', speclist='==0.1.1,!=0.1.1-alpha') self.assertRaises(ValueError, models.VersionModel, - version='0.1', spec='>0.1.1', speclist='~=0.1,!~0.1.1') + version='0.1', spec='>0.1.1', speclist='==0.1.1,!=0.1.1-alpha') self.assertRaises(ValueError, models.VersionModel, - version='0.1.1', spec='>0.1.1', speclist='~=0,!=0.2') + version='0.1.1', spec='>0.1.1', speclist='==0,!=0.2') def test_partial(self): - obj = models.PartialVersionModel(partial='0.1') + obj = models.PartialVersionModel(partial='0.1.0') - self.assertEqual(semantic_version.Version('0.1', partial=True), obj.partial) + self.assertEqual(semantic_version.Version('0.1.0', partial=True), obj.partial) self.assertIsNone(obj.optional) self.assertIsNone(obj.optional_spec) self.assertIsNone(obj.optional_speclist) - alt_obj = models.PartialVersionModel(partial=obj.partial, optional=obj.optional, - optional_spec=obj.optional_spec, optional_speclist=obj.optional_speclist) - self.assertEqual(semantic_version.Version('0.1', partial=True), alt_obj.partial) + # Copy values to another model + alt_obj = models.PartialVersionModel( + partial=obj.partial, + optional=obj.optional, + optional_spec=obj.optional_spec, + optional_speclist=obj.optional_speclist, + ) + + self.assertEqual(semantic_version.Version('0.1.0', partial=True), alt_obj.partial) self.assertEqual(obj.partial, alt_obj.partial) self.assertIsNone(obj.optional) self.assertIsNone(obj.optional_spec) @@ -76,9 +82,9 @@ class DjangoFieldTestCase(unittest.TestCase): def test_serialization(self): o1 = models.VersionModel(version='0.1.1', spec='<0.2.4-rc42', - speclist='~=0.1,!=0.1.1') - o2 = models.VersionModel(version='0.4.3-rc3+build3', spec='~=0.4', - speclist='<=0.1.1-rc2,!~0.1.1-rc1') + speclist='==0.1.1,!=0.1.1-alpha') + o2 = models.VersionModel(version='0.4.3-rc3+build3', spec='==0.4.3', + speclist='<=0.1.1-rc2,!=0.1.1-rc1') data = serializers.serialize('json', [o1, o2]) @@ -90,7 +96,7 @@ class DjangoFieldTestCase(unittest.TestCase): o1 = models.PartialVersionModel(partial='0.1.1', optional='0.2.4-rc42', optional_spec=None, optional_speclist=None) o2 = models.PartialVersionModel(partial='0.4.3-rc3+build3', optional='', - optional_spec='~=1.1', optional_speclist='~=0.1,!=0.1.1') + optional_spec='==1.1.0', optional_speclist='==0.1.1,!=0.1.1-alpha') data = serializers.serialize('json', [o1, o2]) @@ -115,7 +121,7 @@ if django_loaded: def test_db_interaction(self): o1 = models.VersionModel(version='0.1.1', spec='<0.2.4-rc42') - o2 = models.VersionModel(version='0.4.3-rc3+build3', spec='~=0.4') + o2 = models.VersionModel(version='0.4.3-rc3+build3', spec='==0.4.3') o1.save() o2.save() diff --git a/tests/test_match.py b/tests/test_match.py index 2e88c60..f6888bc 100755 --- a/tests/test_match.py +++ b/tests/test_match.py @@ -17,7 +17,7 @@ class MatchTestCase(unittest.TestCase): ] valid_specs = [ - '~=0.1', + '==0.1.0', '<=0.1.1', '>0.1.2-rc1', '>=0.1.2-rc1.3.4', @@ -26,14 +26,7 @@ class MatchTestCase(unittest.TestCase): ] matches = { - '~=0.1': [ - '0.1.1', - '0.1.2-rc1', - '0.1.2-rc1.3.4', - '0.1.2+build42-12.2012-01-01.12h23', - '0.1.2-rc1.3-14.15+build.2012-01-01.11h34', - ], - '~=0.1.2': [ + '==0.1.2': [ '0.1.2-rc1', '0.1.2-rc1.3.4', '0.1.2+build42-12.2012-01-01.12h23', @@ -44,8 +37,9 @@ class MatchTestCase(unittest.TestCase): '0.1.2-rc1', '0.1.2-rc1.3.4', '0.1.2', + '0.1.2+build4', ], - '<0.1.2': [ + '<0.1.2+': [ '0.1.1', '0.1.2-rc1', '0.1.2-rc1.3.4', @@ -59,19 +53,19 @@ class MatchTestCase(unittest.TestCase): '1.0.0', ], '>0.1.1': [ - '0.1.1+build4.5', + '0.1.2+build4.5', '0.1.2-rc1.3', '0.2.0', '1.0.0', ], - '>~0.1': [ - '0.1.1', - '0.1.0-rc1', + '>0.1.1+': [ + '0.1.1+b2', + '0.1.2-rc1', '1.1.1', '2.0.4', ], - '<~0.1.1': [ - '0.1.1', + '<0.1.1-': [ + '0.1.1-alpha', '0.1.1-rc4', '0.1.0+12.3', ], @@ -101,7 +95,7 @@ class MatchTestCase(unittest.TestCase): self.assertFalse('0.1.0' in spec, "0.1.0 should not be in %r" % spec) version = semantic_version.Version('0.1.1+4.2') - self.assertFalse(version in spec, "%r should not be in %r" % (version, spec)) + self.assertTrue(version in spec, "%r should be in %r" % (version, spec)) version = semantic_version.Version('0.1.1-rc1+4.2') self.assertTrue(version in spec, "%r should be in %r" % (version, spec)) |