1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
# frozen_string_literal: true
require 'strscan'
module SemverDialects
module Semver2
# Represents a token that matches any major, minor, or patch number.
ANY_NUMBER = 'x'
class Version < BaseVersion
def initialize(tokens, prerelease_tag: nil)
@tokens = tokens
@addition = prerelease_tag
end
def <=>(other)
if (idx = tokens.index(ANY_NUMBER))
a = tokens[0..(idx - 1)]
b = other.tokens[0..(idx - 1)]
return compare_tokens(a, b)
end
if (idx = other.tokens.index(ANY_NUMBER))
a = tokens[0..(idx - 1)]
b = other.tokens[0..(idx - 1)]
return compare_tokens(a, b)
end
super
end
private
# Compares pre-release tags as specified in https://semver.org/#spec-item-9.
def compare_additions(a, b) # rubocop:disable Naming/MethodParameterName
# Pre-release versions have a lower precedence than the associated normal version.
return -1 if !a.nil? && b.nil? # only self is a pre-release
return 1 if a.nil? && !b.nil? # only other is a pre-release
a <=> b
end
end
class PrereleaseTag < BaseVersion
def initialize(tokens)
@tokens = tokens
end
# Returns true if the prerelease tag is empty.
# In Semver 2 1.2.3-0 is NOT equivalent to 1.2.3.
def is_zero?
tokens.empty?
end
private
# Compares pre-release identifiers as specified in https://semver.org/#spec-item-11.
def compare_token_pair(a, b) # rubocop:disable Naming/MethodParameterName
case a
when Integer
case b
when String
# Numeric identifiers always have lower precedence than non-numeric identifiers.
return -1
when nil
# A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal.
return 1
end
when String
case b
when Integer
# Numeric identifiers always have lower precedence than non-numeric identifiers.
return 1
when nil
# A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal.
return 1
end
when nil
case b
when Integer
# A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal.
return -1
when String
# A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal.
return -1
end
end
# Identifiers have both the same type (numeric or non-numeric).
# This returns nil if the identifiers can't be compared.
a <=> b
end
end
class VersionParser
def self.parse(input)
new(input).parse
end
attr_reader :input
def initialize(input)
@input = input
@scanner = StringScanner.new(input)
end
def parse
tokens = []
prerelease_tag = nil
# skip ignore leading v if any
scanner.skip('v')
until scanner.eos?
if (s = scanner.scan(/\d+/))
tokens << s.to_i
elsif (s = scanner.scan(/\.x\z/i))
tokens << ANY_NUMBER
elsif (s = scanner.scan('.'))
# continue
elsif (s = scanner.scan('-'))
prerelease_tag = parse_prerelease_tag
elsif (s = scanner.scan(/\+.*/))
# continue
else
raise IncompleteScanError, scanner.rest
end
end
Version.new(tokens, prerelease_tag: prerelease_tag)
end
private
attr_reader :scanner
def parse_prerelease_tag
tokens = []
at_build_tag = false
until scanner.eos? || at_build_tag
if (s = scanner.scan(/\d+(?![a-zA-Z-])/))
tokens << s.to_i
elsif (s = scanner.scan(/[0-9a-zA-Z-]+/))
tokens << s
elsif (s = scanner.scan('.'))
# continue
elsif (s = scanner.scan('+'))
scanner.unscan
at_build_tag = true
else
raise IncompleteScanError, scanner.rest
end
end
PrereleaseTag.new(tokens)
end
end
end
end
|