diff options
-rw-r--r-- | pkg_resources.py | 47 | ||||
-rw-r--r-- | setuptools/tests/test_resources.py | 58 |
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') + + + + + + + + + + + + + + + |