~anitanayak/charms/trusty/ibm-mq/ibm-mq-trusty

« back to all changes in this revision

Viewing changes to .tox/py35/lib/python3.5/site-packages/pip/_vendor/distlib/version.py

  • Committer: Anita Nayak
  • Date: 2016-10-24 07:10:08 UTC
  • Revision ID: anitanayak@in.ibm.com-20161024071008-tqk3cefak6nc1c69
checking in after fixing lint errors

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
#
 
3
# Copyright (C) 2012-2016 The Python Software Foundation.
 
4
# See LICENSE.txt and CONTRIBUTORS.txt.
 
5
#
 
6
"""
 
7
Implementation of a flexible versioning scheme providing support for PEP-440,
 
8
setuptools-compatible and semantic versioning.
 
9
"""
 
10
 
 
11
import logging
 
12
import re
 
13
 
 
14
from .compat import string_types
 
15
 
 
16
__all__ = ['NormalizedVersion', 'NormalizedMatcher',
 
17
           'LegacyVersion', 'LegacyMatcher',
 
18
           'SemanticVersion', 'SemanticMatcher',
 
19
           'UnsupportedVersionError', 'get_scheme']
 
20
 
 
21
logger = logging.getLogger(__name__)
 
22
 
 
23
 
 
24
class UnsupportedVersionError(ValueError):
 
25
    """This is an unsupported version."""
 
26
    pass
 
27
 
 
28
 
 
29
class Version(object):
 
30
    def __init__(self, s):
 
31
        self._string = s = s.strip()
 
32
        self._parts = parts = self.parse(s)
 
33
        assert isinstance(parts, tuple)
 
34
        assert len(parts) > 0
 
35
 
 
36
    def parse(self, s):
 
37
        raise NotImplementedError('please implement in a subclass')
 
38
 
 
39
    def _check_compatible(self, other):
 
40
        if type(self) != type(other):
 
41
            raise TypeError('cannot compare %r and %r' % (self, other))
 
42
 
 
43
    def __eq__(self, other):
 
44
        self._check_compatible(other)
 
45
        return self._parts == other._parts
 
46
 
 
47
    def __ne__(self, other):
 
48
        return not self.__eq__(other)
 
49
 
 
50
    def __lt__(self, other):
 
51
        self._check_compatible(other)
 
52
        return self._parts < other._parts
 
53
 
 
54
    def __gt__(self, other):
 
55
        return not (self.__lt__(other) or self.__eq__(other))
 
56
 
 
57
    def __le__(self, other):
 
58
        return self.__lt__(other) or self.__eq__(other)
 
59
 
 
60
    def __ge__(self, other):
 
61
        return self.__gt__(other) or self.__eq__(other)
 
62
 
 
63
    # See http://docs.python.org/reference/datamodel#object.__hash__
 
64
    def __hash__(self):
 
65
        return hash(self._parts)
 
66
 
 
67
    def __repr__(self):
 
68
        return "%s('%s')" % (self.__class__.__name__, self._string)
 
69
 
 
70
    def __str__(self):
 
71
        return self._string
 
72
 
 
73
    @property
 
74
    def is_prerelease(self):
 
75
        raise NotImplementedError('Please implement in subclasses.')
 
76
 
 
77
 
 
78
class Matcher(object):
 
79
    version_class = None
 
80
 
 
81
    dist_re = re.compile(r"^(\w[\s\w'.-]*)(\((.*)\))?")
 
82
    comp_re = re.compile(r'^(<=|>=|<|>|!=|={2,3}|~=)?\s*([^\s,]+)$')
 
83
    num_re = re.compile(r'^\d+(\.\d+)*$')
 
84
 
 
85
    # value is either a callable or the name of a method
 
86
    _operators = {
 
87
        '<': lambda v, c, p: v < c,
 
88
        '>': lambda v, c, p: v > c,
 
89
        '<=': lambda v, c, p: v == c or v < c,
 
90
        '>=': lambda v, c, p: v == c or v > c,
 
91
        '==': lambda v, c, p: v == c,
 
92
        '===': lambda v, c, p: v == c,
 
93
        # by default, compatible => >=.
 
94
        '~=': lambda v, c, p: v == c or v > c,
 
95
        '!=': lambda v, c, p: v != c,
 
96
    }
 
97
 
 
98
    def __init__(self, s):
 
99
        if self.version_class is None:
 
100
            raise ValueError('Please specify a version class')
 
101
        self._string = s = s.strip()
 
102
        m = self.dist_re.match(s)
 
103
        if not m:
 
104
            raise ValueError('Not valid: %r' % s)
 
105
        groups = m.groups('')
 
106
        self.name = groups[0].strip()
 
107
        self.key = self.name.lower()    # for case-insensitive comparisons
 
108
        clist = []
 
109
        if groups[2]:
 
110
            constraints = [c.strip() for c in groups[2].split(',')]
 
111
            for c in constraints:
 
112
                m = self.comp_re.match(c)
 
113
                if not m:
 
114
                    raise ValueError('Invalid %r in %r' % (c, s))
 
115
                groups = m.groups()
 
116
                op = groups[0] or '~='
 
117
                s = groups[1]
 
118
                if s.endswith('.*'):
 
119
                    if op not in ('==', '!='):
 
120
                        raise ValueError('\'.*\' not allowed for '
 
121
                                         '%r constraints' % op)
 
122
                    # Could be a partial version (e.g. for '2.*') which
 
123
                    # won't parse as a version, so keep it as a string
 
124
                    vn, prefix = s[:-2], True
 
125
                    if not self.num_re.match(vn):
 
126
                        # Just to check that vn is a valid version
 
127
                        self.version_class(vn)
 
128
                else:
 
129
                    # Should parse as a version, so we can create an
 
130
                    # instance for the comparison
 
131
                    vn, prefix = self.version_class(s), False
 
132
                clist.append((op, vn, prefix))
 
133
        self._parts = tuple(clist)
 
134
 
 
135
    def match(self, version):
 
136
        """
 
137
        Check if the provided version matches the constraints.
 
138
 
 
139
        :param version: The version to match against this instance.
 
140
        :type version: Strring or :class:`Version` instance.
 
141
        """
 
142
        if isinstance(version, string_types):
 
143
            version = self.version_class(version)
 
144
        for operator, constraint, prefix in self._parts:
 
145
            f = self._operators.get(operator)
 
146
            if isinstance(f, string_types):
 
147
                f = getattr(self, f)
 
148
            if not f:
 
149
                msg = ('%r not implemented '
 
150
                       'for %s' % (operator, self.__class__.__name__))
 
151
                raise NotImplementedError(msg)
 
152
            if not f(version, constraint, prefix):
 
153
                return False
 
154
        return True
 
155
 
 
156
    @property
 
157
    def exact_version(self):
 
158
        result = None
 
159
        if len(self._parts) == 1 and self._parts[0][0] in ('==', '==='):
 
160
            result = self._parts[0][1]
 
161
        return result
 
162
 
 
163
    def _check_compatible(self, other):
 
164
        if type(self) != type(other) or self.name != other.name:
 
165
            raise TypeError('cannot compare %s and %s' % (self, other))
 
166
 
 
167
    def __eq__(self, other):
 
168
        self._check_compatible(other)
 
169
        return self.key == other.key and self._parts == other._parts
 
170
 
 
171
    def __ne__(self, other):
 
172
        return not self.__eq__(other)
 
173
 
 
174
    # See http://docs.python.org/reference/datamodel#object.__hash__
 
175
    def __hash__(self):
 
176
        return hash(self.key) + hash(self._parts)
 
177
 
 
178
    def __repr__(self):
 
179
        return "%s(%r)" % (self.__class__.__name__, self._string)
 
180
 
 
181
    def __str__(self):
 
182
        return self._string
 
183
 
 
184
 
 
185
PEP440_VERSION_RE = re.compile(r'^v?(\d+!)?(\d+(\.\d+)*)((a|b|c|rc)(\d+))?'
 
186
                               r'(\.(post)(\d+))?(\.(dev)(\d+))?'
 
187
                               r'(\+([a-zA-Z\d]+(\.[a-zA-Z\d]+)?))?$')
 
188
 
 
189
 
 
190
def _pep_440_key(s):
 
191
    s = s.strip()
 
192
    m = PEP440_VERSION_RE.match(s)
 
193
    if not m:
 
194
        raise UnsupportedVersionError('Not a valid version: %s' % s)
 
195
    groups = m.groups()
 
196
    nums = tuple(int(v) for v in groups[1].split('.'))
 
197
    while len(nums) > 1 and nums[-1] == 0:
 
198
        nums = nums[:-1]
 
199
 
 
200
    if not groups[0]:
 
201
        epoch = 0
 
202
    else:
 
203
        epoch = int(groups[0])
 
204
    pre = groups[4:6]
 
205
    post = groups[7:9]
 
206
    dev = groups[10:12]
 
207
    local = groups[13]
 
208
    if pre == (None, None):
 
209
        pre = ()
 
210
    else:
 
211
        pre = pre[0], int(pre[1])
 
212
    if post == (None, None):
 
213
        post = ()
 
214
    else:
 
215
        post = post[0], int(post[1])
 
216
    if dev == (None, None):
 
217
        dev = ()
 
218
    else:
 
219
        dev = dev[0], int(dev[1])
 
220
    if local is None:
 
221
        local = ()
 
222
    else:
 
223
        parts = []
 
224
        for part in local.split('.'):
 
225
            # to ensure that numeric compares as > lexicographic, avoid
 
226
            # comparing them directly, but encode a tuple which ensures
 
227
            # correct sorting
 
228
            if part.isdigit():
 
229
                part = (1, int(part))
 
230
            else:
 
231
                part = (0, part)
 
232
            parts.append(part)
 
233
        local = tuple(parts)
 
234
    if not pre:
 
235
        # either before pre-release, or final release and after
 
236
        if not post and dev:
 
237
            # before pre-release
 
238
            pre = ('a', -1)     # to sort before a0
 
239
        else:
 
240
            pre = ('z',)        # to sort after all pre-releases
 
241
    # now look at the state of post and dev.
 
242
    if not post:
 
243
        post = ('_',)   # sort before 'a'
 
244
    if not dev:
 
245
        dev = ('final',)
 
246
 
 
247
    #print('%s -> %s' % (s, m.groups()))
 
248
    return epoch, nums, pre, post, dev, local
 
249
 
 
250
 
 
251
_normalized_key = _pep_440_key
 
252
 
 
253
 
 
254
class NormalizedVersion(Version):
 
255
    """A rational version.
 
256
 
 
257
    Good:
 
258
        1.2         # equivalent to "1.2.0"
 
259
        1.2.0
 
260
        1.2a1
 
261
        1.2.3a2
 
262
        1.2.3b1
 
263
        1.2.3c1
 
264
        1.2.3.4
 
265
        TODO: fill this out
 
266
 
 
267
    Bad:
 
268
        1           # mininum two numbers
 
269
        1.2a        # release level must have a release serial
 
270
        1.2.3b
 
271
    """
 
272
    def parse(self, s):
 
273
        result = _normalized_key(s)
 
274
        # _normalized_key loses trailing zeroes in the release
 
275
        # clause, since that's needed to ensure that X.Y == X.Y.0 == X.Y.0.0
 
276
        # However, PEP 440 prefix matching needs it: for example,
 
277
        # (~= 1.4.5.0) matches differently to (~= 1.4.5.0.0).
 
278
        m = PEP440_VERSION_RE.match(s)      # must succeed
 
279
        groups = m.groups()
 
280
        self._release_clause = tuple(int(v) for v in groups[1].split('.'))
 
281
        return result
 
282
 
 
283
    PREREL_TAGS = set(['a', 'b', 'c', 'rc', 'dev'])
 
284
 
 
285
    @property
 
286
    def is_prerelease(self):
 
287
        return any(t[0] in self.PREREL_TAGS for t in self._parts if t)
 
288
 
 
289
 
 
290
def _match_prefix(x, y):
 
291
    x = str(x)
 
292
    y = str(y)
 
293
    if x == y:
 
294
        return True
 
295
    if not x.startswith(y):
 
296
        return False
 
297
    n = len(y)
 
298
    return x[n] == '.'
 
299
 
 
300
 
 
301
class NormalizedMatcher(Matcher):
 
302
    version_class = NormalizedVersion
 
303
 
 
304
    # value is either a callable or the name of a method
 
305
    _operators = {
 
306
        '~=': '_match_compatible',
 
307
        '<': '_match_lt',
 
308
        '>': '_match_gt',
 
309
        '<=': '_match_le',
 
310
        '>=': '_match_ge',
 
311
        '==': '_match_eq',
 
312
        '===': '_match_arbitrary',
 
313
        '!=': '_match_ne',
 
314
    }
 
315
 
 
316
    def _adjust_local(self, version, constraint, prefix):
 
317
        if prefix:
 
318
            strip_local = '+' not in constraint and version._parts[-1]
 
319
        else:
 
320
            # both constraint and version are
 
321
            # NormalizedVersion instances.
 
322
            # If constraint does not have a local component,
 
323
            # ensure the version doesn't, either.
 
324
            strip_local = not constraint._parts[-1] and version._parts[-1]
 
325
        if strip_local:
 
326
            s = version._string.split('+', 1)[0]
 
327
            version = self.version_class(s)
 
328
        return version, constraint
 
329
 
 
330
    def _match_lt(self, version, constraint, prefix):
 
331
        version, constraint = self._adjust_local(version, constraint, prefix)
 
332
        if version >= constraint:
 
333
            return False
 
334
        release_clause = constraint._release_clause
 
335
        pfx = '.'.join([str(i) for i in release_clause])
 
336
        return not _match_prefix(version, pfx)
 
337
 
 
338
    def _match_gt(self, version, constraint, prefix):
 
339
        version, constraint = self._adjust_local(version, constraint, prefix)
 
340
        if version <= constraint:
 
341
            return False
 
342
        release_clause = constraint._release_clause
 
343
        pfx = '.'.join([str(i) for i in release_clause])
 
344
        return not _match_prefix(version, pfx)
 
345
 
 
346
    def _match_le(self, version, constraint, prefix):
 
347
        version, constraint = self._adjust_local(version, constraint, prefix)
 
348
        return version <= constraint
 
349
 
 
350
    def _match_ge(self, version, constraint, prefix):
 
351
        version, constraint = self._adjust_local(version, constraint, prefix)
 
352
        return version >= constraint
 
353
 
 
354
    def _match_eq(self, version, constraint, prefix):
 
355
        version, constraint = self._adjust_local(version, constraint, prefix)
 
356
        if not prefix:
 
357
            result = (version == constraint)
 
358
        else:
 
359
            result = _match_prefix(version, constraint)
 
360
        return result
 
361
 
 
362
    def _match_arbitrary(self, version, constraint, prefix):
 
363
        return str(version) == str(constraint)
 
364
 
 
365
    def _match_ne(self, version, constraint, prefix):
 
366
        version, constraint = self._adjust_local(version, constraint, prefix)
 
367
        if not prefix:
 
368
            result = (version != constraint)
 
369
        else:
 
370
            result = not _match_prefix(version, constraint)
 
371
        return result
 
372
 
 
373
    def _match_compatible(self, version, constraint, prefix):
 
374
        version, constraint = self._adjust_local(version, constraint, prefix)
 
375
        if version == constraint:
 
376
            return True
 
377
        if version < constraint:
 
378
            return False
 
379
#        if not prefix:
 
380
#            return True
 
381
        release_clause = constraint._release_clause
 
382
        if len(release_clause) > 1:
 
383
            release_clause = release_clause[:-1]
 
384
        pfx = '.'.join([str(i) for i in release_clause])
 
385
        return _match_prefix(version, pfx)
 
386
 
 
387
_REPLACEMENTS = (
 
388
    (re.compile('[.+-]$'), ''),                     # remove trailing puncts
 
389
    (re.compile(r'^[.](\d)'), r'0.\1'),             # .N -> 0.N at start
 
390
    (re.compile('^[.-]'), ''),                      # remove leading puncts
 
391
    (re.compile(r'^\((.*)\)$'), r'\1'),             # remove parentheses
 
392
    (re.compile(r'^v(ersion)?\s*(\d+)'), r'\2'),    # remove leading v(ersion)
 
393
    (re.compile(r'^r(ev)?\s*(\d+)'), r'\2'),        # remove leading v(ersion)
 
394
    (re.compile('[.]{2,}'), '.'),                   # multiple runs of '.'
 
395
    (re.compile(r'\b(alfa|apha)\b'), 'alpha'),      # misspelt alpha
 
396
    (re.compile(r'\b(pre-alpha|prealpha)\b'),
 
397
                'pre.alpha'),                       # standardise
 
398
    (re.compile(r'\(beta\)$'), 'beta'),             # remove parentheses
 
399
)
 
400
 
 
401
_SUFFIX_REPLACEMENTS = (
 
402
    (re.compile('^[:~._+-]+'), ''),                   # remove leading puncts
 
403
    (re.compile('[,*")([\]]'), ''),                   # remove unwanted chars
 
404
    (re.compile('[~:+_ -]'), '.'),                    # replace illegal chars
 
405
    (re.compile('[.]{2,}'), '.'),                   # multiple runs of '.'
 
406
    (re.compile(r'\.$'), ''),                       # trailing '.'
 
407
)
 
408
 
 
409
_NUMERIC_PREFIX = re.compile(r'(\d+(\.\d+)*)')
 
410
 
 
411
 
 
412
def _suggest_semantic_version(s):
 
413
    """
 
414
    Try to suggest a semantic form for a version for which
 
415
    _suggest_normalized_version couldn't come up with anything.
 
416
    """
 
417
    result = s.strip().lower()
 
418
    for pat, repl in _REPLACEMENTS:
 
419
        result = pat.sub(repl, result)
 
420
    if not result:
 
421
        result = '0.0.0'
 
422
 
 
423
    # Now look for numeric prefix, and separate it out from
 
424
    # the rest.
 
425
    #import pdb; pdb.set_trace()
 
426
    m = _NUMERIC_PREFIX.match(result)
 
427
    if not m:
 
428
        prefix = '0.0.0'
 
429
        suffix = result
 
430
    else:
 
431
        prefix = m.groups()[0].split('.')
 
432
        prefix = [int(i) for i in prefix]
 
433
        while len(prefix) < 3:
 
434
            prefix.append(0)
 
435
        if len(prefix) == 3:
 
436
            suffix = result[m.end():]
 
437
        else:
 
438
            suffix = '.'.join([str(i) for i in prefix[3:]]) + result[m.end():]
 
439
            prefix = prefix[:3]
 
440
        prefix = '.'.join([str(i) for i in prefix])
 
441
        suffix = suffix.strip()
 
442
    if suffix:
 
443
        #import pdb; pdb.set_trace()
 
444
        # massage the suffix.
 
445
        for pat, repl in _SUFFIX_REPLACEMENTS:
 
446
            suffix = pat.sub(repl, suffix)
 
447
 
 
448
    if not suffix:
 
449
        result = prefix
 
450
    else:
 
451
        sep = '-' if 'dev' in suffix else '+'
 
452
        result = prefix + sep + suffix
 
453
    if not is_semver(result):
 
454
        result = None
 
455
    return result
 
456
 
 
457
 
 
458
def _suggest_normalized_version(s):
 
459
    """Suggest a normalized version close to the given version string.
 
460
 
 
461
    If you have a version string that isn't rational (i.e. NormalizedVersion
 
462
    doesn't like it) then you might be able to get an equivalent (or close)
 
463
    rational version from this function.
 
464
 
 
465
    This does a number of simple normalizations to the given string, based
 
466
    on observation of versions currently in use on PyPI. Given a dump of
 
467
    those version during PyCon 2009, 4287 of them:
 
468
    - 2312 (53.93%) match NormalizedVersion without change
 
469
      with the automatic suggestion
 
470
    - 3474 (81.04%) match when using this suggestion method
 
471
 
 
472
    @param s {str} An irrational version string.
 
473
    @returns A rational version string, or None, if couldn't determine one.
 
474
    """
 
475
    try:
 
476
        _normalized_key(s)
 
477
        return s   # already rational
 
478
    except UnsupportedVersionError:
 
479
        pass
 
480
 
 
481
    rs = s.lower()
 
482
 
 
483
    # part of this could use maketrans
 
484
    for orig, repl in (('-alpha', 'a'), ('-beta', 'b'), ('alpha', 'a'),
 
485
                       ('beta', 'b'), ('rc', 'c'), ('-final', ''),
 
486
                       ('-pre', 'c'),
 
487
                       ('-release', ''), ('.release', ''), ('-stable', ''),
 
488
                       ('+', '.'), ('_', '.'), (' ', ''), ('.final', ''),
 
489
                       ('final', '')):
 
490
        rs = rs.replace(orig, repl)
 
491
 
 
492
    # if something ends with dev or pre, we add a 0
 
493
    rs = re.sub(r"pre$", r"pre0", rs)
 
494
    rs = re.sub(r"dev$", r"dev0", rs)
 
495
 
 
496
    # if we have something like "b-2" or "a.2" at the end of the
 
497
    # version, that is pobably beta, alpha, etc
 
498
    # let's remove the dash or dot
 
499
    rs = re.sub(r"([abc]|rc)[\-\.](\d+)$", r"\1\2", rs)
 
500
 
 
501
    # 1.0-dev-r371 -> 1.0.dev371
 
502
    # 0.1-dev-r79 -> 0.1.dev79
 
503
    rs = re.sub(r"[\-\.](dev)[\-\.]?r?(\d+)$", r".\1\2", rs)
 
504
 
 
505
    # Clean: 2.0.a.3, 2.0.b1, 0.9.0~c1
 
506
    rs = re.sub(r"[.~]?([abc])\.?", r"\1", rs)
 
507
 
 
508
    # Clean: v0.3, v1.0
 
509
    if rs.startswith('v'):
 
510
        rs = rs[1:]
 
511
 
 
512
    # Clean leading '0's on numbers.
 
513
    #TODO: unintended side-effect on, e.g., "2003.05.09"
 
514
    # PyPI stats: 77 (~2%) better
 
515
    rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs)
 
516
 
 
517
    # Clean a/b/c with no version. E.g. "1.0a" -> "1.0a0". Setuptools infers
 
518
    # zero.
 
519
    # PyPI stats: 245 (7.56%) better
 
520
    rs = re.sub(r"(\d+[abc])$", r"\g<1>0", rs)
 
521
 
 
522
    # the 'dev-rNNN' tag is a dev tag
 
523
    rs = re.sub(r"\.?(dev-r|dev\.r)\.?(\d+)$", r".dev\2", rs)
 
524
 
 
525
    # clean the - when used as a pre delimiter
 
526
    rs = re.sub(r"-(a|b|c)(\d+)$", r"\1\2", rs)
 
527
 
 
528
    # a terminal "dev" or "devel" can be changed into ".dev0"
 
529
    rs = re.sub(r"[\.\-](dev|devel)$", r".dev0", rs)
 
530
 
 
531
    # a terminal "dev" can be changed into ".dev0"
 
532
    rs = re.sub(r"(?![\.\-])dev$", r".dev0", rs)
 
533
 
 
534
    # a terminal "final" or "stable" can be removed
 
535
    rs = re.sub(r"(final|stable)$", "", rs)
 
536
 
 
537
    # The 'r' and the '-' tags are post release tags
 
538
    #   0.4a1.r10       ->  0.4a1.post10
 
539
    #   0.9.33-17222    ->  0.9.33.post17222
 
540
    #   0.9.33-r17222   ->  0.9.33.post17222
 
541
    rs = re.sub(r"\.?(r|-|-r)\.?(\d+)$", r".post\2", rs)
 
542
 
 
543
    # Clean 'r' instead of 'dev' usage:
 
544
    #   0.9.33+r17222   ->  0.9.33.dev17222
 
545
    #   1.0dev123       ->  1.0.dev123
 
546
    #   1.0.git123      ->  1.0.dev123
 
547
    #   1.0.bzr123      ->  1.0.dev123
 
548
    #   0.1a0dev.123    ->  0.1a0.dev123
 
549
    # PyPI stats:  ~150 (~4%) better
 
550
    rs = re.sub(r"\.?(dev|git|bzr)\.?(\d+)$", r".dev\2", rs)
 
551
 
 
552
    # Clean '.pre' (normalized from '-pre' above) instead of 'c' usage:
 
553
    #   0.2.pre1        ->  0.2c1
 
554
    #   0.2-c1         ->  0.2c1
 
555
    #   1.0preview123   ->  1.0c123
 
556
    # PyPI stats: ~21 (0.62%) better
 
557
    rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs)
 
558
 
 
559
    # Tcl/Tk uses "px" for their post release markers
 
560
    rs = re.sub(r"p(\d+)$", r".post\1", rs)
 
561
 
 
562
    try:
 
563
        _normalized_key(rs)
 
564
    except UnsupportedVersionError:
 
565
        rs = None
 
566
    return rs
 
567
 
 
568
#
 
569
#   Legacy version processing (distribute-compatible)
 
570
#
 
571
 
 
572
_VERSION_PART = re.compile(r'([a-z]+|\d+|[\.-])', re.I)
 
573
_VERSION_REPLACE = {
 
574
    'pre': 'c',
 
575
    'preview': 'c',
 
576
    '-': 'final-',
 
577
    'rc': 'c',
 
578
    'dev': '@',
 
579
    '': None,
 
580
    '.': None,
 
581
}
 
582
 
 
583
 
 
584
def _legacy_key(s):
 
585
    def get_parts(s):
 
586
        result = []
 
587
        for p in _VERSION_PART.split(s.lower()):
 
588
            p = _VERSION_REPLACE.get(p, p)
 
589
            if p:
 
590
                if '0' <= p[:1] <= '9':
 
591
                    p = p.zfill(8)
 
592
                else:
 
593
                    p = '*' + p
 
594
                result.append(p)
 
595
        result.append('*final')
 
596
        return result
 
597
 
 
598
    result = []
 
599
    for p in get_parts(s):
 
600
        if p.startswith('*'):
 
601
            if p < '*final':
 
602
                while result and result[-1] == '*final-':
 
603
                    result.pop()
 
604
            while result and result[-1] == '00000000':
 
605
                result.pop()
 
606
        result.append(p)
 
607
    return tuple(result)
 
608
 
 
609
 
 
610
class LegacyVersion(Version):
 
611
    def parse(self, s):
 
612
        return _legacy_key(s)
 
613
 
 
614
    @property
 
615
    def is_prerelease(self):
 
616
        result = False
 
617
        for x in self._parts:
 
618
            if (isinstance(x, string_types) and x.startswith('*') and
 
619
                x < '*final'):
 
620
                result = True
 
621
                break
 
622
        return result
 
623
 
 
624
 
 
625
class LegacyMatcher(Matcher):
 
626
    version_class = LegacyVersion
 
627
 
 
628
    _operators = dict(Matcher._operators)
 
629
    _operators['~='] = '_match_compatible'
 
630
 
 
631
    numeric_re = re.compile('^(\d+(\.\d+)*)')
 
632
 
 
633
    def _match_compatible(self, version, constraint, prefix):
 
634
        if version < constraint:
 
635
            return False
 
636
        m = self.numeric_re.match(str(constraint))
 
637
        if not m:
 
638
            logger.warning('Cannot compute compatible match for version %s '
 
639
                           ' and constraint %s', version, constraint)
 
640
            return True
 
641
        s = m.groups()[0]
 
642
        if '.' in s:
 
643
            s = s.rsplit('.', 1)[0]
 
644
        return _match_prefix(version, s)
 
645
 
 
646
#
 
647
#   Semantic versioning
 
648
#
 
649
 
 
650
_SEMVER_RE = re.compile(r'^(\d+)\.(\d+)\.(\d+)'
 
651
                        r'(-[a-z0-9]+(\.[a-z0-9-]+)*)?'
 
652
                        r'(\+[a-z0-9]+(\.[a-z0-9-]+)*)?$', re.I)
 
653
 
 
654
 
 
655
def is_semver(s):
 
656
    return _SEMVER_RE.match(s)
 
657
 
 
658
 
 
659
def _semantic_key(s):
 
660
    def make_tuple(s, absent):
 
661
        if s is None:
 
662
            result = (absent,)
 
663
        else:
 
664
            parts = s[1:].split('.')
 
665
            # We can't compare ints and strings on Python 3, so fudge it
 
666
            # by zero-filling numeric values so simulate a numeric comparison
 
667
            result = tuple([p.zfill(8) if p.isdigit() else p for p in parts])
 
668
        return result
 
669
 
 
670
    m = is_semver(s)
 
671
    if not m:
 
672
        raise UnsupportedVersionError(s)
 
673
    groups = m.groups()
 
674
    major, minor, patch = [int(i) for i in groups[:3]]
 
675
    # choose the '|' and '*' so that versions sort correctly
 
676
    pre, build = make_tuple(groups[3], '|'), make_tuple(groups[5], '*')
 
677
    return (major, minor, patch), pre, build
 
678
 
 
679
 
 
680
class SemanticVersion(Version):
 
681
    def parse(self, s):
 
682
        return _semantic_key(s)
 
683
 
 
684
    @property
 
685
    def is_prerelease(self):
 
686
        return self._parts[1][0] != '|'
 
687
 
 
688
 
 
689
class SemanticMatcher(Matcher):
 
690
    version_class = SemanticVersion
 
691
 
 
692
 
 
693
class VersionScheme(object):
 
694
    def __init__(self, key, matcher, suggester=None):
 
695
        self.key = key
 
696
        self.matcher = matcher
 
697
        self.suggester = suggester
 
698
 
 
699
    def is_valid_version(self, s):
 
700
        try:
 
701
            self.matcher.version_class(s)
 
702
            result = True
 
703
        except UnsupportedVersionError:
 
704
            result = False
 
705
        return result
 
706
 
 
707
    def is_valid_matcher(self, s):
 
708
        try:
 
709
            self.matcher(s)
 
710
            result = True
 
711
        except UnsupportedVersionError:
 
712
            result = False
 
713
        return result
 
714
 
 
715
    def is_valid_constraint_list(self, s):
 
716
        """
 
717
        Used for processing some metadata fields
 
718
        """
 
719
        return self.is_valid_matcher('dummy_name (%s)' % s)
 
720
 
 
721
    def suggest(self, s):
 
722
        if self.suggester is None:
 
723
            result = None
 
724
        else:
 
725
            result = self.suggester(s)
 
726
        return result
 
727
 
 
728
_SCHEMES = {
 
729
    'normalized': VersionScheme(_normalized_key, NormalizedMatcher,
 
730
                                _suggest_normalized_version),
 
731
    'legacy': VersionScheme(_legacy_key, LegacyMatcher, lambda self, s: s),
 
732
    'semantic': VersionScheme(_semantic_key, SemanticMatcher,
 
733
                              _suggest_semantic_version),
 
734
}
 
735
 
 
736
_SCHEMES['default'] = _SCHEMES['normalized']
 
737
 
 
738
 
 
739
def get_scheme(name):
 
740
    if name not in _SCHEMES:
 
741
        raise ValueError('unknown scheme name: %r' % name)
 
742
    return _SCHEMES[name]