lib/version_boss/gem/incrementable_version.rb
# frozen_string_literal: true
require_relative 'version'
module VersionBoss
module Gem
# A VersionBoss::Semver with additional constraints on the pre-release part of the version
#
# A IncrementableVersion is valid if one of the two following conditions is met:
# 1. The pre-release part is empty
# 2. The pre-release part is composed of two dot-separated identifiers:
# * the first being a String representing the pre-release type (e.g. 'alpha',
# 'beta', etc.). The default pre-release type is 'pre'
# * the second being an Integer representing the pre-release sequence number
# (starting at 1)
#
# Valid versions with pre-release parts: `1.2.3.alpha.1`, `1.2.3.beta.2`, `1.2.3.pre.3`
#
# @api public
#
class IncrementableVersion < Version
# Create a new IncrementableVersion object
#
# @example
# VersionBoss::Semver.new('1.2.3').valid? # => true
# VersionBoss::Semver.new('1.2.3-alpha.1+build.001').valid? # => true
# VersionBoss::Semver.new('1.2.3-alpha').valid? # => raise VersionBoss::Error
# VersionBoss::Semver.new('1.2.3-alpha.1.2').valid? # => raise VersionBoss::Error
# VersionBoss::Semver.new('1.2.3-alpha.one').valid? # => raise VersionBoss::Error
#
# @return [Boolean] true if the version string is a valid semver and meets the conditions above
#
def valid?
super && (pre_release.empty? || (pre_release_identifiers.size == 2 && pre_number.is_a?(Integer)))
end
# The default pre-release identifier
DEFAULT_PRE_TYPE = 'pre'
# Increment the major version
#
# @example
# VersionBoss::IncrementableVersion.new('1.2.3').next_major # =>
# VersionBoss::IncrementableVersion.new('2.0.0')
#
# @return [IncrementableVersion] a new IncrementableVersion object with the major version incremented
#
def next_major(pre: false, pre_type: DEFAULT_PRE_TYPE)
version_string = "#{major.to_i + 1}.0.0"
version_string += ".#{pre_type}1" if pre
IncrementableVersion.new(version_string)
end
# Increment the minor version
#
# @example
# VersionBoss::IncrementableVersion.new('1.2.3').next_minor # =>
# VersionBoss::IncrementableVersion.new('1.3.0')
#
# @return [IncrementableVersion] a new IncrementableVersion object with the major version incremented
#
def next_minor(pre: false, pre_type: DEFAULT_PRE_TYPE)
version_string = "#{major}.#{minor.to_i + 1}.0"
version_string += ".#{pre_type}1" if pre
IncrementableVersion.new(version_string)
end
# Increment the patch version
#
# @example
# VersionBoss::IncrementableVersion.new('1.2.3').next_patch # =>
# VersionBoss::IncrementableVersion.new('1.2.1')
#
# @return [IncrementableVersion] a new IncrementableVersion object with the patch part incremented
#
def next_patch(pre: false, pre_type: DEFAULT_PRE_TYPE)
version_string = "#{major}.#{minor}.#{patch.to_i + 1}"
version_string += ".#{pre_type}1" if pre
IncrementableVersion.new(version_string)
end
# Increment the pre_release part of the version
#
# @example
# VersionBoss::IncrementableVersion.new('1.2.3.pre.1').next_patch.to_s # => '1.2.3.pre.2'
#
# @return [IncrementableVersion] a new object with the pre_release part incremented
#
def next_pre(pre_type: nil)
assert_is_a_pre_release_version
version_string = "#{major}.#{minor}.#{patch}"
version_string += next_pre_part(pre_type)
IncrementableVersion.new(version_string)
end
# Drop the pre-release part of the version
#
# @example
# VersionBoss::IncrementableVersion.new('1.2.3.pre.1').next_release.to_s # => '1.2.3'
#
# @return [IncrementableVersion] a new IncrementableVersion object with the pre_release part dropped
# @raise [VersionBoss::Error] if the version is not a pre-release version
#
def next_release
assert_is_a_pre_release_version
version_string = "#{major}.#{minor}.#{patch}"
IncrementableVersion.new(version_string)
end
# The pre-release type (for example, 'alpha', 'beta', 'pre', etc.)
#
# @example
# VersionBoss::IncrementableVersion.new('1.2.3.pre1').pre_type # => 'pre'
#
# @return [String]
#
def pre_type
pre_release_identifiers[0].identifier
end
# Returns the prefix of the pre-release type
#
# Ruby Gem versions can optionally prefix the pre-release type with a period.
#
# @example
# VersionBoss::GemVersion.new('1.2.3.pre1').pre_type_prefix # => '-'
# Semver requires a hyphen prefix.
#
# @example
# VersionBoss::IncrementableVersion.new('1.2.3.pre1').pre_type # => 'pre'
#
# @return ['.', '']
#
def pre_type_prefix
pre_release_identifiers[0].prefix
end
# The pre-release sequence number
#
# The pre-release sequence number starts at 1 for each pre-release type.
#
# @example
# IncrementableVersion.new('1.2.3.pre.1').pre_number # => 1
#
# @return [Integer]
#
def pre_number
pre_release_identifiers[1].identifier
end
# The pre-release sequence number
#
# The pre-release identifier can optionally prefix the pre-release sequence number with a period.
#
# @example
# IncrementableVersion.new('1.2.3.pre.1').pre_number_prefix # => '.'
# IncrementableVersion.new('1.2.3.pre1').pre_number_prefix # => ''
#
# @return [String]
#
def pre_number_prefix
pre_release_identifiers[1].prefix
end
private
# Raise an error if the version is not a pre-release version
#
# @return [void]
# @raise [VersionBoss::Error] if the version is not a pre-release version
# @api private
def assert_is_a_pre_release_version
return unless pre_release.empty?
raise VersionBoss::Error, 'Cannot increment the pre-release part of a release version'
end
# Raise an error if new pre-release type is less than the old pre-release type
#
# If the new pre-release type is lexically less than the old pre-release type,
# then raise an error because the resulting error would sort before the existing
# version.
#
# @return [void]
# @raise [VersionBoss::Error] if the pre-release type is not valid
# @api private
def assert_pre_type_is_valid(pre_type)
return if self.pre_type <= pre_type
# :nocov: JRuby coverage does not report this line correctly
message = 'Cannot increment the pre-release identifier ' \
"from '#{self.pre_type}' to '#{pre_type}' " \
"because '#{self.pre_type}' is lexically less than '#{pre_type}'"
# :nocov:
raise VersionBoss::Error, message
end
# Return the next pre-release part
# @param pre_type [String, nil] the given pre-release type or nil to use the existing pre-release type
# @return [String]
# @api private
def next_pre_part(pre_type)
pre_type ||= self.pre_type
assert_pre_type_is_valid(pre_type)
next_pre_number = self.pre_type == pre_type ? (pre_number + 1) : 1
"#{pre_type_prefix}#{pre_type}#{pre_number_prefix}#{next_pre_number}"
end
end
end
end