14
14
__maintainer__ = ["Sebastien Celles" , "Tom Schraitle" ]
15
15
__maintainer_email__ = "s.celles@gmail.com"
16
16
17
- _REGEX = re .compile (
18
- r"""
19
- ^
20
- (?P<major>0|[1-9]\d*)
21
- \.
22
- (?P<minor>0|[1-9]\d*)
23
- \.
24
- (?P<patch>0|[1-9]\d*)
25
- (?:-(?P<prerelease>
26
- (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
27
- (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
28
- ))?
29
- (?:\+(?P<build>
30
- [0-9a-zA-Z-]+
31
- (?:\.[0-9a-zA-Z-]+)*
32
- ))?
33
- $
34
- """ ,
35
- re .VERBOSE ,
36
- )
37
17
38
18
_LAST_NUMBER = re .compile (r"(?:[^\d]*(\d+)[^\d]*)+" )
39
19
@@ -69,17 +49,7 @@ def parse(version):
69
49
>>> ver['build']
70
50
'build.4'
71
51
"""
72
- match = _REGEX .match (version )
73
- if match is None :
74
- raise ValueError ("%s is not valid SemVer string" % version )
75
-
76
- version_parts = match .groupdict ()
77
-
78
- version_parts ["major" ] = int (version_parts ["major" ])
79
- version_parts ["minor" ] = int (version_parts ["minor" ])
80
- version_parts ["patch" ] = int (version_parts ["patch" ])
81
-
82
- return version_parts
52
+ return VersionInfo .parse (version )._asdict ()
83
53
84
54
85
55
def comparator (operator ):
@@ -110,6 +80,26 @@ class VersionInfo(object):
110
80
"""
111
81
112
82
__slots__ = ("_major" , "_minor" , "_patch" , "_prerelease" , "_build" )
83
+ _REGEX = re .compile (
84
+ r"""
85
+ ^
86
+ (?P<major>0|[1-9]\d*)
87
+ \.
88
+ (?P<minor>0|[1-9]\d*)
89
+ \.
90
+ (?P<patch>0|[1-9]\d*)
91
+ (?:-(?P<prerelease>
92
+ (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
93
+ (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
94
+ ))?
95
+ (?:\+(?P<build>
96
+ [0-9a-zA-Z-]+
97
+ (?:\.[0-9a-zA-Z-]+)*
98
+ ))?
99
+ $
100
+ """ ,
101
+ re .VERBOSE ,
102
+ )
113
103
114
104
def __init__ (self , major , minor = 0 , patch = 0 , prerelease = None , build = None ):
115
105
self ._major = int (major )
@@ -195,7 +185,8 @@ def bump_major(self):
195
185
>>> ver.bump_major()
196
186
VersionInfo(major=4, minor=0, patch=0, prerelease=None, build=None)
197
187
"""
198
- return parse_version_info (bump_major (str (self )))
188
+ cls = type (self )
189
+ return cls (self ._major + 1 )
199
190
200
191
def bump_minor (self ):
201
192
"""
@@ -209,7 +200,8 @@ def bump_minor(self):
209
200
>>> ver.bump_minor()
210
201
VersionInfo(major=3, minor=5, patch=0, prerelease=None, build=None)
211
202
"""
212
- return parse_version_info (bump_minor (str (self )))
203
+ cls = type (self )
204
+ return cls (self ._major , self ._minor + 1 )
213
205
214
206
def bump_patch (self ):
215
207
"""
@@ -223,7 +215,8 @@ def bump_patch(self):
223
215
>>> ver.bump_patch()
224
216
VersionInfo(major=3, minor=4, patch=6, prerelease=None, build=None)
225
217
"""
226
- return parse_version_info (bump_patch (str (self )))
218
+ cls = type (self )
219
+ return cls (self ._major , self ._minor , self ._patch + 1 )
227
220
228
221
def bump_prerelease (self , token = "rc" ):
229
222
"""
@@ -239,7 +232,11 @@ def bump_prerelease(self, token="rc"):
239
232
VersionInfo(major=3, minor=4, patch=5, prerelease='rc.2', \
240
233
build=None)
241
234
"""
242
- return parse_version_info (bump_prerelease (str (self ), token ))
235
+ cls = type (self )
236
+ prerelease = _increment_string (
237
+ self ._prerelease or (token or "rc" ) + ".0"
238
+ )
239
+ return cls (self ._major , self ._minor , self ._patch , prerelease )
243
240
244
241
def bump_build (self , token = "build" ):
245
242
"""
@@ -255,7 +252,11 @@ def bump_build(self, token="build"):
255
252
VersionInfo(major=3, minor=4, patch=5, prerelease='rc.1', \
256
253
build='build.10')
257
254
"""
258
- return parse_version_info (bump_build (str (self ), token ))
255
+ cls = type (self )
256
+ build = _increment_string (
257
+ self ._build or (token or "build" ) + ".0"
258
+ )
259
+ return cls (self ._major , self ._minor , self ._patch , self ._prerelease , build )
259
260
260
261
@comparator
261
262
def __eq__ (self , other ):
@@ -286,7 +287,7 @@ def __repr__(self):
286
287
return "%s(%s)" % (type (self ).__name__ , s )
287
288
288
289
def __str__ (self ):
289
- return format_version (* (self ._astuple ()))
290
+ return VersionInfo . format_version (* (self ._astuple ()))
290
291
291
292
def __hash__ (self ):
292
293
return hash (self ._astuple ())
@@ -304,7 +305,17 @@ def parse(version):
304
305
VersionInfo(major=3, minor=4, patch=5, \
305
306
prerelease='pre.2', build='build.4')
306
307
"""
307
- return parse_version_info (version )
308
+ match = VersionInfo ._REGEX .match (version )
309
+ if match is None :
310
+ raise ValueError ("%s is not valid SemVer string" % version )
311
+
312
+ version_parts = match .groupdict ()
313
+
314
+ version_parts ["major" ] = int (version_parts ["major" ])
315
+ version_parts ["minor" ] = int (version_parts ["minor" ])
316
+ version_parts ["patch" ] = int (version_parts ["patch" ])
317
+
318
+ return VersionInfo (** version_parts )
308
319
309
320
def replace (self , ** parts ):
310
321
"""Replace one or more parts of a version and return a new
@@ -344,6 +355,31 @@ def isvalid(cls, version):
344
355
except ValueError :
345
356
return False
346
357
358
+ @staticmethod
359
+ def format_version (major , minor , patch , prerelease = None , build = None ):
360
+ """
361
+ Format a version according to the Semantic Versioning specification.
362
+
363
+ :param int major: the required major part of a version
364
+ :param int minor: the required minor part of a version
365
+ :param int patch: the required patch part of a version
366
+ :param str prerelease: the optional prerelease part of a version
367
+ :param str build: the optional build part of a version
368
+ :return: the formatted string
369
+ :rtype: str
370
+
371
+ >>> semver.VersionInfo.format_version(3, 4, 5, 'pre.2', 'build.4')
372
+ '3.4.5-pre.2+build.4'
373
+ """
374
+ version = "%d.%d.%d" % (major , minor , patch )
375
+ if prerelease is not None :
376
+ version = version + "-%s" % prerelease
377
+
378
+ if build is not None :
379
+ version = version + "+%s" % build
380
+
381
+ return version
382
+
347
383
348
384
def _to_dict (obj ):
349
385
if isinstance (obj , VersionInfo ):
@@ -373,16 +409,7 @@ def parse_version_info(version):
373
409
>>> version_info.build
374
410
'build.4'
375
411
"""
376
- parts = parse (version )
377
- version_info = VersionInfo (
378
- parts ["major" ],
379
- parts ["minor" ],
380
- parts ["patch" ],
381
- parts ["prerelease" ],
382
- parts ["build" ],
383
- )
384
-
385
- return version_info
412
+ return VersionInfo .parse (version )
386
413
387
414
388
415
def _nat_cmp (a , b ):
@@ -449,7 +476,8 @@ def compare(ver1, ver2):
449
476
0
450
477
"""
451
478
452
- v1 , v2 = parse (ver1 ), parse (ver2 )
479
+ v1 = VersionInfo .parse (ver1 )._asdict ()
480
+ v2 = VersionInfo .parse (ver2 )._asdict ()
453
481
454
482
return _compare_by_keys (v1 , v2 )
455
483
@@ -556,14 +584,7 @@ def format_version(major, minor, patch, prerelease=None, build=None):
556
584
>>> semver.format_version(3, 4, 5, 'pre.2', 'build.4')
557
585
'3.4.5-pre.2+build.4'
558
586
"""
559
- version = "%d.%d.%d" % (major , minor , patch )
560
- if prerelease is not None :
561
- version = version + "-%s" % prerelease
562
-
563
- if build is not None :
564
- version = version + "+%s" % build
565
-
566
- return version
587
+ return VersionInfo .format_version (major , minor , patch , prerelease , build )
567
588
568
589
569
590
def _increment_string (string ):
@@ -591,8 +612,7 @@ def bump_major(version):
591
612
>>> semver.bump_major("3.4.5")
592
613
'4.0.0'
593
614
"""
594
- verinfo = parse (version )
595
- return format_version (verinfo ["major" ] + 1 , 0 , 0 )
615
+ return str (VersionInfo .parse (version ).bump_major ())
596
616
597
617
598
618
def bump_minor (version ):
@@ -606,8 +626,7 @@ def bump_minor(version):
606
626
>>> semver.bump_minor("3.4.5")
607
627
'3.5.0'
608
628
"""
609
- verinfo = parse (version )
610
- return format_version (verinfo ["major" ], verinfo ["minor" ] + 1 , 0 )
629
+ return str (VersionInfo .parse (version ).bump_minor ())
611
630
612
631
613
632
def bump_patch (version ):
@@ -621,8 +640,7 @@ def bump_patch(version):
621
640
>>> semver.bump_patch("3.4.5")
622
641
'3.4.6'
623
642
"""
624
- verinfo = parse (version )
625
- return format_version (verinfo ["major" ], verinfo ["minor" ], verinfo ["patch" ] + 1 )
643
+ return str (VersionInfo .parse (version ).bump_patch ())
626
644
627
645
628
646
def bump_prerelease (version , token = "rc" ):
@@ -637,13 +655,7 @@ def bump_prerelease(version, token="rc"):
637
655
>>> semver.bump_prerelease('3.4.5', 'dev')
638
656
'3.4.5-dev.1'
639
657
"""
640
- verinfo = parse (version )
641
- verinfo ["prerelease" ] = _increment_string (
642
- verinfo ["prerelease" ] or (token or "rc" ) + ".0"
643
- )
644
- return format_version (
645
- verinfo ["major" ], verinfo ["minor" ], verinfo ["patch" ], verinfo ["prerelease" ]
646
- )
658
+ return str (VersionInfo .parse (version ).bump_prerelease (token ))
647
659
648
660
649
661
def bump_build (version , token = "build" ):
@@ -658,15 +670,7 @@ def bump_build(version, token="build"):
658
670
>>> semver.bump_build('3.4.5-rc.1+build.9')
659
671
'3.4.5-rc.1+build.10'
660
672
"""
661
- verinfo = parse (version )
662
- verinfo ["build" ] = _increment_string (verinfo ["build" ] or (token or "build" ) + ".0" )
663
- return format_version (
664
- verinfo ["major" ],
665
- verinfo ["minor" ],
666
- verinfo ["patch" ],
667
- verinfo ["prerelease" ],
668
- verinfo ["build" ],
669
- )
673
+ return str (VersionInfo .parse (version ).bump_build (token ))
670
674
671
675
672
676
def finalize_version (version ):
@@ -680,8 +684,26 @@ def finalize_version(version):
680
684
>>> semver.finalize_version('1.2.3-rc.5')
681
685
'1.2.3'
682
686
"""
683
- verinfo = parse (version )
684
- return format_version (verinfo ["major" ], verinfo ["minor" ], verinfo ["patch" ])
687
+ verinfo = VersionInfo .parse (version )
688
+ return VersionInfo .format_version (verinfo .major , verinfo .minor , verinfo .patch )
689
+
690
+
691
+ def replace (version , ** parts ):
692
+ """
693
+ Replace one or more parts of a version and return the new string.
694
+
695
+ :param str version: the version string to replace
696
+ :param dict parts: the parts to be updated. Valid keys are:
697
+ ``major``, ``minor``, ``patch``, ``prerelease``, or ``build``
698
+ :return: the replaced version string
699
+ :raises: TypeError, if ``parts`` contains invalid keys
700
+ :rtype: str
701
+
702
+ >>> import semver
703
+ >>> semver.replace("1.2.3", major=2, patch=10)
704
+ '2.2.10'
705
+ """
706
+ return str (VersionInfo .parse (version ).replace (** parts ))
685
707
686
708
687
709
def cmd_bump (args ):
@@ -707,7 +729,7 @@ def cmd_bump(args):
707
729
# print the help and exit
708
730
args .parser .parse_args (["bump" , "-h" ])
709
731
710
- ver = parse_version_info (args .version )
732
+ ver = VersionInfo . parse (args .version )
711
733
# get the respective method and call it
712
734
func = getattr (ver , maptable [args .bump ])
713
735
return str (func ())
@@ -826,25 +848,6 @@ def main(cliargs=None):
826
848
return 2
827
849
828
850
829
- def replace (version , ** parts ):
830
- """
831
- Replace one or more parts of a version and return the new string.
832
-
833
- :param str version: the version string to replace
834
- :param dict parts: the parts to be updated. Valid keys are:
835
- ``major``, ``minor``, ``patch``, ``prerelease``, or ``build``
836
- :return: the replaced version string
837
- :raises: TypeError, if ``parts`` contains invalid keys
838
- :rtype: str
839
-
840
- >>> import semver
841
- >>> semver.replace("1.2.3", major=2, patch=10)
842
- '2.2.10'
843
- """
844
- version = parse_version_info (version )
845
- return str (version .replace (** parts ))
846
-
847
-
848
851
if __name__ == "__main__" :
849
852
import doctest
850
853
0 commit comments