Commit 3aa0c69d authored by Giorgenes Gelatti's avatar Giorgenes Gelatti

Removes semver sorting

Removes semver sorting logic
and simplify semver code
parent a778866f
# frozen_string_literal: true # frozen_string_literal: true
module Packages class Packages::SemVer
class SemVer attr_accessor :major, :minor, :patch, :prerelease, :build
attr_accessor :major, :minor, :patch, :prerelease, :build
def initialize(major = 0, minor = 0, patch = 0, prerelease = nil, build = nil, prefixed: false)
def initialize(major = 0, minor = 0, patch = 0, prerelease = nil, build = nil, prefixed: false) @major = major
@major = major @minor = minor
@minor = minor @patch = patch
@patch = patch @prerelease = prerelease
@prerelease = prerelease @build = build
@build = build @prefixed = prefixed
@prefixed = prefixed end
end
def prefixed?
@prefixed
end
def with(**args)
self.class.new(
args.fetch(:major, major),
args.fetch(:minor, minor),
args.fetch(:patch, patch),
args.fetch(:prerelease, args.fetch(:pre, prerelease)),
args.fetch(:build, build),
prefixed: args.fetch(:prefixed, prefixed?)
)
end
def ==(other)
self.class == other.class &&
self.major == other.major &&
self.minor == other.minor &&
self.patch == other.patch &&
self.prerelease == other.prerelease &&
self.build == other.build
end
# rubocop: disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/AbcSize
def <=>(other)
a, b = self, other
raise ArgumentError.new('Not the same type') unless a.class == b.class
return 0 if a == b
return -1 if a.major < b.major
return +1 if a.major > b.major
return -1 if a.minor < b.minor
return +1 if a.minor > b.minor
return -1 if a.patch < b.patch
return +1 if a.patch > b.patch
if a.prerelease == b.prerelease
# "Build metadata MUST be ignored when determining version precedence."
# But that would lead to unstable ordering, so check it anyways.
return 0 if a.build == b.build
return -1 if !a.build.nil? && b.build.nil?
return +1 if a.build.nil? && !b.build.nil?
return -1 if a.build < b.build
return +1 ## a.build > b.build
end
return -1 if !a.prerelease.nil? && b.prerelease.nil?
return +1 if a.prerelease.nil? && !b.prerelease.nil?
# "Precedence for [...] patch versions MUST be determined by comparing each
# dot separated identifier from left to right."
a_parts = a.prerelease&.split('.') || []
b_parts = b.prerelease&.split('.') || []
(0...[a_parts.length, b_parts.length].min).each do |i|
a_part, b_part = a_parts[i], b_parts[i]
next if a_part == b_part
a_num = a_part.to_i if /^\d+$/.match?(a_part)
b_num = b_part.to_i if /^\d+$/.match?(b_part)
unless a_num.nil? || b_num.nil?
return -1 if a_num < b_num
return +1 if a_num > b_num
# '0' and '000' have the same precedence, but stable ordering is good.
end
# "Numeric identifiers always have lower precedence than non-numeric identifiers."
return -1 if !a_num.nil? && b_num.nil?
return +1 if a_num.nil? && !b_num.nil?
return -1 if a_part < b_part
return +1 if a_part > b_part
end
return -1 if a_parts.length < b_parts.length def prefixed?
return +1 if a_parts.length > b_parts.length @prefixed
end
return 0 def ==(other)
end self.class == other.class &&
# rubocop: enable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/AbcSize self.major == other.major &&
self.minor == other.minor &&
self.patch == other.patch &&
self.prerelease == other.prerelease &&
self.build == other.build
end
def to_s def to_s
s = "#{prefixed? ? 'v' : ''}#{major || 0}.#{minor || 0}.#{patch || 0}" s = "#{prefixed? ? 'v' : ''}#{major || 0}.#{minor || 0}.#{patch || 0}"
s += "-#{prerelease}" if prerelease s += "-#{prerelease}" if prerelease
s += "+#{build}" if build s += "+#{build}" if build
s s
end end
def self.match(str, prefixed: false) def self.match(str, prefixed: false)
return unless str&.start_with?('v') == prefixed return unless str&.start_with?('v') == prefixed
str = str[1..] if prefixed str = str[1..] if prefixed
Gitlab::Regex.semver_regex.match(str) Gitlab::Regex.semver_regex.match(str)
end end
def self.match?(str, prefixed: false) def self.match?(str, prefixed: false)
!match(str, prefixed: prefixed).nil? !match(str, prefixed: prefixed).nil?
end end
def self.parse(str, prefixed: false) def self.parse(str, prefixed: false)
m = match str, prefixed: prefixed m = match str, prefixed: prefixed
return unless m return unless m
new(m[1].to_i, m[2].to_i, m[3].to_i, m[4], m[5], prefixed: prefixed) new(m[1].to_i, m[2].to_i, m[3].to_i, m[4], m[5], prefixed: prefixed)
end
end end
end end
# frozen_string_literal: true
FactoryBot.define do
factory :semver, class: 'Packages::SemVer' do
initialize_with { new(attributes[:major], attributes[:minor], attributes[:patch], attributes[:prerelease], attributes[:build], prefixed: attributes[:prefixed]) }
skip_create
major { 1 }
minor { 0 }
patch { 0 }
prerelease { nil }
build { nil }
prefixed { false }
end
end
# frozen_string_literal: true # frozen_string_literal: true
require 'fast_spec_helper' require 'spec_helper'
RSpec.describe Packages::SemVer, type: :model do RSpec.describe Packages::SemVer, type: :model do
shared_examples '#parse with a valid semver' do |str, major, minor, patch, prerelease, build| shared_examples '#parse with a valid semver' do |str, major, minor, patch, prerelease, build|
subject(:semver) { described_class.new(major, minor, patch, prerelease, build) }
context "with #{str}" do context "with #{str}" do
subject(:expected) { semver.with(prefixed: prefixed) } it "returns #{described_class.new(major, minor, patch, prerelease, build, prefixed: true)} with prefix" do
expected = described_class.new(major, minor, patch, prerelease, build, prefixed: true)
context 'prefixed' do expect(described_class.parse('v' + str, prefixed: true)).to eq(expected)
let(:prefixed) { true }
specify do
expect(described_class.parse('v' + str, prefixed: true)).to eq(expected)
end
end end
context 'without prefix' do it "returns #{described_class.new(major, minor, patch, prerelease, build)} without prefix" do
let(:prefixed) { false } expected = described_class.new(major, minor, patch, prerelease, build)
expect(described_class.parse(str)).to eq(expected)
specify do
expect(described_class.parse(str)).to eq(expected)
end
end end
end end
end end
...@@ -39,17 +29,9 @@ RSpec.describe Packages::SemVer, type: :model do ...@@ -39,17 +29,9 @@ RSpec.describe Packages::SemVer, type: :model do
end end
end end
shared_examples 'sorted' do
it 'orders correctly' do
(1..10).each do |_|
expect(expected_list.shuffle.sort.map(&:to_s)).to eq(expected_list.map(&:to_s))
end
end
end
describe '#parse' do describe '#parse' do
it_behaves_like '#parse with a valid semver', '1.0.0', 1, 0, 0, nil, nil it_behaves_like '#parse with a valid semver', '1.0.0', 1, 0, 0, nil, nil
it_behaves_like '#parse with a valid semver', '1.0.0-pre', 1, 0, 0, 'pre' it_behaves_like '#parse with a valid semver', '1.0.0-pre', 1, 0, 0, 'pre', nil
it_behaves_like '#parse with a valid semver', '1.0.0+build', 1, 0, 0, nil, 'build' it_behaves_like '#parse with a valid semver', '1.0.0+build', 1, 0, 0, nil, 'build'
it_behaves_like '#parse with a valid semver', '1.0.0-pre+build', 1, 0, 0, 'pre', 'build' it_behaves_like '#parse with a valid semver', '1.0.0-pre+build', 1, 0, 0, 'pre', 'build'
it_behaves_like '#parse with an invalid semver', '01.0.0' it_behaves_like '#parse with an invalid semver', '01.0.0'
...@@ -57,17 +39,4 @@ RSpec.describe Packages::SemVer, type: :model do ...@@ -57,17 +39,4 @@ RSpec.describe Packages::SemVer, type: :model do
it_behaves_like '#parse with an invalid semver', '0.0.01' it_behaves_like '#parse with an invalid semver', '0.0.01'
it_behaves_like '#parse with an invalid semver', '1.0.0asdf' it_behaves_like '#parse with an invalid semver', '1.0.0asdf'
end end
describe '#<=>' do
let(:v1) { described_class.new(1, 0, 0) }
let(:v2) { described_class.new(2, 0, 0) }
it_behaves_like 'sorted' do
let(:expected_list) { [v1.with(pre: 'beta'), v1, v1.with(minor: 1), v2.with(pre: 'alpha'), v2, v2.with(patch: 1), v2.with(minor: 1)] }
end
it_behaves_like 'sorted' do
let(:expected_list) { [v1.with(pre: 'alpha'), v1.with(pre: 'alpha.1'), v1.with(pre: 'alpha.beta'), v1.with(pre: 'beta'), v1.with(pre: 'beta.2'), v1.with(pre: 'beta.11'), v1.with(pre: 'rc.1'), v1] }
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment