From 405e820b0bbb65b5510bb0110c5ec8a47acfc0e1 Mon Sep 17 00:00:00 2001
From: Gabriel Silveira <gabriel.silveira@poli.ufrj.br>
Date: Fri, 9 Dec 2022 14:51:46 -0300
Subject: [PATCH 1/2] Add methods to manipulate semantic versions

---
 spec/std/semantic_version_spec.cr | 32 ++++++++++++++++++++
 src/semantic_version.cr           | 49 +++++++++++++++++++++++++++++++
 2 files changed, 81 insertions(+)

diff --git a/spec/std/semantic_version_spec.cr b/spec/std/semantic_version_spec.cr
index 9902e1fe46ee..bf0fd7cc42e0 100644
--- a/spec/std/semantic_version_spec.cr
+++ b/spec/std/semantic_version_spec.cr
@@ -88,6 +88,38 @@ describe SemanticVersion do
     end
   end
 
+  it "copies with specified modifications" do
+    base_version = SemanticVersion.new(1, 2, 3, "rc", "0000")
+    base_version.copy_with(major: 0).should eq SemanticVersion.new(0, 2, 3, "rc", "0000")
+    base_version.copy_with(minor: 0).should eq SemanticVersion.new(1, 0, 3, "rc", "0000")
+    base_version.copy_with(patch: 0).should eq SemanticVersion.new(1, 2, 0, "rc", "0000")
+    base_version.copy_with(prerelease: "alpha").should eq SemanticVersion.new(1, 2, 3, "alpha", "0000")
+    base_version.copy_with(build: "0001").should eq SemanticVersion.new(1, 2, 3, "rc", "0001")
+    base_version.copy_with(prerelease: nil, build: nil).should eq SemanticVersion.new(1, 2, 3)
+  end
+
+  it "bumps to the correct version" do
+    sversions = %w(
+      1.1.1
+      1.2.0
+      1.2.1
+      2.0.0
+      2.0.1
+      2.1.0
+      3.0.0
+    )
+    versions = sversions.map { |s| SemanticVersion.parse(s) }.to_a
+
+    {% for bump, i in %w(minor patch major patch minor major) %}
+    versions[{{i}}].bump_{{bump.id}}.should eq versions[{{i + 1}}]
+    {% end %}
+
+    version_with_prerelease = SemanticVersion.new(1, 2, 3, "rc", "0001")
+    version_with_prerelease.bump_major.should eq SemanticVersion.new(2, 0, 0)
+    version_with_prerelease.bump_minor.should eq SemanticVersion.new(1, 3, 0)
+    version_with_prerelease.bump_patch.should eq SemanticVersion.new(1, 2, 3)
+  end
+
   describe SemanticVersion::Prerelease do
     it "compares <" do
       sprereleases = %w(
diff --git a/src/semantic_version.cr b/src/semantic_version.cr
index 1e15321a3089..7e6632322d58 100644
--- a/src/semantic_version.cr
+++ b/src/semantic_version.cr
@@ -82,6 +82,55 @@ struct SemanticVersion
     end
   end
 
+  # Returns a new `SemanticVersion` created with the specified parts. The
+  # default for each part is its current value.
+  #
+  # ```
+  # require "semantic_version"
+  #
+  # current_version = SemanticVersion.new 1, 1, 1, "rc"
+  # current_version.copy_with(patch: 2) # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=2, @prerelease=SemanticVersion::Prerelease(@identifiers=["rc"]))
+  # current_version.copy_with(prerelease: nil) # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=2, @prerelease=SemanticVersion::Prerelease(@identifiers=[]))
+  def copy_with(major : Int32 = @major, minor : Int32 = @minor, patch : Int32 = @patch, prerelease : String | Prerelease | Nil = @prerelease, build : String? = @build)
+    SemanticVersion.new major, minor, patch, prerelease, build
+  end
+
+  # Returns a copy of the current version with a major bump.
+  #
+  # ```
+  # require "semantic_version"
+  #
+  # current_version = SemanticVersion.new 1, 1, 1, "rc"
+  # current_version.bump_major # => SemanticVersion(@build=nil, @major=2, @minor=0, @patch=0, @prerelease=SemanticVersion::Prerelease(@identifiers=[]))
+  def bump_major
+    copy_with(major: major + 1, minor: 0, patch: 0, prerelease: nil, build: nil)
+  end
+
+  # Returns a copy of the current version with a minor bump.
+  #
+  # ```
+  # require "semantic_version"
+  #
+  # current_version = SemanticVersion.new 1, 1, 1, "rc"
+  # current_version.bump_minor # => SemanticVersion(@build=nil, @major=1, @minor=2, @patch=0, @prerelease=SemanticVersion::Prerelease(@identifiers=[]))
+  def bump_minor
+    copy_with(minor: minor + 1, patch: 0, prerelease: nil, build: nil)
+  end
+
+  # Returns a copy of the current version with a patch bump. Bumping a patch of
+  # a prerelease just erase the prerelease data.
+  #
+  # ```
+  # require "semantic_version"
+  #
+  # current_version = SemanticVersion.new 1, 1, 1, "rc"
+  # next_patch = current_version.bump_patch # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=1, @prerelease=SemanticVersion::Prerelease(@identifiers=[]))
+  # next_patch.bump_patch # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=2, @prerelease=SemanticVersion::Prerelease(@identifiers=[]))
+  def bump_patch
+    return copy_with(prerelease: nil, build: nil) unless prerelease.identifiers.empty?
+    copy_with(patch: patch + 1, prerelease: nil, build: nil)
+  end
+
   # The comparison operator.
   #
   # Returns `-1`, `0` or `1` depending on whether `self`'s version is lower than *other*'s,

From 71641d6a19c62871d57903f473587998f46115df Mon Sep 17 00:00:00 2001
From: Gabriel Silveira <gabriel.silveira@poli.ufrj.br>
Date: Mon, 12 Dec 2022 12:47:51 -0300
Subject: [PATCH 2/2] Refactor code to make it easier to read

---
 spec/std/semantic_version_spec.cr | 20 ++++++--------------
 src/semantic_version.cr           |  7 +++++--
 2 files changed, 11 insertions(+), 16 deletions(-)

diff --git a/spec/std/semantic_version_spec.cr b/spec/std/semantic_version_spec.cr
index bf0fd7cc42e0..41403505b776 100644
--- a/spec/std/semantic_version_spec.cr
+++ b/spec/std/semantic_version_spec.cr
@@ -99,20 +99,12 @@ describe SemanticVersion do
   end
 
   it "bumps to the correct version" do
-    sversions = %w(
-      1.1.1
-      1.2.0
-      1.2.1
-      2.0.0
-      2.0.1
-      2.1.0
-      3.0.0
-    )
-    versions = sversions.map { |s| SemanticVersion.parse(s) }.to_a
-
-    {% for bump, i in %w(minor patch major patch minor major) %}
-    versions[{{i}}].bump_{{bump.id}}.should eq versions[{{i + 1}}]
-    {% end %}
+    SemanticVersion.new(1, 1, 1).bump_minor.should eq SemanticVersion.new(1, 2, 0)
+    SemanticVersion.new(1, 2, 0).bump_patch.should eq SemanticVersion.new(1, 2, 1)
+    SemanticVersion.new(1, 2, 1).bump_major.should eq SemanticVersion.new(2, 0, 0)
+    SemanticVersion.new(2, 0, 0).bump_patch.should eq SemanticVersion.new(2, 0, 1)
+    SemanticVersion.new(2, 0, 1).bump_minor.should eq SemanticVersion.new(2, 1, 0)
+    SemanticVersion.new(2, 1, 0).bump_major.should eq SemanticVersion.new(3, 0, 0)
 
     version_with_prerelease = SemanticVersion.new(1, 2, 3, "rc", "0001")
     version_with_prerelease.bump_major.should eq SemanticVersion.new(2, 0, 0)
diff --git a/src/semantic_version.cr b/src/semantic_version.cr
index 7e6632322d58..8c35e9998eae 100644
--- a/src/semantic_version.cr
+++ b/src/semantic_version.cr
@@ -127,8 +127,11 @@ struct SemanticVersion
   # next_patch = current_version.bump_patch # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=1, @prerelease=SemanticVersion::Prerelease(@identifiers=[]))
   # next_patch.bump_patch # => SemanticVersion(@build=nil, @major=1, @minor=1, @patch=2, @prerelease=SemanticVersion::Prerelease(@identifiers=[]))
   def bump_patch
-    return copy_with(prerelease: nil, build: nil) unless prerelease.identifiers.empty?
-    copy_with(patch: patch + 1, prerelease: nil, build: nil)
+    if prerelease.identifiers.empty?
+      copy_with(patch: patch + 1, prerelease: nil, build: nil)
+    else
+      copy_with(prerelease: nil, build: nil)
+    end
   end
 
   # The comparison operator.