class Dependabot::Uv::Version
def self.correct?(version)
def self.correct?(version) return false if version.nil? version.to_s.match?(ANCHORED_VERSION_PATTERN) end
def self.new(version)
def self.new(version) T.cast(super, Dependabot::Uv::Version) end
def <=>(other)
def <=>(other) other = Dependabot::Uv::Version.new(other.to_s) unless other.is_a?(Dependabot::Uv::Version) other = T.cast(other, Dependabot::Uv::Version) epoch_comparison = epoch <=> other.epoch return epoch_comparison unless epoch_comparison.zero? release_comparison = release_version_comparison(other) return release_comparison unless release_comparison.zero? pre_comparison = compare_keys(pre_cmp_key, other.pre_cmp_key) return pre_comparison unless pre_comparison.zero? post_comparison = compare_keys(post_cmp_key, other.post_cmp_key) return post_comparison unless post_comparison.zero? dev_comparison = compare_keys(dev_cmp_key, other.dev_cmp_key) return dev_comparison unless dev_comparison.zero? compare_keys(local_cmp_key, other.local_cmp_key) end
def compare_keys(key, other_key)
def compare_keys(key, other_key) if key.is_a?(Integer) && other_key.is_a?(Integer) key <=> other_key elsif key.is_a?(Array) && other_key.is_a?(Array) key <=> other_key elsif key.is_a?(Integer) key == NEGATIVE_INFINITY ? -1 : 1 elsif other_key.is_a?(Integer) other_key == NEGATIVE_INFINITY ? 1 : -1 end end
def dev_cmp_key
def dev_cmp_key # Versions without a dev segment should sort after those with one. return INFINITY if dev.nil? T.must(dev) end
def ignored_major_versions
def ignored_major_versions version_parts = release_segment # e.g [1,2,3] if version is 1.2.3-alpha3 lower_parts = [version_parts[0].to_i + 1] + [lowest_prerelease_suffix] # earliest next major version prerelease lower_bound = ">= #{lower_parts.join('.')}" [lower_bound] end
def ignored_minor_versions
def ignored_minor_versions parts = release_segment # e.g [1,2,3] if version is 1.2.3-alpha3 version_parts = parts.fill(0, parts.length...2) lower_parts = version_parts.first(1) + [version_parts[1].to_i + 1] + [lowest_prerelease_suffix] upper_parts = version_parts.first(0) + [version_parts[0].to_i + 1] + [lowest_prerelease_suffix] lower_bound = ">= #{lower_parts.join('.')}" upper_bound = "< #{upper_parts.join('.')}" ["#{lower_bound}, #{upper_bound}"] end
def ignored_patch_versions
def ignored_patch_versions parts = release_segment # e.g [1,2,3] if version is 1.2.3-alpha3 version_parts = parts.fill(0, parts.length...2) upper_parts = version_parts.first(1) + [version_parts[1].to_i + 1] + [lowest_prerelease_suffix] lower_bound = "> #{self}" upper_bound = "< #{upper_parts.join('.')}" ["#{lower_bound}, #{upper_bound}"] end
def initialize(version)
def initialize(version) raise Dependabot::BadRequirementError, "Malformed version string - string is nil" if version.nil? @version_string = version.to_s raise Dependabot::BadRequirementError, "Malformed version string - string is empty" if @version_string.empty? matches = ANCHORED_VERSION_PATTERN.match(@version_string.downcase) unless matches raise Dependabot::BadRequirementError, "Malformed version string - #{@version_string} does not match regex" end @epoch = matches["epoch"].to_i @release_segment = matches["release"]&.split(".")&.map(&:to_i) || [] @pre = parse_letter_version(matches["pre_l"], matches["pre_n"]) @post = parse_letter_version(matches["post_l"], matches["post_n1"] || matches["post_n2"]) @dev = parse_letter_version(matches["dev_l"], matches["dev_n"]) @local = parse_local_version(matches["local"]) super(matches["release"] || "") end
def inspect # :nodoc:
def inspect # :nodoc: "#<#{self.class} #{@version_string}>" end
def local_cmp_key
def local_cmp_key if local.nil? # Versions without a local segment should sort before those with one. NEGATIVE_INFINITY else # According to PEP440. # - Alphanumeric segments sort before numeric segments # - Alphanumeric segments sort lexicographically # - Numeric segments sort numerically # - Shorter versions sort before longer versions when the prefixes match exactly local&.map do |token| if token.is_a?(Integer) [token, ""] else [NEGATIVE_INFINITY, token] end end end end
def lowest_prerelease_suffix
def lowest_prerelease_suffix "dev0" end
def pad_for_comparison(tokens, other_tokens)
def pad_for_comparison(tokens, other_tokens) tokens = tokens.dup other_tokens = other_tokens.dup longer = [tokens, other_tokens].max_by(&:count) shorter = [tokens, other_tokens].min_by(&:count) difference = T.must(longer).length - T.must(shorter).length difference.times { T.must(shorter) << 0 } [tokens, other_tokens] end
def parse_letter_version(letter = nil, number = nil)
def parse_letter_version(letter = nil, number = nil) return if letter.nil? && number.nil? if letter # Implicit 0 for cases where prerelease has no numeral number ||= 0 # Normalize alternate spellings if letter == "alpha" letter = "a" elsif letter == "beta" letter = "b" elsif %w(c pre preview).include? letter letter = "rc" elsif %w(rev r).include? letter letter = "post" end return letter, number.to_i end # Number but no letter i.e. implicit post release syntax (e.g. 1.0-1) letter = "post" [letter, number.to_i] end
def parse_local_version(local)
def parse_local_version(local) return if local.nil? # Takes a string like abc.1.twelve and turns it into ["abc", 1, "twelve"] local.split(/[\._-]/).map { |s| /^\d+$/.match?(s) ? s.to_i : s } end
def post_cmp_key
def post_cmp_key # Versions without a post segment should sort before those with one. return NEGATIVE_INFINITY if post.nil? T.must(post) end
def pre_cmp_key
def pre_cmp_key if pre.nil? && post.nil? && dev # sort 1.0.dev0 before 1.0a0 NEGATIVE_INFINITY elsif pre.nil? INFINITY # versions without a pre-release should sort after those with one. else T.must(pre) end end
def prerelease?
def prerelease? !!(pre || dev) end
def release
def release Dependabot::Uv::Version.new(release_segment.join(".")) end
def release_version_comparison(other)
def release_version_comparison(other) tokens, other_tokens = pad_for_comparison(release_segment, other.release_segment) tokens <=> other_tokens end
def to_s
def to_s @version_string end