aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pkg_resources.py47
-rw-r--r--setuptools/tests/test_resources.py58
2 files changed, 102 insertions, 3 deletions
diff --git a/pkg_resources.py b/pkg_resources.py
index 17dc23df..5fd6362a 100644
--- a/pkg_resources.py
+++ b/pkg_resources.py
@@ -17,7 +17,7 @@ __all__ = [
'register_loader_type', 'get_provider', 'IResourceProvider',
'ResourceManager', 'iter_distributions', 'require', 'resource_string',
'resource_stream', 'resource_filename', 'set_extraction_path',
- 'cleanup_resources', 'parse_requirements', # 'glob_resources'
+ 'cleanup_resources', 'parse_requirements', 'parse_version'# 'glob_resources'
]
import sys, os, zipimport, time, re
@@ -508,8 +508,20 @@ DISTRO = re.compile(r"\s*(\w+)").match # Distribution name
VERSION = re.compile(r"\s*(<=?|>=?|==|!=)\s*((\w|\.)+)").match # version info
COMMA = re.compile(r"\s*,").match # comma between items
+component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE)
+replace = {'pre':'c', 'preview':'c','-':'final-','rc':'c'}.get
+def _parse_version_parts(s):
+ for part in component_re.split(s):
+ part = replace(part,part)
+ if not part or part=='.':
+ continue
+ if part[:1] in '0123456789':
+ yield part.zfill(8) # pad for numeric comparison
+ else:
+ yield '*'+part
+ yield '*final' # ensure that alpha/beta/candidate are before final
@@ -519,12 +531,41 @@ COMMA = re.compile(r"\s*,").match # comma between items
+def parse_version(s):
+ """Convert a version string to a sortable key
+ This is a rough cross between distutils' StrictVersion and LooseVersion;
+ if you give it versions that would work with StrictVersion, then it behaves
+ the same; otherwise it acts like a slightly-smarter LooseVersion.
+ The returned value will be a tuple of strings. Numeric portions of the
+ version are padded to 8 digits so they will compare numerically, but
+ without relying on how numbers compare relative to strings. Dots are
+ dropped, but dashes are retained. Trailing zeros between alpha segments
+ or dashes are suppressed, so that e.g. 2.4.0 is considered the same as 2.4.
+ Alphanumeric parts are lower-cased.
+
+ The algorithm assumes that strings like '-' and any alpha string > "final"
+ represents a "patch level". So, "2.4-1" is assumed to be a branch or patch
+ of "2.4", and therefore "2.4.1" is considered newer than "2.4-1".
+ Strings like "a", "b", "c", "alpha", "beta", "candidate" and so on (that
+ come before "final" alphabetically) are assumed to be pre-release versions,
+ and so the version "2.4" is considered newer than "2.4a1".
-
-
+ Finally, to handle miscellaneous cases, the strings "pre", "preview", and
+ "rc" are treated as if they were "c", i.e. as though they were release
+ candidates, and therefore are not as new as a version string that does not
+ contain them.
+ """
+ parts = []
+ for part in _parse_version_parts(s.lower()):
+ if part.startswith('*'):
+ # remove trailing zeros from each series of numeric parts
+ while parts and parts[-1]=='00000000':
+ parts.pop()
+ parts.append(part)
+ return tuple(parts)
diff --git a/setuptools/tests/test_resources.py b/setuptools/tests/test_resources.py
index f918d21b..8797cda9 100644
--- a/setuptools/tests/test_resources.py
+++ b/setuptools/tests/test_resources.py
@@ -31,3 +31,61 @@ class ParseTests(TestCase):
self.assertRaises(ValueError,lambda:list(parse_requirements("x\\")))
self.assertRaises(ValueError,lambda:list(parse_requirements("x==2 q")))
+
+
+
+
+
+
+
+
+ def testVersionOrdering(self):
+ def c(s1,s2):
+ p1, p2 = parse_version(s1),parse_version(s2)
+ self.failUnless(p1<p2, (s1,s2,p1,p2))
+
+ c('2.1','2.1.1')
+ c('2a1','2b0')
+ c('2a1','2.1')
+ c('2.3a1', '2.3')
+ c('2.1-1', '2.1-2')
+ c('2.1-1', '2.1.1')
+ c('2.1', '2.1pl4')
+ c('2.1a0-20040501', '2.1')
+ c('1.1', '02.1')
+ c('A56','B27')
+ c('3.2', '3.2.pl0')
+ c('3.2-1', '3.2pl1')
+ c('3.2pl1', '3.2pl1-1')
+ c('0.4', '4.0')
+ c('0.0.4', '0.4.0')
+ c('0pl1', '0.4pl1')
+
+
+ def testVersionEquality(self):
+ def c(s1,s2):
+ p1, p2 = parse_version(s1),parse_version(s2)
+ self.assertEqual(p1,p2, (s1,s2,p1,p2))
+
+ c('0.4', '0.4.0')
+ c('0.4.0.0', '0.4.0')
+ c('0.4.0-0', '0.4-0')
+ c('0pl1', '0.0pl1')
+ c('0pre1', '0.0c1')
+ c('0.0.0preview1', '0c1')
+ c('0.0c1', '0rc1')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+