Skip to content

Commit c0a1f14

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 c0a1f14

File tree

3 files changed

+130
-2
lines changed

3 files changed

+130
-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.10.0
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: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,51 @@ def compare(self, other):
422422

423423
return rccmp
424424

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

900945

946+
# ---- CLI
901947
def cmd_bump(args):
902948
"""
903949
Subcommand: Bumps a version.
@@ -953,6 +999,19 @@ def cmd_compare(args):
953999
return str(compare(args.version1, args.version2))
9541000

9551001

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

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

10001068

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy