summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog1
-rw-r--r--docs/reference.rst6
-rw-r--r--semantic_version/base.py26
-rwxr-xr-xtests/test_base.py23
-rwxr-xr-xtests/test_match.py5
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 <https://github.com/rbarrois/python-semanticversion/issues/24>`_: Fix handling of bumping pre-release versions, thanks to @minchinweb.
+ * `#30 <https://github.com/rbarrois/python-semanticversion/issues/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():