Cleanup and document fixes from #31.

The PR was broken through fixed in ``next_minor()`` / ``next_major()``.
This commit is contained in:
Raphaël Barrois
2016-02-12 01:18:15 +01:00
parent 9f4ccad84b
commit b923dfcdf9
5 changed files with 44 additions and 17 deletions

View File

@@ -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)
------------------

View File

@@ -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

View File

@@ -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)

View File

@@ -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):

View File

@@ -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():