From b923dfcdf9ea32072d49ff9f1527d8826327aef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 12 Feb 2016 01:18:15 +0100 Subject: Cleanup and document fixes from #31. The PR was broken through fixed in ``next_minor()`` / ``next_major()``. --- ChangeLog | 1 + docs/reference.rst | 6 ++++++ semantic_version/base.py | 26 ++++++++++++++------------ tests/test_base.py | 23 ++++++++++++++++++++--- tests/test_match.py | 5 +++-- 5 files changed, 44 insertions(+), 17 deletions(-) diff --git a/ChangeLog b/ChangeLog index 0fd7823..48cf749 100644 --- a/ChangeLog +++ b/ChangeLog @@ -32,6 +32,7 @@ ChangeLog * ``Spec('<=1.3.0')`` now matches ``Version('1.3.0+abde24fe883')`` * `#24 `_: Fix handling of bumping pre-release versions, thanks to @minchinweb. + * `#30 `_: Add support for NPM-style ``^1.2.3`` and ``~2.3.4`` specs, thanks to @skwashd 2.4.2 (2015-07-02) ------------------ diff --git a/docs/reference.rst b/docs/reference.rst index 261e738..a645c1f 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -277,6 +277,11 @@ This means that:: .. note:: python-semanticversion also accepts ``"*"`` as a version spec, that matches all (valid) version strings. +.. note:: python-semanticversion includes support for NPM-style specs: + + * ``~1.2.3`` means "Any release between 1.2.3 and 1.3.0" + * ``^1.3.4`` means "Any release between 1.3.4 and 2.0.0" + In order to force matches to *strictly* compare version numbers, these additional rules apply: @@ -307,6 +312,7 @@ rules apply: * ``<1.1.1+b1`` is invalid + .. class:: Spec(spec_string[, spec_string[, ...]]) Stores a list of :class:`SpecItem` and matches any :class:`Version` against all diff --git a/semantic_version/base.py b/semantic_version/base.py index 7451e14..cf85d86 100644 --- a/semantic_version/base.py +++ b/semantic_version/base.py @@ -406,7 +406,13 @@ class SpecItem(object): KIND_CARET = '^' KIND_TILDE = '~' - re_spec = re.compile(r'^(<|<=|={,2}|>=|>|!=|\^|~)(\d.*)$') + # Map a kind alias to its full version + KIND_ALIASES = { + KIND_SHORTEQ: KIND_EQUAL, + KIND_EMPTY: KIND_EQUAL, + } + + re_spec = re.compile(r'^(<|<=||=|==|>=|>|!=|\^|~)(\d.*)$') def __init__(self, requirement_string): kind, spec = self.parse(requirement_string) @@ -427,6 +433,9 @@ class SpecItem(object): raise ValueError("Invalid requirement specification: %r" % requirement_string) kind, version = match.groups() + if kind in cls.KIND_ALIASES: + kind = cls.KIND_ALIASES[kind] + spec = Version(version, partial=True) if spec.build is not None and kind not in (cls.KIND_EQUAL, cls.KIND_NEQ): raise ValueError( @@ -441,7 +450,7 @@ class SpecItem(object): return version < self.spec elif self.kind == self.KIND_LTE: return version <= self.spec - elif self.kind in [self.KIND_EQUAL, self.KIND_SHORTEQ, self.KIND_EMPTY]: + elif self.kind == self.KIND_EQUAL: return version == self.spec elif self.kind == self.KIND_GTE: return version >= self.spec @@ -450,20 +459,13 @@ class SpecItem(object): elif self.kind == self.KIND_NEQ: return version != self.spec elif self.kind == self.KIND_CARET: - return self.caretCompare(version) + return self.spec <= version < self.spec.next_major() + return self.caret_compare(version) elif self.kind == self.KIND_TILDE: - return self.tildeCompare(version) + return self.spec <= version < self.spec.next_minor() else: # pragma: no cover raise ValueError('Unexpected match kind: %r' % self.kind) - def caretCompare(self, version): - max_version = version.next_major() - return version >= self.spec and version < max_version - - def tildeCompare(self, version): - max_version = version.next_minor() - return version >= self.spec and version < max_version - def __str__(self): return '%s%s' % (self.kind, self.spec) diff --git a/tests/test_base.py b/tests/test_base.py index 6b64073..1fcce8c 100755 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -419,6 +419,10 @@ class SpecItemTestCase(unittest.TestCase): '>=2.0.0': (base.SpecItem.KIND_GTE, 2, 0, 0, None, None), '!=0.1.1+rc3': (base.SpecItem.KIND_NEQ, 0, 1, 1, (), ('rc3',)), '!=0.3.0': (base.SpecItem.KIND_NEQ, 0, 3, 0, None, None), + '=0.3.0': (base.SpecItem.KIND_EQUAL, 0, 3, 0, None, None), + '0.3.0': (base.SpecItem.KIND_EQUAL, 0, 3, 0, None, None), + '~0.1.2': (base.SpecItem.KIND_TILDE, 0, 1, 2, None, None), + '^0.1.3': (base.SpecItem.KIND_CARET, 0, 1, 3, None, None), } def test_components(self): @@ -433,14 +437,19 @@ class SpecItemTestCase(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': ( ['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.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.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+build1', '0.1.2-rc3+build4.5'], ['0.1.2-rc4', '0.1.2', '0.1.3'], @@ -489,6 +498,14 @@ class SpecItemTestCase(unittest.TestCase): ['0.4.0', '1.3.0', '0.3.4-alpha', '0.3.4-alpha+b1'], ['0.3.4', '0.3.4+b1'], ), + '~1.1.2': ( + ['1.1.3', '1.1.2-alpha', '1.1.2-alpha+b1'], + ['1.1.1', '1.2.1', '2.1.0'], + ), + '^1.1.2': ( + ['1.1.3', '1.2.1', '1.1.2-alpha', '1.1.2-alpha+b1'], + ['1.1.1', '2.1.0'], + ), } def test_matches(self): diff --git a/tests/test_match.py b/tests/test_match.py index 9955b9f..f0b0fe8 100755 --- a/tests/test_match.py +++ b/tests/test_match.py @@ -122,8 +122,9 @@ class MatchTestCase(unittest.TestCase): def test_simple(self): for valid in self.valid_specs: - version = semantic_version.Spec(valid) - self.assertEqual(valid, str(version)) + spec = semantic_version.SpecItem(valid) + normalized = str(spec) + self.assertEqual(spec, semantic_version.SpecItem(normalized)) def test_match(self): for spec_txt, versions in self.matches.items(): -- cgit v1.2.1