Skip to content

Commit 3c3388d

Browse files
tomschrgsakkisppkt
committed
Implement of next_version() function
* Implement semver.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 <github@ppkt.eu>
1 parent 3f92aa5 commit 3c3388d

File tree

3 files changed

+213
-12
lines changed

3 files changed

+213
-12
lines changed

docs/usage.rst

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,8 @@ Depending which function you call, you get different types
213213
{'major': 3, 'minor': 4, 'patch': 5, 'prerelease': None, 'build': None}
214214

215215

216-
Increasing Parts of a Version
217-
-----------------------------
216+
Raising Parts of a Version
217+
--------------------------
218218

219219
The ``semver`` module contains the following functions to raise parts of
220220
a version:
@@ -243,6 +243,57 @@ a version:
243243
'3.4.5-pre.2+build.5'
244244
245245
246+
Increasing Parts of a Version Taking into Account Prereleases
247+
-------------------------------------------------------------
248+
249+
.. versionadded:: 2.9.2
250+
Added :func:`semver.next_version`.
251+
252+
If you want to raise your version and take prereleases into account,
253+
the :func:`semver.next_version` or :func:`semver.VersionInfo.next_version`
254+
would perhaps a better fit.
255+
256+
257+
.. code-block:: python
258+
259+
>>> semver.next_version("3.4.5-pre.2+build.4", part="prerelease"))
260+
'3.4.5-pre.3'
261+
>>> semver.next_version("3.4.5-pre.2+build.4", part="patch")
262+
'3.4.5'
263+
>>> semver.next_version("3.4.5+build.4", part="patch")
264+
'3.4.5'
265+
>>> semver.next_version("0.1.4", "prerelease")
266+
'0.1.5-rc.1'
267+
268+
269+
Increasing Parts of a Version and Taking Prereleases into Account
270+
-----------------------------------------------------------------
271+
272+
.. versionadded:: 2.9.2
273+
Added :func:`semver.next_version`.
274+
275+
If you want to raise your version and take prereleases into account,
276+
the :func:`semver.next_version` or :func:`semver.VersionInfo.next_version`
277+
would perhaps a better fit.
278+
279+
The function requires two arguments: a version string and a string which
280+
parts to raise. Currently, only ``major``, ``minor``, ``patch``, and
281+
``prerelease`` are allowed. The ``build`` part is currently undefined and
282+
as such not allowed.
283+
284+
285+
.. code-block:: python
286+
287+
>>> semver.next_version("3.4.5-pre.2+build.4", part="prerelease"))
288+
'3.4.5-pre.3'
289+
>>> semver.next_version("3.4.5-pre.2+build.4", part="patch")
290+
'3.5.0'
291+
>>> semver.next_version("3.4.5+build.4", part="patch")
292+
'3.4.5'
293+
>>> semver.next_version("0.1.4", "prerelease")
294+
'0.1.5-rc.1'
295+
296+
246297
Comparing Versions
247298
------------------
248299

@@ -401,8 +452,7 @@ For example:
401452

402453
.. code-block:: python
403454
404-
>>> coerce("v1.2")
455+
>>> coerce("v1.2")
405456
(VersionInfo(major=1, minor=2, patch=0, prerelease=None, build=None), '')
406457
>>> coerce("v2.5.2-bla")
407458
(VersionInfo(major=2, minor=5, patch=2, prerelease=None, build=None), '-bla')
408-

semver.py

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,26 @@ def bump_build(self, token="build"):
257257
"""
258258
return parse_version_info(bump_build(str(self), token))
259259

260+
def next_version(self, part, prerelease_token="rc"):
261+
"""
262+
Determines the next version, taking prereleases into account.
263+
264+
The "major", "minor", and "patch" raises the respective parts like
265+
the ``bump_*`` functions. The real difference is using the
266+
"preprelease" part. It gives you the next patch version of the prerelease,
267+
for example:
268+
269+
>>> semver.next_version("0.1.4", "prerelease")
270+
'0.1.5-rc.1'
271+
272+
:param version: a semver version string
273+
:param part: One of "major", "minor", "patch", or "prerelease"
274+
:param prerelease_token: prefix string of prerelease, defaults to 'rc'
275+
:return:
276+
"""
277+
nxt = next_version(str(self), part, prerelease_token)
278+
return parse_version_info(nxt)
279+
260280
@comparator
261281
def __eq__(self, other):
262282
return _compare_by_keys(self._asdict(), _to_dict(other)) == 0
@@ -374,14 +394,7 @@ def parse_version_info(version):
374394
'build.4'
375395
"""
376396
parts = parse(version)
377-
version_info = VersionInfo(
378-
parts["major"],
379-
parts["minor"],
380-
parts["patch"],
381-
parts["prerelease"],
382-
parts["build"],
383-
)
384-
397+
version_info = VersionInfo(**parts)
385398
return version_info
386399

387400

@@ -684,6 +697,53 @@ def finalize_version(version):
684697
return format_version(verinfo["major"], verinfo["minor"], verinfo["patch"])
685698

686699

700+
def next_version(version, part, prerelease_token="rc"):
701+
"""
702+
Determines the next version, taking prereleases into account.
703+
704+
The "major", "minor", and "patch" raises the respective parts like
705+
the ``bump_*`` functions. The real difference is using the
706+
"preprelease" part. It gives you the next patch version of the prerelease,
707+
for example:
708+
709+
>>> semver.next_version("0.1.4", "prerelease")
710+
'0.1.5-rc.1'
711+
712+
:param version: a semver version string
713+
:param part: One of "major", "minor", "patch", or "prerelease"
714+
:param prerelease_token: prefix string of prerelease, defaults to 'rc'
715+
:return:
716+
"""
717+
validparts = {
718+
"major",
719+
"minor",
720+
"patch",
721+
"prerelease",
722+
# "build", # ???
723+
}
724+
if part not in validparts:
725+
raise ValueError(
726+
"Invalid part. Expected one of {validparts}, but got {part!r}".format(
727+
validparts=validparts, part=part
728+
)
729+
)
730+
version = VersionInfo.parse(version)
731+
if (version.prerelease or version.build) and (
732+
part == "patch"
733+
or (part == "minor" and version.patch == 0)
734+
or (part == "major" and version.minor == version.patch == 0)
735+
):
736+
return str(version.replace(prerelease=None, build=None))
737+
738+
if part in ("major", "minor", "patch"):
739+
return str(getattr(version, "bump_" + part)())
740+
741+
if not version.prerelease:
742+
version = version.bump_patch()
743+
return str(version.bump_prerelease(prerelease_token))
744+
745+
746+
# ---- CLI
687747
def cmd_bump(args):
688748
"""
689749
Subcommand: Bumps a version.
@@ -739,6 +799,18 @@ def cmd_compare(args):
739799
return str(compare(args.version1, args.version2))
740800

741801

802+
def cmd_nextver(args):
803+
"""
804+
Subcommand: Determines the next version, taking prereleases into account.
805+
806+
Synopsis: nextver <VERSION> <PART>
807+
808+
:param args: The parsed arguments
809+
:type args: :class:`argparse.Namespace`
810+
"""
811+
return str(next_version(args.version, args.part))
812+
813+
742814
def createparser():
743815
"""
744816
Create an :class:`argparse.ArgumentParser` instance.
@@ -781,6 +853,15 @@ def createparser():
781853
parser_check.set_defaults(func=cmd_check)
782854
parser_check.add_argument("version", help="Version to check")
783855

856+
# Create the nextver subcommand
857+
parser_nextver = s.add_parser(
858+
"nextver", help="Determines the next version, taking prereleases into account."
859+
)
860+
parser_nextver.set_defaults(func=cmd_nextver)
861+
parser_nextver.add_argument("version", help="Version to raise")
862+
parser_nextver.add_argument(
863+
"part", help="One of 'major', 'minor', 'patch', or 'prerelease'"
864+
)
784865
return parser
785866

786867

test_semver.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
match,
2121
max_ver,
2222
min_ver,
23+
next_version,
2324
parse,
2425
parse_version_info,
2526
process,
@@ -39,6 +40,7 @@
3940
match,
4041
max_ver,
4142
min_ver,
43+
next_version,
4244
parse,
4345
process,
4446
replace,
@@ -827,3 +829,71 @@ def test_replace_raises_ValueError_for_non_numeric_values():
827829
def test_should_versioninfo_isvalid():
828830
assert VersionInfo.isvalid("1.0.0") is True
829831
assert VersionInfo.isvalid("foo") is False
832+
833+
834+
@pytest.mark.parametrize(
835+
"version, part, expected",
836+
[
837+
# major
838+
("1.0.4-rc.1", "major", "2.0.0"),
839+
("1.1.0-rc.1", "major", "2.0.0"),
840+
("1.1.4-rc.1", "major", "2.0.0"),
841+
("1.2.3", "major", "2.0.0"),
842+
("1.0.0-rc.1", "major", "1.0.0"),
843+
# minor
844+
("0.2.0-rc.1", "minor", "0.2.0"),
845+
("0.2.5-rc.1", "minor", "0.3.0"),
846+
("1.3.1", "minor", "1.4.0"),
847+
# patch
848+
("1.3.2", "patch", "1.3.3"),
849+
("0.1.5-rc.2", "patch", "0.1.5"),
850+
# prerelease
851+
("0.1.4", "prerelease", "0.1.5-rc.1"),
852+
("0.1.5-rc.1", "prerelease", "0.1.5-rc.2"),
853+
# special cases
854+
("0.2.0-rc.1", "patch", "0.2.0"), # same as "minor"
855+
("1.0.0-rc.1", "patch", "1.0.0"), # same as "major"
856+
("1.0.0-rc.1", "minor", "1.0.0"), # same as "major"
857+
#
858+
("3.4.5-pre.2+build.4", "prerelease", "3.4.5-pre.3"),
859+
("3.4.5-pre.2+build.4", "patch", "3.4.5"),
860+
("3.4.5+build.4", "patch", "3.4.5"),
861+
],
862+
)
863+
def test_next_version(version, part, expected):
864+
assert next_version(version, part) == expected
865+
866+
867+
def test_next_version_with_invalid_parts():
868+
with pytest.raises(ValueError):
869+
next_version("1.0.1", "invalid")
870+
871+
872+
@pytest.mark.parametrize(
873+
"version, part, expected",
874+
[
875+
# major
876+
("1.0.4-rc.1", "major", "2.0.0"),
877+
("1.1.0-rc.1", "major", "2.0.0"),
878+
("1.1.4-rc.1", "major", "2.0.0"),
879+
("1.2.3", "major", "2.0.0"),
880+
("1.0.0-rc.1", "major", "1.0.0"),
881+
# minor
882+
("0.2.0-rc.1", "minor", "0.2.0"),
883+
("0.2.5-rc.1", "minor", "0.3.0"),
884+
("1.3.1", "minor", "1.4.0"),
885+
# patch
886+
("1.3.2", "patch", "1.3.3"),
887+
("0.1.5-rc.2", "patch", "0.1.5"),
888+
# prerelease
889+
("0.1.4", "prerelease", "0.1.5-rc.1"),
890+
("0.1.5-rc.1", "prerelease", "0.1.5-rc.2"),
891+
# special cases
892+
("0.2.0-rc.1", "patch", "0.2.0"), # same as "minor"
893+
("1.0.0-rc.1", "patch", "1.0.0"), # same as "major"
894+
("1.0.0-rc.1", "minor", "1.0.0"), # same as "major"
895+
],
896+
)
897+
def test_next_version_with_versioninfo(version, part, expected):
898+
ver = VersionInfo.parse(version)
899+
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