29
29
Comparator = Callable [["Version" , Comparable ], bool ]
30
30
31
31
32
- def cmp (a , b ): # TODO: type hints
33
- """Return negative if a<b, zero if a==b, positive if a>b."""
34
- return (a > b ) - (a < b )
35
-
36
-
37
- def ensure_str (s : String , encoding = "utf-8" , errors = "strict" ) -> str :
38
- # Taken from six project
39
- """
40
- Coerce *s* to `str`.
41
-
42
- * `str` -> `str`
43
- * `bytes` -> decoded to `str`
44
-
45
- :param s: the string to convert
46
- :param encoding: the encoding to apply, defaults to "utf-8"
47
- :param errors: set a different error handling scheme,
48
- defaults to "strict".
49
- Other possible values are `ignore`, `replace`, and
50
- `xmlcharrefreplace` as well as any other name
51
- registered with :func:`codecs.register_error`.
52
- :raises TypeError: if ``s`` is not str or bytes type
53
- :return: the converted string
54
- """
55
- if isinstance (s , bytes ):
56
- s = s .decode (encoding , errors )
57
- elif not isinstance (s , String .__args__ ): # type: ignore
58
- raise TypeError ("not expecting type '%s'" % type (s ))
59
- return s
60
-
61
-
62
- def comparator (operator : Comparator ) -> Comparator :
32
+ def _comparator (operator : Comparator ) -> Comparator :
63
33
"""Wrap a Version binary op method in a type-check."""
64
34
65
35
@wraps (operator )
@@ -78,31 +48,9 @@ def wrapper(self: "Version", other: Comparable) -> bool:
78
48
return wrapper
79
49
80
50
81
- def _nat_cmp (a , b ): # TODO: type hints
82
- def convert (text ):
83
- return int (text ) if re .match ("^[0-9]+$" , text ) else text
84
-
85
- def split_key (key ):
86
- return [convert (c ) for c in key .split ("." )]
87
-
88
- def cmp_prerelease_tag (a , b ):
89
- if isinstance (a , int ) and isinstance (b , int ):
90
- return cmp (a , b )
91
- elif isinstance (a , int ):
92
- return - 1
93
- elif isinstance (b , int ):
94
- return 1
95
- else :
96
- return cmp (a , b )
97
-
98
- a , b = a or "" , b or ""
99
- a_parts , b_parts = split_key (a ), split_key (b )
100
- for sub_a , sub_b in zip (a_parts , b_parts ):
101
- cmp_result = cmp_prerelease_tag (sub_a , sub_b )
102
- if cmp_result != 0 :
103
- return cmp_result
104
- else :
105
- return cmp (len (a ), len (b ))
51
+ def _cmp (a , b ): # TODO: type hints
52
+ """Return negative if a<b, zero if a==b, positive if a>b."""
53
+ return (a > b ) - (a < b )
106
54
107
55
108
56
class Version :
@@ -165,6 +113,29 @@ def __init__(
165
113
self ._prerelease = None if prerelease is None else str (prerelease )
166
114
self ._build = None if build is None else str (build )
167
115
116
+ @classmethod
117
+ def _nat_cmp (cls , a , b ): # TODO: type hints
118
+ def cmp_prerelease_tag (a , b ):
119
+ if isinstance (a , int ) and isinstance (b , int ):
120
+ return _cmp (a , b )
121
+ elif isinstance (a , int ):
122
+ return - 1
123
+ elif isinstance (b , int ):
124
+ return 1
125
+ else :
126
+ return _cmp (a , b )
127
+
128
+ a , b = a or "" , b or ""
129
+ a_parts , b_parts = a .split ("." ), b .split ("." )
130
+ a_parts = [int (x ) if re .match (r"^\d+$" , x ) else x for x in a_parts ]
131
+ b_parts = [int (x ) if re .match (r"^\d+$" , x ) else x for x in b_parts ]
132
+ for sub_a , sub_b in zip (a_parts , b_parts ):
133
+ cmp_result = cmp_prerelease_tag (sub_a , sub_b )
134
+ if cmp_result != 0 :
135
+ return cmp_result
136
+ else :
137
+ return _cmp (len (a ), len (b ))
138
+
168
139
@property
169
140
def major (self ) -> int :
170
141
"""The major part of a version (read-only)."""
@@ -381,12 +352,12 @@ def compare(self, other: Comparable) -> int:
381
352
382
353
v1 = self .to_tuple ()[:3 ]
383
354
v2 = other .to_tuple ()[:3 ]
384
- x = cmp (v1 , v2 )
355
+ x = _cmp (v1 , v2 )
385
356
if x :
386
357
return x
387
358
388
359
rc1 , rc2 = self .prerelease , other .prerelease
389
- rccmp = _nat_cmp (rc1 , rc2 )
360
+ rccmp = self . _nat_cmp (rc1 , rc2 )
390
361
391
362
if not rccmp :
392
363
return 0
@@ -444,27 +415,27 @@ def next_version(self, part: str, prerelease_token: str = "rc") -> "Version":
444
415
version = version .bump_patch ()
445
416
return version .bump_prerelease (prerelease_token )
446
417
447
- @comparator
418
+ @_comparator
448
419
def __eq__ (self , other : Comparable ) -> bool : # type: ignore
449
420
return self .compare (other ) == 0
450
421
451
- @comparator
422
+ @_comparator
452
423
def __ne__ (self , other : Comparable ) -> bool : # type: ignore
453
424
return self .compare (other ) != 0
454
425
455
- @comparator
426
+ @_comparator
456
427
def __lt__ (self , other : Comparable ) -> bool :
457
428
return self .compare (other ) < 0
458
429
459
- @comparator
430
+ @_comparator
460
431
def __le__ (self , other : Comparable ) -> bool :
461
432
return self .compare (other ) <= 0
462
433
463
- @comparator
434
+ @_comparator
464
435
def __gt__ (self , other : Comparable ) -> bool :
465
436
return self .compare (other ) > 0
466
437
467
- @comparator
438
+ @_comparator
468
439
def __ge__ (self , other : Comparable ) -> bool :
469
440
return self .compare (other ) >= 0
470
441
@@ -593,15 +564,20 @@ def parse(cls, version: String) -> "Version":
593
564
:param version: version string
594
565
:return: a new :class:`Version` instance
595
566
:raises ValueError: if version is invalid
567
+ :raises TypeError: if version contains the wrong type
596
568
597
569
>>> semver.Version.parse('3.4.5-pre.2+build.4')
598
570
Version(major=3, minor=4, patch=5, \
599
571
prerelease='pre.2', build='build.4')
600
572
"""
601
- version_str = ensure_str (version )
602
- match = cls ._REGEX .match (version_str )
573
+ if isinstance (version , bytes ):
574
+ version = version .decode ("UTF-8" )
575
+ elif not isinstance (version , String .__args__ ): # type: ignore
576
+ raise TypeError ("not expecting type '%s'" % type (version ))
577
+
578
+ match = cls ._REGEX .match (version )
603
579
if match is None :
604
- raise ValueError (f"{ version_str } is not valid SemVer string" )
580
+ raise ValueError (f"{ version } is not valid SemVer string" )
605
581
606
582
matched_version_parts : Dict [str , Any ] = match .groupdict ()
607
583
0 commit comments