Repository: python-semver/python-semver (python)

Issue:
 Unable to create pre-release with an empty token
<!-- Remove the comments below and adapt it to your needs  -->

# Situation

Unable to create pre-release with an empty token.

# To Reproduce

Run this script:
```python
ver = semver.VersionInfo.parse('1.0.0')
ver = ver.bump_prerelease('');
print(ver)
# 1.0.0-rc.1
```

# Expected Behavior

`.bump_prerelease('')` should generate an version like `1.0.0-1`.

# Environment
- OS: Linux ubuntu 5.11.0-40-generic
- Python version: Docker image python:3.8
- Version of semver library: semver==2.13.0

# Additional context

Pre-release documentation https://semver.org/#spec-item-9

Pull request:
Fix #344 Allow empty string for bump method
The methods `.bump_prerelease('')` and `.bump_build('')` generates a prerelease and build part without the token.

Patch:
diff --git a/changelog.d/344.bugfix.rst b/changelog.d/344.bugfix.rst
new file mode 100644
index 0000000..9daa9e7
--- /dev/null
+++ b/changelog.d/344.bugfix.rst
@@ -0,0 +1,5 @@
+Allow empty string, a string with a prefix, or ``None``
+as token in
+:meth:`Version.bump_build <semver.Version.bump_build>` and
+:meth:`Version.bump_prerelease <semver.Version.bump_prerelease>`.
+
diff --git a/docs/usage/increase-parts-of-a-version_prereleases.rst b/docs/usage/increase-parts-of-a-version_prereleases.rst
index 9828393..87685b7 100644
--- a/docs/usage/increase-parts-of-a-version_prereleases.rst
+++ b/docs/usage/increase-parts-of-a-version_prereleases.rst
@@ -1,3 +1,4 @@
+.. _increase-parts-of-a-version:
 Increasing Parts of a Version Taking into Account Prereleases
 =============================================================
 
diff --git a/docs/usage/raise-parts-of-a-version.rst b/docs/usage/raise-parts-of-a-version.rst
index cc62fff..d369c57 100644
--- a/docs/usage/raise-parts-of-a-version.rst
+++ b/docs/usage/raise-parts-of-a-version.rst
@@ -1,6 +1,19 @@
 Raising Parts of a Version
 ==========================
 
+.. note::
+
+   Keep in mind, "raising" the pre-release only will make your
+   complete version *lower* than before.
+
+   For example, having version ``1.0.0`` and raising the pre-release
+   will lead to ``1.0.0-rc.1``, but ``1.0.0-rc.1`` is smaller than ``1.0.0``.
+
+   If you search for a way to take into account this behavior, look for the
+   method :meth:`Version.next_version <semver.version.Version.next_version>`
+   in section :ref:`increase-parts-of-a-version`.
+
+
 The ``semver`` module contains the following functions to raise parts of
 a version:
 
@@ -14,6 +27,7 @@ a version:
   ``build`` to ``None``.
 * :func:`Version.bump_build <semver.version.Version.bump_build>`: raises the build part.
 
+
 .. code-block:: python
 
     >>> str(Version.parse("3.4.5-pre.2+build.4").bump_major())
@@ -28,3 +42,29 @@ a version:
     '3.4.5-pre.2+build.5'
 
 Likewise the module level functions :func:`semver.bump_major`.
+
+For the methods :meth:`Version.bump_prerelease <semver.version.Version.bump_prerelease>`
+and :meth:`Version.bump_build <semver.version.Version.bump_build>` it's possible to pass an empty string or ``None``.
+However, it gives different results::
+
+.. code-block:: python
+
+    >>> str(Version.parse("3.4.5").bump_prerelease(''))
+    '3.4.5-1'
+    >>> str(Version.parse("3.4.5").bump_prerelease(None))
+    '3.4.5-rc.1'
+
+An empty string removes any prefix whereas ``None`` is the same as calling
+the method without any argument.
+
+If you already have a prerelease, the argument for the method
+is not taken into account:
+
+.. code-block:: python
+
+    >>> str(Version.parse("3.4.5-rc.1").bump_prerelease(None))
+    '3.4.5-rc.2'
+    >>> str(Version.parse("3.4.5-rc.1").bump_prerelease(''))
+    '3.4.5-rc.2'
+
+
diff --git a/src/semver/version.py b/src/semver/version.py
index bf949bb..bfce810 100644
--- a/src/semver/version.py
+++ b/src/semver/version.py
@@ -305,30 +305,44 @@ class Version:
         cls = type(self)
         return cls(self._major, self._minor, self._patch + 1)
 
-    def bump_prerelease(self, token: str = "rc") -> "Version":
+    def bump_prerelease(self, token: Optional[str] = "rc") -> "Version":
         """
         Raise the prerelease part of the version, return a new object but leave
         self untouched.
 
-        :param token: defaults to ``rc``
-        :return: new object with the raised prerelease part
+        :param token: defaults to ``'rc'``
+        :return: new :class:`Version` object with the raised prerelease part.
+            The original object is not modified.
 
         >>> ver = semver.parse("3.4.5")
-        >>> ver.bump_prerelease()
-        Version(major=3, minor=4, patch=5, prerelease='rc.2', \
-build=None)
+        >>> ver.bump_prerelease().prerelease
+        'rc.2'
+        >>> ver.bump_prerelease('').prerelease
+        '1'
+        >>> ver.bump_prerelease(None).prerelease
+        'rc.1'
         """
         cls = type(self)
-        prerelease = cls._increment_string(self._prerelease or (token or "rc") + ".0")
+        if self._prerelease is not None:
+            prerelease = self._prerelease
+        elif token == "":
+            prerelease = "0"
+        elif token is None:
+            prerelease = "rc.0"
+        else:
+            prerelease = str(token) + ".0"
+
+        prerelease = cls._increment_string(prerelease)
         return cls(self._major, self._minor, self._patch, prerelease)
 
-    def bump_build(self, token: str = "build") -> "Version":
+    def bump_build(self, token: Optional[str] = "build") -> "Version":
         """
         Raise the build part of the version, return a new object but leave self
         untouched.
 
-        :param token: defaults to ``build``
-        :return: new object with the raised build part
+        :param token: defaults to ``'build'``
+        :return: new :class:`Version` object with the raised build part.
+            The original object is not modified.
 
         >>> ver = semver.parse("3.4.5-rc.1+build.9")
         >>> ver.bump_build()
@@ -336,7 +350,28 @@ build=None)
 build='build.10')
         """
         cls = type(self)
-        build = cls._increment_string(self._build or (token or "build") + ".0")
+        if self._build is not None:
+            build = self._build
+        elif token == "":
+            build = "0"
+        elif token is None:
+            build = "build.0"
+        else:
+            build = str(token) + ".0"
+
+        # self._build or (token or "build") + ".0"
+        build = cls._increment_string(build)
+        if self._build is not None:
+            build = self._build
+        elif token == "":
+            build = "0"
+        elif token is None:
+            build = "build.0"
+        else:
+            build = str(token) + ".0"
+
+        # self._build or (token or "build") + ".0"
+        build = cls._increment_string(build)
         return cls(self._major, self._minor, self._patch, self._prerelease, build)
 
     def compare(self, other: Comparable) -> int:


Test patch:
diff --git a/tests/test_bump.py b/tests/test_bump.py
index c28e190..34e0b2a 100644
--- a/tests/test_bump.py
+++ b/tests/test_bump.py
@@ -66,6 +66,30 @@ def test_should_versioninfo_bump_multiple():
     assert v.bump_prerelease().bump_build().bump_build().bump_prerelease() == expected
 
 
+def test_should_versioninfo_bump_prerelease_with_empty_str():
+    v = parse_version_info("3.4.5")
+    expected = parse_version_info("3.4.5-1")
+    assert v.bump_prerelease("") == expected
+
+
+def test_should_versioninfo_bump_prerelease_with_none():
+    v = parse_version_info("3.4.5")
+    expected = parse_version_info("3.4.5-rc.1")
+    assert v.bump_prerelease(None) == expected
+
+
+def test_should_versioninfo_bump_build_with_empty_str():
+    v = parse_version_info("3.4.5")
+    expected = parse_version_info("3.4.5+1")
+    assert v.bump_build("") == expected
+
+
+def test_should_versioninfo_bump_build_with_none():
+    v = parse_version_info("3.4.5")
+    expected = parse_version_info("3.4.5+build.1")
+    assert v.bump_build(None) == expected
+
+
 def test_should_ignore_extensions_for_bump():
     assert bump_patch("3.4.5-rc1+build4") == "3.4.6"