Skip to content

Commit 5755f9a

Browse files
tomschrscls19fr
authored andcommitted
Improve pysemver subcommands (#214)
* Use separate cmd_* functions for each subcommand * Replace which with func keyword * Use func keyword to store the specific cmd_* function * Adapt tests * (Re)format with black * Clarify return code in manpage (add new section "Return Code") => Easier to extend
1 parent 177f080 commit 5755f9a

File tree

3 files changed

+118
-78
lines changed

3 files changed

+118
-78
lines changed

docs/pysemver.rst

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,17 +133,39 @@ to indicates which is the bigger version:
133133
* ``0`` if both versions are the same,
134134
* ``1`` if the first version is greater than the second version.
135135

136-
The *error code* returned by the script indicates if both versions
137-
are valid (return code 0) or not (return code != 0)::
136+
137+
Return Code
138+
-----------
139+
140+
The *return code* of the script (accessible by ``$?`` from the Bash)
141+
indicates if the subcommand returned successfully nor not. It is *not*
142+
meant as the result of the subcommand.
143+
144+
The result of the subcommand is printed on the standard out channel
145+
("stdout" or ``0``), any error messages to standard error ("stderr" or
146+
``2``).
147+
148+
For example, to compare two versions, the command expects two valid
149+
semver versions::
138150

139151
$ pysemver compare 1.2.3 2.4.0
140152
-1
141-
$ pysemver compare 1.2.3 2.4.0 ; echo $?
153+
$ echo $?
142154
0
143-
$ pysemver compare 1.2.3 2.4.0 ; echo $?
144-
ERROR 1.2.x is not valid SemVer string
155+
156+
The return code is zero, but the result is ``-1``.
157+
158+
However, if you pass invalid versions, you get this situation::
159+
160+
$ pysemver compare 1.2.3 2.4
161+
ERROR 2.4 is not valid SemVer string
162+
$ echo $?
145163
2
146164

165+
If you use the :command:`pysemver` in your own scripts, check the
166+
return code first before you process the standard output.
167+
168+
147169
See also
148170
--------
149171

semver.py

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,61 @@ def finalize_version(version):
684684
return format_version(verinfo["major"], verinfo["minor"], verinfo["patch"])
685685

686686

687+
def cmd_bump(args):
688+
"""
689+
Subcommand: Bumps a version.
690+
691+
Synopsis: bump <PART> <VERSION>
692+
<PART> can be major, minor, patch, prerelease, or build
693+
694+
:param args: The parsed arguments
695+
:type args: :class:`argparse.Namespace`
696+
:return: the new, bumped version
697+
"""
698+
maptable = {
699+
"major": "bump_major",
700+
"minor": "bump_minor",
701+
"patch": "bump_patch",
702+
"prerelease": "bump_prerelease",
703+
"build": "bump_build",
704+
}
705+
if args.bump is None:
706+
# When bump is called without arguments,
707+
# print the help and exit
708+
args.parser.parse_args(["bump", "-h"])
709+
710+
ver = parse_version_info(args.version)
711+
# get the respective method and call it
712+
func = getattr(ver, maptable[args.bump])
713+
return str(func())
714+
715+
716+
def cmd_check(args):
717+
"""
718+
Subcommand: Checks if a string is a valid semver version.
719+
720+
Synopsis: check <VERSION>
721+
722+
:param args: The parsed arguments
723+
:type args: :class:`argparse.Namespace`
724+
"""
725+
if VersionInfo.isvalid(args.version):
726+
return None
727+
raise ValueError("Invalid version %r" % args.version)
728+
729+
730+
def cmd_compare(args):
731+
"""
732+
Subcommand: Compare two versions
733+
734+
Synopsis: compare <VERSION1> <VERSION2>
735+
736+
:param args: The parsed arguments
737+
:type args: :class:`argparse.Namespace`
738+
"""
739+
return str(compare(args.version1, args.version2))
740+
741+
687742
def createparser():
688743
"""
689744
Create an :class:`argparse.ArgumentParser` instance.
@@ -700,13 +755,13 @@ def createparser():
700755
s = parser.add_subparsers()
701756
# create compare subcommand
702757
parser_compare = s.add_parser("compare", help="Compare two versions")
703-
parser_compare.set_defaults(which="compare")
758+
parser_compare.set_defaults(func=cmd_compare)
704759
parser_compare.add_argument("version1", help="First version")
705760
parser_compare.add_argument("version2", help="Second version")
706761

707762
# create bump subcommand
708763
parser_bump = s.add_parser("bump", help="Bumps a version")
709-
parser_bump.set_defaults(which="bump")
764+
parser_bump.set_defaults(func=cmd_bump)
710765
sb = parser_bump.add_subparsers(title="Bump commands", dest="bump")
711766

712767
# Create subparsers for the bump subparser:
@@ -723,7 +778,7 @@ def createparser():
723778
parser_check = s.add_parser(
724779
"check", help="Checks if a string is a valid semver version"
725780
)
726-
parser_check.set_defaults(which="check")
781+
parser_check.set_defaults(func=cmd_check)
727782
parser_check.add_argument("version", help="Version to check")
728783

729784
return parser
@@ -740,35 +795,12 @@ def process(args):
740795
:return: result of the selected action
741796
:rtype: str
742797
"""
743-
if not hasattr(args, "which"):
798+
if not hasattr(args, "func"):
744799
args.parser.print_help()
745800
raise SystemExit()
746801

747-
elif args.which == "bump":
748-
maptable = {
749-
"major": "bump_major",
750-
"minor": "bump_minor",
751-
"patch": "bump_patch",
752-
"prerelease": "bump_prerelease",
753-
"build": "bump_build",
754-
}
755-
if args.bump is None:
756-
# When bump is called without arguments,
757-
# print the help and exit
758-
args.parser.parse_args([args.which, "-h"])
759-
760-
ver = parse_version_info(args.version)
761-
# get the respective method and call it
762-
func = getattr(ver, maptable[args.bump])
763-
return str(func())
764-
765-
elif args.which == "compare":
766-
return str(compare(args.version1, args.version2))
767-
768-
elif args.which == "check":
769-
if VersionInfo.isvalid(args.version):
770-
return None
771-
raise ValueError("Invalid version %r" % args.version)
802+
# Call the respective function object:
803+
return args.func(args)
772804

773805

774806
def main(cliargs=None):

test_semver.py

Lines changed: 30 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
bump_minor,
1010
bump_patch,
1111
bump_prerelease,
12+
cmd_bump,
13+
cmd_check,
14+
cmd_compare,
1215
compare,
1316
createparser,
1417
finalize_version,
@@ -684,87 +687,70 @@ def test_should_be_able_to_use_integers_as_prerelease_build():
684687
@pytest.mark.parametrize(
685688
"cli,expected",
686689
[
687-
(
688-
["bump", "major", "1.2.3"],
689-
Namespace(which="bump", bump="major", version="1.2.3"),
690-
),
691-
(
692-
["bump", "minor", "1.2.3"],
693-
Namespace(which="bump", bump="minor", version="1.2.3"),
694-
),
695-
(
696-
["bump", "patch", "1.2.3"],
697-
Namespace(which="bump", bump="patch", version="1.2.3"),
698-
),
690+
(["bump", "major", "1.2.3"], Namespace(bump="major", version="1.2.3")),
691+
(["bump", "minor", "1.2.3"], Namespace(bump="minor", version="1.2.3")),
692+
(["bump", "patch", "1.2.3"], Namespace(bump="patch", version="1.2.3")),
699693
(
700694
["bump", "prerelease", "1.2.3"],
701-
Namespace(which="bump", bump="prerelease", version="1.2.3"),
702-
),
703-
(
704-
["bump", "build", "1.2.3"],
705-
Namespace(which="bump", bump="build", version="1.2.3"),
695+
Namespace(bump="prerelease", version="1.2.3"),
706696
),
697+
(["bump", "build", "1.2.3"], Namespace(bump="build", version="1.2.3")),
707698
# ---
708-
(
709-
["compare", "1.2.3", "2.1.3"],
710-
Namespace(which="compare", version1="1.2.3", version2="2.1.3"),
711-
),
699+
(["compare", "1.2.3", "2.1.3"], Namespace(version1="1.2.3", version2="2.1.3")),
712700
# ---
713-
(["check", "1.2.3"], Namespace(which="check", version="1.2.3")),
701+
(["check", "1.2.3"], Namespace(version="1.2.3")),
714702
],
715703
)
716704
def test_should_parse_cli_arguments(cli, expected):
717705
parser = createparser()
718706
assert parser
719707
result = parser.parse_args(cli)
708+
del result.func
720709
assert result == expected
721710

722711

723712
@pytest.mark.parametrize(
724-
"args,expectation",
713+
"func,args,expectation",
725714
[
726715
# bump subcommand
716+
(cmd_bump, Namespace(bump="major", version="1.2.3"), does_not_raise("2.0.0")),
717+
(cmd_bump, Namespace(bump="minor", version="1.2.3"), does_not_raise("1.3.0")),
718+
(cmd_bump, Namespace(bump="patch", version="1.2.3"), does_not_raise("1.2.4")),
727719
(
728-
Namespace(which="bump", bump="major", version="1.2.3"),
729-
does_not_raise("2.0.0"),
730-
),
731-
(
732-
Namespace(which="bump", bump="minor", version="1.2.3"),
733-
does_not_raise("1.3.0"),
734-
),
735-
(
736-
Namespace(which="bump", bump="patch", version="1.2.3"),
737-
does_not_raise("1.2.4"),
738-
),
739-
(
740-
Namespace(which="bump", bump="prerelease", version="1.2.3-rc1"),
720+
cmd_bump,
721+
Namespace(bump="prerelease", version="1.2.3-rc1"),
741722
does_not_raise("1.2.3-rc2"),
742723
),
743724
(
744-
Namespace(which="bump", bump="build", version="1.2.3+build.13"),
725+
cmd_bump,
726+
Namespace(bump="build", version="1.2.3+build.13"),
745727
does_not_raise("1.2.3+build.14"),
746728
),
747729
# compare subcommand
748730
(
749-
Namespace(which="compare", version1="1.2.3", version2="2.1.3"),
731+
cmd_compare,
732+
Namespace(version1="1.2.3", version2="2.1.3"),
750733
does_not_raise("-1"),
751734
),
752735
(
753-
Namespace(which="compare", version1="1.2.3", version2="1.2.3"),
736+
cmd_compare,
737+
Namespace(version1="1.2.3", version2="1.2.3"),
754738
does_not_raise("0"),
755739
),
756740
(
757-
Namespace(which="compare", version1="2.4.0", version2="2.1.3"),
741+
cmd_compare,
742+
Namespace(version1="2.4.0", version2="2.1.3"),
758743
does_not_raise("1"),
759744
),
760745
# check subcommand
761-
(Namespace(which="check", version="1.2.3"), does_not_raise(None)),
762-
(Namespace(which="check", version="1.2"), pytest.raises(ValueError)),
746+
(cmd_check, Namespace(version="1.2.3"), does_not_raise(None)),
747+
(cmd_check, Namespace(version="1.2"), pytest.raises(ValueError)),
763748
],
764749
)
765-
def test_should_process_parsed_cli_arguments(args, expectation):
750+
def test_should_process_parsed_cli_arguments(func, args, expectation):
766751
with expectation as expected:
767-
assert process(args) == expected
752+
result = func(args)
753+
assert result == expected
768754

769755

770756
def test_should_process_print(capsys):

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