diff options
author | Raphaël Barrois <raphael.barrois@polyconseil.fr> | 2012-05-14 18:42:09 +0200 |
---|---|---|
committer | Raphaël Barrois <raphael.barrois@polyconseil.fr> | 2012-05-14 18:42:09 +0200 |
commit | 23864f705ca70a7902cf3fa972ca0cff2792c87c (patch) | |
tree | c2d31bbf1912251c2de5e0674beb9576ff795a2e | |
download | semantic-version-23864f705ca70a7902cf3fa972ca0cff2792c87c.tar.gz |
Initial version
Signed-off-by: Raphaël Barrois <raphael.barrois@polyconseil.fr>
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | README | 7 | ||||
l--------- | README.rst | 1 | ||||
-rwxr-xr-x | setup.py | 0 | ||||
-rw-r--r-- | src/semantic_version/__init__.py | 123 | ||||
-rw-r--r-- | tests/__init__.py | 0 | ||||
-rw-r--r-- | tests/test_parsing.py | 64 |
7 files changed, 197 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d18402d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +.*.swp @@ -0,0 +1,7 @@ +python-semantic_version +======================= + +This small python library provides a few tools to handle `SemVer <http://semver.org>`_ in Python. + + +The first release (0.1.0) should handle the 2.0.0-rc1 version of the SemVer scheme. diff --git a/README.rst b/README.rst new file mode 120000 index 0000000..100b938 --- /dev/null +++ b/README.rst @@ -0,0 +1 @@ +README
\ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..e69de29 --- /dev/null +++ b/setup.py diff --git a/src/semantic_version/__init__.py b/src/semantic_version/__init__.py new file mode 100644 index 0000000..b04dcc4 --- /dev/null +++ b/src/semantic_version/__init__.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2012 Raphaël Barrois + + +import itertools +import re + + +def _to_int(value): + try: + return int(value), True + except ValueError: + return value, False + + +def identifier_cmp(a, b): + """Compare two identifier (for pre-release/build components).""" + + a_cmp, a_is_int = _to_int(a) + b_cmp, b_is_int = _to_int(b) + + if a_is_int and b_is_int: + # Numeric identifiers are compared as integers + return cmp(a_cmp, b_cmp) + elif a_is_int: + # Numeric identifiers have lower precedence + return -1 + elif b_is_int: + return 1 + else: + # Non-numeric identifers are compared lexicographically + return cmp(a_cmp, b_cmp) + + +def identifier_list_cmp(a, b): + identifier_pairs = zip(a, b) + for id_a, id_b in identifier_pairs: + cmp_res = identifier_cmp(id_a, id_b) + if cmp_res != 0: + return cmp_res + # alpha1.3 < alpha1.3.1 + return cmp(len(a), len(b)) + + +class SemanticVersion(object): + + version_re = re.compile('^(\d+)\.(\d+)\.(\d+)(?:-([0-9a-zA-Z.-]+))?(?:\+([0-9a-zA-Z.-]+))?$') + + def __init__(self, version_string): + major, minor, patch, prerelease, build = self.parse(version_string) + self.major = int(major) + self.minor = int(minor) + self.patch = int(patch) + if prerelease is None: + self.prerelease = [] + else: + self.prerelease = prerelease.split('.') + if build is None: + self.build = [] + else: + self.build = build.split('.') + + @classmethod + def parse(cls, version_string): + if not version_string: + raise ValueError('Invalid version string %r.' % version_string) + + match = cls.version_re.match(version_string) + if match: + return match.groups() + else: + raise ValueError('Invalid version string %r.' % version_string) + + def __str__(self): + prerelease = '.'.join(self.prerelease) + build = '.'.join(self.build) + version = '%d.%d.%d' % (self.major, self.minor, self.patch) + if prerelease: + version = '%s-%s' % (version, prerelease) + if build: + version = '%s+%s' % (version, build) + return version + + def __repr__(self): + return '<SemVer(%d, %d, %d, %r, %r)>' % ( + self.major, + self.minor, + self.patch, + self.prerelease, + self.build, + ) + + def __cmp__(self, other): + if not isinstance(other, SemanticVersion): + return NotImplemented + + base_cmp = cmp( + (self.major, self.minor, self.patch), + (other.major, other.minor, other.patch)) + + if base_cmp != 0: + return base_cmp + + if self.prerelease and other.prerelease: + prerelease_cmp = identifier_list_cmp(self.prerelease, other.prerelease) + if prerelease_cmp != 0: + return prerelease_cmp + elif self.prerelease: + # Prerelease version have lower precedence + return -1 + elif other.prerelease: + return 1 + + if self.build and other.build: + return identifier_list_cmp(self.build, other.build) + elif self.build: + # Build version have higher precedence + return 1 + elif other.build: + return -1 + else: + return 0 + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/__init__.py diff --git a/tests/test_parsing.py b/tests/test_parsing.py new file mode 100644 index 0000000..cdf21fc --- /dev/null +++ b/tests/test_parsing.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import unittest + +import semantic_version + + +class ParsingTestCase(unittest.TestCase): + invalids = [ + '0.1', + '0.1.4a', + '0.1.1.1', + '0.1.2-rc23,1', + ] + + valids = [ + '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', + ] + + def test_invalid(self): + for invalid in self.invalids: + self.assertRaises(ValueError, semantic_version.SemanticVersion, invalid) + + def test_simple(self): + for valid in self.valids: + version = semantic_version.SemanticVersion(valid) + self.assertEqual(valid, str(version)) + + +class ComparisonTestCase(unittest.TestCase): + order = [ + '1.0.0-alpha', + '1.0.0-alpha.1', + '1.0.0-beta.2', + '1.0.0-beta.11', + '1.0.0-rc.1', + '1.0.0-rc.1+build.1', + '1.0.0', + '1.0.0+0.3.7', + '1.3.7+build', + '1.3.7+build.2.b8f12d7', + '1.3.7+build.11.e0f985a', + ] + + def test_comparisons(self): + for i, first in enumerate(self.order): + first_ver = semantic_version.SemanticVersion(first) + for j, second in enumerate(self.order): + second_ver = semantic_version.SemanticVersion(second) + if i < j: + self.assertTrue(first_ver < second_ver, '%r !< %r' % (first_ver, second_ver)) + elif i == j: + self.assertTrue(first_ver == second_ver, '%r != %r' % (first_ver, second_ver)) + else: + self.assertTrue(first_ver > second_ver, '%r !> %r' % (first_ver, second_ver)) + + +if __name__ == '__main__': + unittest.main() |