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
module Packages
class SemVer
attr_accessor :major, :minor, :patch, :prerelease, :build
def initialize(major = 0, minor = 0, patch = 0, prerelease = nil, build = nil, prefixed: false)
@major = major
@minor = minor
@patch = patch
@prerelease = prerelease
@build = build
@prefixed = prefixed
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
class Packages::SemVer
attr_accessor :major, :minor, :patch, :prerelease, :build
def initialize(major = 0, minor = 0, patch = 0, prerelease = nil, build = nil, prefixed: false)
@major = major
@minor = minor
@patch = patch
@prerelease = prerelease
@build = build
@prefixed = prefixed
end
return -1 if a_parts.length < b_parts.length
return +1 if a_parts.length > b_parts.length
def prefixed?
@prefixed
end
return 0
end
# rubocop: enable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/AbcSize
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
def to_s
s = "#{prefixed? ? 'v' : ''}#{major || 0}.#{minor || 0}.#{patch || 0}"
s += "-#{prerelease}" if prerelease
s += "+#{build}" if build
def to_s
s = "#{prefixed? ? 'v' : ''}#{major || 0}.#{minor || 0}.#{patch || 0}"
s += "-#{prerelease}" if prerelease
s += "+#{build}" if build
s
end
s
end
def self.match(str, prefixed: false)
return unless str&.start_with?('v') == prefixed
def self.match(str, prefixed: false)
return unless str&.start_with?('v') == prefixed
str = str[1..] if prefixed
str = str[1..] if prefixed
Gitlab::Regex.semver_regex.match(str)
end
Gitlab::Regex.semver_regex.match(str)
end
def self.match?(str, prefixed: false)
!match(str, prefixed: prefixed).nil?
end
def self.match?(str, prefixed: false)
!match(str, prefixed: prefixed).nil?
end
def self.parse(str, prefixed: false)
m = match str, prefixed: prefixed
return unless m
def self.parse(str, prefixed: false)
m = match str, prefixed: prefixed
return unless m
new(m[1].to_i, m[2].to_i, m[3].to_i, m[4], m[5], prefixed: prefixed)
end
new(m[1].to_i, m[2].to_i, m[3].to_i, m[4], m[5], prefixed: prefixed)
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
require 'fast_spec_helper'
require 'spec_helper'
RSpec.describe Packages::SemVer, type: :model do
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
subject(:expected) { semver.with(prefixed: prefixed) }
context 'prefixed' do
let(:prefixed) { true }
specify do
expect(described_class.parse('v' + str, prefixed: true)).to eq(expected)
end
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)
expect(described_class.parse('v' + str, prefixed: true)).to eq(expected)
end
context 'without prefix' do
let(:prefixed) { false }
specify do
expect(described_class.parse(str)).to eq(expected)
end
it "returns #{described_class.new(major, minor, patch, prerelease, build)} without prefix" do
expected = described_class.new(major, minor, patch, prerelease, build)
expect(described_class.parse(str)).to eq(expected)
end
end
end
......@@ -39,17 +29,9 @@ RSpec.describe Packages::SemVer, type: :model do
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
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-pre+build', 1, 0, 0, 'pre', 'build'
it_behaves_like '#parse with an invalid semver', '01.0.0'
......@@ -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', '1.0.0asdf'
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
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