Skip to content

Commit 477e8f2

Browse files
tomschrgsakkisppkt
committed
Implement of VersionInfo.next_version() function
Synopsis: semver.VersionInfo.next_version(version, part, prerelease_token="rc") * Add test cases * test_next_version * test_next_version_with_invalid_parts * Reformat code with black * Document it in usage.rst * Implement "nextver" subcommand for pysemver command Co-authored-by: George Sakkis <gsakkis@users.noreply.github.com> Co-authored-By: Karol <ppkt@users.noreply.github.com>
1 parent d69e7c4 commit 477e8f2

File tree

3 files changed

+129
-2
lines changed

3 files changed

+129
-2
lines changed

docs/usage.rst

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ It is possible to convert a :class:`semver.VersionInfo` instance:
244244
(5, 4, 2, None, None)
245245

246246

247-
Increasing Parts of a Version
248-
-----------------------------
247+
Raising Parts of a Version
248+
--------------------------
249249

250250
The ``semver`` module contains the following functions to raise parts of
251251
a version:
@@ -276,6 +276,30 @@ a version:
276276
Likewise the module level functions :func:`semver.bump_major`.
277277

278278

279+
Increasing Parts of a Version Taking into Account Prereleases
280+
-------------------------------------------------------------
281+
282+
.. versionadded:: 2.9.2
283+
Added :func:`semver.VersionInfo.next_version`.
284+
285+
If you want to raise your version and take prereleases into account,
286+
the function :func:`semver.VersionInfo.next_version` would perhaps a
287+
better fit.
288+
289+
290+
.. code-block:: python
291+
292+
>>> v = semver.VersionInfo.parse("3.4.5-pre.2+build.4")
293+
>>> str(v.next_version(part="prerelease"))
294+
'3.4.5-pre.3'
295+
>>> str(semver.VersionInfo.parse("3.4.5-pre.2+build.4").next_version(part="patch"))
296+
'3.4.5'
297+
>>> str(semver.VersionInfo.parse("3.4.5+build.4").next_version(part="patch"))
298+
'3.4.5'
299+
>>> str(semver.VersionInfo.parse("0.1.4").next_version("prerelease"))
300+
'0.1.5-rc.1'
301+
302+
279303
Comparing Versions
280304
------------------
281305

semver.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,50 @@ def compare(self, other):
422422

423423
return rccmp
424424

425+
def next_version(self, part, prerelease_token="rc"):
426+
"""
427+
Determines the next version, taking prereleases into account.
428+
429+
The "major", "minor", and "patch" raises the respective parts like
430+
the ``bump_*`` functions. The real difference is using the
431+
"preprelease" part. It gives you the next patch version of the prerelease,
432+
for example:
433+
434+
>>> str(semver.VersionInfo.parse("0.1.4").next_version("prerelease"))
435+
'0.1.5-rc.1'
436+
437+
:param part: One of "major", "minor", "patch", or "prerelease"
438+
:param prerelease_token: prefix string of prerelease, defaults to 'rc'
439+
:return:
440+
"""
441+
validparts = {
442+
"major",
443+
"minor",
444+
"patch",
445+
"prerelease",
446+
# "build", # currently not used
447+
}
448+
if part not in validparts:
449+
raise ValueError(
450+
"Invalid part. Expected one of {validparts}, but got {part!r}".format(
451+
validparts=validparts, part=part
452+
)
453+
)
454+
version = self
455+
if (version.prerelease or version.build) and (
456+
part == "patch"
457+
or (part == "minor" and version.patch == 0)
458+
or (part == "major" and version.minor == version.patch == 0)
459+
):
460+
return version.replace(prerelease=None, build=None)
461+
462+
if part in ("major", "minor", "patch"):
463+
return str(getattr(version, "bump_" + part)())
464+
465+
if not version.prerelease:
466+
version = version.bump_patch()
467+
return version.bump_prerelease(prerelease_token)
468+
425469
@comparator
426470
def __eq__(self, other):
427471
return self.compare(other) == 0
@@ -898,6 +942,7 @@ def replace(version, **parts):
898942
return str(VersionInfo.parse(version).replace(**parts))
899943

900944

945+
# ---- CLI
901946
def cmd_bump(args):
902947
"""
903948
Subcommand: Bumps a version.
@@ -953,6 +998,19 @@ def cmd_compare(args):
953998
return str(compare(args.version1, args.version2))
954999

9551000

1001+
def cmd_nextver(args):
1002+
"""
1003+
Subcommand: Determines the next version, taking prereleases into account.
1004+
1005+
Synopsis: nextver <VERSION> <PART>
1006+
1007+
:param args: The parsed arguments
1008+
:type args: :class:`argparse.Namespace`
1009+
"""
1010+
version = VersionInfo.parse(args.version)
1011+
return str(version.next_version(args.part))
1012+
1013+
9561014
def createparser():
9571015
"""
9581016
Create an :class:`argparse.ArgumentParser` instance.
@@ -995,6 +1053,15 @@ def createparser():
9951053
parser_check.set_defaults(func=cmd_check)
9961054
parser_check.add_argument("version", help="Version to check")
9971055

1056+
# Create the nextver subcommand
1057+
parser_nextver = s.add_parser(
1058+
"nextver", help="Determines the next version, taking prereleases into account."
1059+
)
1060+
parser_nextver.set_defaults(func=cmd_nextver)
1061+
parser_nextver.add_argument("version", help="Version to raise")
1062+
parser_nextver.add_argument(
1063+
"part", help="One of 'major', 'minor', 'patch', or 'prerelease'"
1064+
)
9981065
return parser
9991066

10001067

test_semver.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,3 +881,39 @@ def mock_func():
881881

882882
with pytest.deprecated_call():
883883
assert mock_func()
884+
885+
886+
def test_next_version_with_invalid_parts():
887+
version = VersionInfo.parse("1.0.1")
888+
with pytest.raises(ValueError):
889+
version.next_version("invalid")
890+
891+
892+
@pytest.mark.parametrize(
893+
"version, part, expected",
894+
[
895+
# major
896+
("1.0.4-rc.1", "major", "2.0.0"),
897+
("1.1.0-rc.1", "major", "2.0.0"),
898+
("1.1.4-rc.1", "major", "2.0.0"),
899+
("1.2.3", "major", "2.0.0"),
900+
("1.0.0-rc.1", "major", "1.0.0"),
901+
# minor
902+
("0.2.0-rc.1", "minor", "0.2.0"),
903+
("0.2.5-rc.1", "minor", "0.3.0"),
904+
("1.3.1", "minor", "1.4.0"),
905+
# patch
906+
("1.3.2", "patch", "1.3.3"),
907+
("0.1.5-rc.2", "patch", "0.1.5"),
908+
# prerelease
909+
("0.1.4", "prerelease", "0.1.5-rc.1"),
910+
("0.1.5-rc.1", "prerelease", "0.1.5-rc.2"),
911+
# special cases
912+
("0.2.0-rc.1", "patch", "0.2.0"), # same as "minor"
913+
("1.0.0-rc.1", "patch", "1.0.0"), # same as "major"
914+
("1.0.0-rc.1", "minor", "1.0.0"), # same as "major"
915+
],
916+
)
917+
def test_next_version_with_versioninfo(version, part, expected):
918+
ver = VersionInfo.parse(version)
919+
assert str(ver.next_version(part)) == expected

0 commit comments

Comments
 (0)