Skip to content

Commit 85d4ff1

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 9691b41 commit 85d4ff1

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
@@ -371,6 +371,50 @@ def bump_build(self, token="build"):
371371
build = cls._increment_string(self._build or (token or "build") + ".0")
372372
return cls(self._major, self._minor, self._patch, self._prerelease, build)
373373

374+
def next_version(self, part, prerelease_token="rc"):
375+
"""
376+
Determines the next version, taking prereleases into account.
377+
378+
The "major", "minor", and "patch" raises the respective parts like
379+
the ``bump_*`` functions. The real difference is using the
380+
"preprelease" part. It gives you the next patch version of the prerelease,
381+
for example:
382+
383+
>>> str(semver.VersionInfo.parse("0.1.4").next_version("prerelease"))
384+
'0.1.5-rc.1'
385+
386+
:param part: One of "major", "minor", "patch", or "prerelease"
387+
:param prerelease_token: prefix string of prerelease, defaults to 'rc'
388+
:return:
389+
"""
390+
validparts = {
391+
"major",
392+
"minor",
393+
"patch",
394+
"prerelease",
395+
# "build", # currently not used
396+
}
397+
if part not in validparts:
398+
raise ValueError(
399+
"Invalid part. Expected one of {validparts}, but got {part!r}".format(
400+
validparts=validparts, part=part
401+
)
402+
)
403+
version = self
404+
if (version.prerelease or version.build) and (
405+
part == "patch"
406+
or (part == "minor" and version.patch == 0)
407+
or (part == "major" and version.minor == version.patch == 0)
408+
):
409+
return version.replace(prerelease=None, build=None)
410+
411+
if part in ("major", "minor", "patch"):
412+
return str(getattr(version, "bump_" + part)())
413+
414+
if not version.prerelease:
415+
version = version.bump_patch()
416+
return version.bump_prerelease(prerelease_token)
417+
374418
@comparator
375419
def __eq__(self, other):
376420
return _compare_by_keys(self.to_dict(), _to_dict(other)) == 0
@@ -851,6 +895,7 @@ def replace(version, **parts):
851895
return str(VersionInfo.parse(version).replace(**parts))
852896

853897

898+
# ---- CLI
854899
def cmd_bump(args):
855900
"""
856901
Subcommand: Bumps a version.
@@ -906,6 +951,19 @@ def cmd_compare(args):
906951
return str(compare(args.version1, args.version2))
907952

908953

954+
def cmd_nextver(args):
955+
"""
956+
Subcommand: Determines the next version, taking prereleases into account.
957+
958+
Synopsis: nextver <VERSION> <PART>
959+
960+
:param args: The parsed arguments
961+
:type args: :class:`argparse.Namespace`
962+
"""
963+
version = VersionInfo.parse(args.version)
964+
return str(version.next_version(args.part))
965+
966+
909967
def createparser():
910968
"""
911969
Create an :class:`argparse.ArgumentParser` instance.
@@ -948,6 +1006,15 @@ def createparser():
9481006
parser_check.set_defaults(func=cmd_check)
9491007
parser_check.add_argument("version", help="Version to check")
9501008

1009+
# Create the nextver subcommand
1010+
parser_nextver = s.add_parser(
1011+
"nextver", help="Determines the next version, taking prereleases into account."
1012+
)
1013+
parser_nextver.set_defaults(func=cmd_nextver)
1014+
parser_nextver.add_argument("version", help="Version to raise")
1015+
parser_nextver.add_argument(
1016+
"part", help="One of 'major', 'minor', 'patch', or 'prerelease'"
1017+
)
9511018
return parser
9521019

9531020

test_semver.py

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

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