semver_dialects-1.5.0/0000755000004100000410000000000014545303723014724 5ustar www-datawww-datasemver_dialects-1.5.0/lib/0000755000004100000410000000000014545303723015472 5ustar www-datawww-datasemver_dialects-1.5.0/lib/semver_dialects.rb0000644000004100000410000000507514545303723021177 0ustar www-datawww-data# frozen_string_literal: true require 'semver_dialects/version' require 'semver_dialects/semantic_version/version_translator' require 'semver_dialects/semantic_version/version_parser' require 'semver_dialects/semantic_version/version_range' require 'deb_version' module SemverDialects # Captures all errors that could be possibly raised class Error < StandardError def initialize(msg) super(msg) end end # A utiltity module that helps with version matching module VersionChecker def self.version_translate(typ, version_string) case typ when 'maven' VersionTranslator.translate_maven(version_string) when 'npm' VersionTranslator.translate_npm(version_string) when 'conan' VersionTranslator.translate_conan(version_string) when 'nuget' VersionTranslator.translate_nuget(version_string) when 'go' VersionTranslator.translate_go(version_string) when 'gem' VersionTranslator.translate_gem(version_string) when 'pypi' VersionTranslator.translate_pypi(version_string) when 'packagist' VersionTranslator.translate_packagist(version_string) else raise SemverDialects::Error, "unsupported package type '#{typ}'" end end def self.version_sat?(typ, raw_ver, raw_constraint) # os package versions are handled very differently from application package versions return os_pkg_version_sat?(typ, raw_ver, raw_constraint) if os_purl_type?(typ) version_constraint = version_translate(typ, raw_constraint) raise SemverDialects::Error, 'malformed constraint' if version_constraint.nil? || version_constraint.empty? version = VersionParser.parse('=' + raw_ver) raise SemverDialects::Error, 'malformed constraint' if version.nil? || version.empty? constraint = VersionRange.new version_constraint.each do |version_interval_str| constraint << VersionParser.parse(version_interval_str) end constraint.overlaps_with?(version) end def self.os_purl_type?(typ) ['deb', 'rpm', 'apk'].include?(typ) end def self.os_pkg_version_sat?(typ, raw_ver, raw_constraint) if typ == 'deb' # we only support the less than operator, because that's the only one currently output # by the advisory exporter for operating system packages. raise SemverDialects::Error, 'malformed constraint' unless raw_constraint[0] == '<' v1 = DebVersion.new(raw_ver) v2 = DebVersion.new(raw_constraint[1..-1]) return v1 < v2 end end end end semver_dialects-1.5.0/lib/utils.rb0000644000004100000410000000120214545303723017152 0ustar www-datawww-data# frozen_string_literal: true # monkey-patch String class class String def unwrap s = self s = s[1..s.length - 1] if s.start_with?('(') s = s[0..s.length - 2] if s.end_with?(')') s end def range_present? !empty? end def number? true if Integer(self) rescue StandardError false end def initial self[0, 1] end def unquote delete_suffix('"').delete_prefix('"').delete_suffix('\'').delete_prefix('\'') end def csv_unquote unquote.unquote.unquote end def remove_trailing_number gsub(/([^\d]*)\d+$/, '\1') end def chars_only gsub(/[^0-9A-Za-z]/, '') end end semver_dialects-1.5.0/lib/semver_dialects/0000755000004100000410000000000014545303723020643 5ustar www-datawww-datasemver_dialects-1.5.0/lib/semver_dialects/command.rb0000644000004100000410000000444314545303723022613 0ustar www-datawww-data# frozen_string_literal: true require 'forwardable' module SemverDialects # A CLI command class Command extend Forwardable def_delegators :command, :run # Execute this command # # @api public def execute(*) raise( NotImplementedError, "#{self.class}##{__method__} must be implemented" ) end # The external commands runner # # @see http://www.rubydoc.info/gems/tty-command # # @api public def command(**options) require 'tty-command' TTY::Command.new(options) end # The cursor movement # # @see http://www.rubydoc.info/gems/tty-cursor # # @api public def cursor require 'tty-cursor' TTY::Cursor end # Open a file or text in the user's preferred editor # # @see http://www.rubydoc.info/gems/tty-editor # # @api public def editor require 'tty-editor' TTY::Editor end # File manipulation utility methods # # @see http://www.rubydoc.info/gems/tty-file # # @api public def generator require 'tty-file' TTY::File end # Terminal output paging # # @see http://www.rubydoc.info/gems/tty-pager # # @api public def pager(**options) require 'tty-pager' TTY::Pager.new(options) end # Terminal platform and OS properties # # @see http://www.rubydoc.info/gems/tty-pager # # @api public def platform require 'tty-platform' TTY::Platform.new end # The interactive prompt # # @see http://www.rubydoc.info/gems/tty-prompt # # @api public def prompt(**options) require 'tty-prompt' TTY::Prompt.new(options) end # Get terminal screen properties # # @see http://www.rubydoc.info/gems/tty-screen # # @api public def screen require 'tty-screen' TTY::Screen end # The unix which utility # # @see http://www.rubydoc.info/gems/tty-which # # @api public def which(*args) require 'tty-which' TTY::Which.which(*args) end # Check if executable exists # # @see http://www.rubydoc.info/gems/tty-which # # @api public def exec_exist?(*args) require 'tty-which' TTY::Which.exist?(*args) end end end semver_dialects-1.5.0/lib/semver_dialects/cli.rb0000644000004100000410000000171514545303723021743 0ustar www-datawww-data# frozen_string_literal: true require 'thor' module SemverDialects # Handle the application command line parsing # and the dispatch to various command objects # # @api public class CLI < Thor # Error raised by this runner Error = Class.new(StandardError) desc 'version', 'semver_dialects version' def version require_relative 'version' puts "v#{SemverDialects::VERSION}" end map %w[--version -v] => :version desc 'check_version TYPE VERSION CONSTRAINT', 'Command description...' method_option :help, aliases: '-h', type: :boolean, desc: 'Display usage information' def check_version(type, version, constraint) if options[:help] invoke :help, ['check_version'] else require_relative 'commands/check_version' ecode = SemverDialects::Commands::CheckVersion.new(type, version, constraint, options).execute exit(ecode) end end end end semver_dialects-1.5.0/lib/semver_dialects/commands/0000755000004100000410000000000014545303723022444 5ustar www-datawww-datasemver_dialects-1.5.0/lib/semver_dialects/commands/check_version.rb0000644000004100000410000000212114545303723025607 0ustar www-datawww-data# frozen_string_literal: true require_relative '../command' require_relative '../../semver_dialects.rb' require_relative '../semantic_version/version_translator.rb' require_relative '../semantic_version/version_parser.rb' require_relative '../semantic_version/version_range.rb' module SemverDialects module Commands # The check version command implementation class CheckVersion < SemverDialects::Command def initialize(type, version, constraint, options) @type = type @version = version @constraint = constraint @options = options @avail_types = %w[gem npm ruby pypi php maven go] end def execute(_input: $stdin, output: $stdout) typ = @type.downcase raise SemverDialects::Error, 'wrong package type' unless @avail_types.include?(typ) if VersionChecker.version_sat?(typ, @version, @constraint) output.puts "#{@version} matches #{@constraint} for #{@type}" 0 else output.puts "#{@version} does not match #{@constraint}" 1 end end end end end semver_dialects-1.5.0/lib/semver_dialects/semantic_version/0000755000004100000410000000000014545303723024213 5ustar www-datawww-datasemver_dialects-1.5.0/lib/semver_dialects/semantic_version/version_translator.rb0000644000004100000410000001065014545303723030500 0ustar www-datawww-data# frozen_string_literal: true require_relative '../../utils.rb' # A prepropcessor -- convert different version strings to something that can be digested by the # version parser module VersionTranslator def self.translate_npm(version_string) version_string.split('||').map do |item| add_missing_operator(single_space_after_operator(item.strip.gsub(/&&/, ' '))) end end def self.translate_conan(version_string) translate_npm(version_string) end def self.translate_go(version_string) translate_gem(version_string) end def self.translate_gem(version_string) version_string.split('||').map do |item| add_missing_operator(single_space_after_operator(item.strip.gsub(/\s+/, ' '))) end end def self.translate_packagist(version_string) translate_pypi(version_string) end def self.translate_pypi(version_string) version_string.split('||').map do |item| add_missing_operator(single_space_after_operator(comma_to_space(item))) end end def self.translate_nuget(version_string) translate_maven(version_string) end def self.translate_maven(version_string) lexing_maven_version_string(version_string).map { |item| translate_mvn_version_item(item) } end def self.add_missing_operator(version_string) starts_with_operator?(version_string) ? version_string : "=#{version_string}" end def self.single_space_after_operator(version_string) version_string.gsub(/([>=<]+) +/, '\1').gsub(/\s+/, ' ') end def self.starts_with_operator?(version_item) version_item.match(/^[=><]/) ? true : false end def self.comma_to_space(version_string) version_string.strip.gsub(/,/, ' ') end def self.lexing_maven_version_string(version_string) open = false substring = '' ret = [] version_string.each_char do |c| case c when '(', '[' if open puts "malformed maven version string #{version_string}" exit(-1) else unless substring.empty? ret << substring substring = '' end open = true substring += c end when ')', ']' if !open puts "malformed maven version string #{version_string}" exit(-1) else open = false substring += c ret << substring substring = '' end when ',' substring += c if open when ' ' # nothing to do substring += '' else substring += c end end if open puts "malformed maven version string #{version_string}" exit(-1) end ret << substring unless substring.empty? ret end def self.parenthesized?(version_item) version_item.match(/^[\(\[]/) && version_item.match(/[\]\)]$/) ? true : false end def self.translate_mvn_version_item(version_item) content = '' parens_pattern = '' if parenthesized?(version_item) content = version_item[1, version_item.size - 2] parens_pattern = version_item[0] + version_item[version_item.size - 1] # special case -- unversal version range return '=*' if content.strip == ',' else # according to the doc, if there is a plain version string in maven, it means 'starting from version x' # https://docs.oracle.com/middleware/1212/core/MAVEN/maven_version.htm#MAVEN8903 content = "#{version_item}," parens_pattern = '[)' end args = content.split(',') first_non_empty_arg = args.find(&:range_present?) if content.start_with?(',') # {,y} case parens_pattern when '[]' "<=#{first_non_empty_arg}" when '()' "<#{first_non_empty_arg}" when '[)' "<#{first_non_empty_arg}" else # par_pattern == "(]" "<=#{first_non_empty_arg}" end elsif content.end_with?(',') # {x,} case parens_pattern when '[]' ">=#{first_non_empty_arg}" when '()' ">#{first_non_empty_arg}" when '[)' ">=#{first_non_empty_arg}" else # par_pattern == "(]" ">#{first_non_empty_arg}" end elsif content[','].nil? # [x,x] "=#{content}" else case parens_pattern when '[]' ">=#{args[0]} <=#{args[1]}" when '()' ">#{args[0]} <#{args[1]}" when '[)' ">=#{args[0]} <#{args[1]}" else # par_pattern == "(]" ">#{args[0]} <=#{args[1]}" end end end end semver_dialects-1.5.0/lib/semver_dialects/semantic_version/version_interval.rb0000644000004100000410000002214014545303723030130 0ustar www-datawww-datamodule IntervalType UNKNOWN = 0 LEFT_OPEN = 1 LEFT_CLOSED = 2 RIGHT_OPEN = 4 RIGHT_CLOSED = 8 end class VersionInterval attr_accessor :type, :start_cut, :end_cut def initialize(type, start_cut, end_cut) @type = type @start_cut = start_cut @end_cut = end_cut end def intersect(other_interval) # this look odd -- we have to use it here though, because it may be that placeholders are present inside # the version for which > and < would yield true return EmptyInterval.new if !(@start_cut <= other_interval.end_cut) || !(other_interval.start_cut <= @end_cut) start_cut_new = max(@start_cut, other_interval.start_cut) end_cut_new = min(@end_cut, other_interval.end_cut) # compute the boundaries for the intersection type = compute_intersection_boundary(self, other_interval, start_cut_new, end_cut_new) interval = VersionInterval.new(type, start_cut_new, end_cut_new) half_open = !(interval.bit_set?(IntervalType::RIGHT_CLOSED) && interval.bit_set?(IntervalType::LEFT_CLOSED)) interval.singleton? && half_open ? EmptyInterval.new : interval end def union(other_interval) return EmptyInterval.new if self.intersect(other_interval).instance_of?(EmptyInterval) start_cut_new = min(@start_cut, other_interval.start_cut) end_cut_new = max(@end_cut, other_interval.end_cut) # compute the boundaries for the union type = compute_union_boundary(self, other_interval, start_cut_new, end_cut_new) VersionInterval.new(type, start_cut_new, end_cut_new) end def collapse(other_interval) return EmptyInterval.new if self.intersect(other_interval).instance_of?(EmptyInterval) frame = [@start_cut, other_interval.start_cut, @end_cut, other_interval.end_cut].reject { |cut| special(cut) } min_cut = frame.reduce { |smallest, current| smallest < current ? smallest : current } max_cut = frame.reduce { |biggest, current| biggest > current ? biggest : current } # compute the boundaries for the union type = compute_union_boundary(self, other_interval, min_cut, max_cut) VersionInterval.new(type, min_cut, max_cut) end def special(cut) cut.instance_of?(AboveAll) || cut.instance_of?(BelowAll) end def to_s s = "" s += bit_set?(IntervalType::LEFT_CLOSED) ? "[" : "" s += bit_set?(IntervalType::LEFT_OPEN) ? "(" : "" s += [@start_cut, @end_cut].join(",") s += bit_set?(IntervalType::RIGHT_CLOSED) ? "]" : "" s += bit_set?(IntervalType::RIGHT_OPEN) ? ")" : "" s end # this function returns a human-readable descriptions of the version strings def to_description_s s = "" if self.distinct? s = "version #{@start_cut.to_s}" elsif self.universal? s = "all versions " else s = "all versions " s += start_cut.instance_of?(BelowAll) ? "" : bit_set?(IntervalType::LEFT_OPEN) ? "after #{@start_cut.to_s} " : bit_set?(IntervalType::LEFT_CLOSED) ? "starting from #{@start_cut.to_s} " : "" s += end_cut.instance_of?(AboveAll) ? "" : bit_set?(IntervalType::RIGHT_OPEN) ? "before #{@end_cut.to_s}" : bit_set?(IntervalType::RIGHT_CLOSED) ? "up to #{@end_cut.to_s}" : "" end s.strip end def to_nuget_s to_maven_s end def to_maven_s s = "" # special case -- distinct version if self.distinct? s += "[#{@start_cut.to_s}]" else s += start_cut.instance_of?(BelowAll) ? "(," : bit_set?(IntervalType::LEFT_OPEN) ? "[#{@start_cut.to_s}," : bit_set?(IntervalType::LEFT_CLOSED) ? "[#{@start_cut.to_s}," : "" s += end_cut.instance_of?(AboveAll) ? ")" : bit_set?(IntervalType::RIGHT_OPEN) ? "#{@end_cut.to_s})" : bit_set?(IntervalType::RIGHT_CLOSED) ? "#{@end_cut.to_s}]" : "" end s end def distinct? bit_set?(IntervalType::LEFT_CLOSED) && bit_set?(IntervalType::RIGHT_CLOSED) && @start_cut == @end_cut end def subsumes?(other) @start_cut <= other.start_cut && @end_cut >= other.end_cut end def universal? (bit_set?(IntervalType::LEFT_OPEN) && bit_set?(IntervalType::RIGHT_OPEN) && @start_cut.instance_of?(BelowAll) && @end_cut.instance_of?(AboveAll)) || @start_cut.is_initial_version? && @end_cut.instance_of?(AboveAll) end def to_gem_s get_canoncial_s end def to_ruby_s get_canoncial_s end def to_npm_s get_canoncial_s end def to_conan_s get_canoncial_s end def to_go_s get_canoncial_s end def to_pypi_s get_canoncial_s(',', '==') end def to_packagist_s get_canoncial_s(',') end def empty? self.instance_of?(EmptyInterval) end def singleton? @start_cut == @end_cut && @start_cut.value == @end_cut.value end def diff(other_interval, abs = true) if self.distinct? && other_interval.distinct? self.start_cut.diff(other_interval.start_cut, abs) else EmptyInterval.new() end end def joinable?(other_interval) other_interval.start_cut.is_successor_of?(self.end_cut) || universal? end def join(other_interval) if self.joinable?(other_interval) _join(self, other_interval) elsif other_interval.joinable?(self) _join(other_interval, self) else EmptyInterval.new() end end def cross_total if distinct? @start_cut.cross_total else @start_cut.cross_total + @end_cut.cross_total end end def ==(other_interval) @start_cut == other_interval.start_cut && @end_cut == other_interval.end_cut && @type == other_interval.type end # inverts the given version interval -- note that this function amy return two version intervals # e.g., (2,10] -> (-inf, 2], (10, +inf) # left right def invert intervals = [] left_type = bit_set?(IntervalType::LEFT_OPEN) ? IntervalType::RIGHT_CLOSED : IntervalType::RIGHT_OPEN left_type = left_type | IntervalType::LEFT_OPEN right_type = bit_set?(IntervalType::RIGHT_OPEN) ? IntervalType::LEFT_CLOSED : IntervalType::LEFT_OPEN right_type = right_type | IntervalType::RIGHT_OPEN intervals << VersionInterval.new(left_type, BelowAll.new, @start_cut) unless @start_cut.instance_of?(BelowAll) intervals << VersionInterval.new(right_type, @end_cut, AboveAll.new) unless @end_cut.instance_of?(AboveAll) intervals end def bit_set?(interval_type) @type & interval_type != 0 end protected # computes the boundary type for union operation def compute_union_boundary(interval_a, interval_b, start_cut_new, end_cut_new) compute_boundary(interval_a, interval_b, start_cut_new, end_cut_new, IntervalType::LEFT_CLOSED, IntervalType::RIGHT_CLOSED) end def compute_intersection_boundary(interval_a, interval_b, start_cut_new, end_cut_new) compute_boundary(interval_a, interval_b,start_cut_new, end_cut_new, IntervalType::LEFT_OPEN, IntervalType::RIGHT_OPEN) end def compute_boundary(interval_a, interval_b, start_cut_new, end_cut_new, left_check, right_check) start_cut_a = interval_a.start_cut end_cut_a = interval_a.end_cut type_a = interval_a.type start_cut_b = interval_b.start_cut end_cut_b = interval_b.end_cut type_b = interval_b.type left_fill = left_check == IntervalType::LEFT_OPEN ? IntervalType::LEFT_CLOSED : IntervalType::LEFT_OPEN right_fill = right_check == IntervalType::RIGHT_OPEN ? IntervalType::RIGHT_CLOSED : IntervalType::RIGHT_OPEN # compute the boundaries for the union if start_cut_b == start_cut_a && start_cut_b == start_cut_b one_left_closed = left_type(type_a) == left_check || left_type(type_b) == left_check left_type = one_left_closed ? left_check : left_fill else left_type = start_cut_new == start_cut_a ? left_type(type_a) : left_type(type_b) end if end_cut_b == end_cut_a && end_cut_b == end_cut_b one_right_closed = right_type(type_a) == right_check || right_type(type_b) == right_check right_type = one_right_closed ? right_check : right_fill else right_type = end_cut_new == end_cut_a ? right_type(type_a) : right_type(type_b) end left_type | right_type end def _join(first_interval, second_interval) if first_interval.joinable?(second_interval) VersionInterval.new(first_interval.type, first_interval.start_cut.dup, second_interval.end_cut.dup) else EmptyInterval.new() end end def get_canoncial_s(delimiter = " ", eq = '=') if self.distinct? "#{eq}#{@start_cut.to_s}" else first = start_cut.instance_of?(BelowAll) ? "" : bit_set?(IntervalType::LEFT_OPEN) ? ">#{@start_cut.to_s}" : bit_set?(IntervalType::LEFT_CLOSED) ? ">=#{@start_cut.to_s}" : "" second = end_cut.instance_of?(AboveAll) ? "" : bit_set?(IntervalType::RIGHT_OPEN) ? "<#{@end_cut.to_s}" : bit_set?(IntervalType::RIGHT_CLOSED) ? "<=#{@end_cut.to_s}" : "" !first.empty? && !second.empty? ? "#{first}#{delimiter}#{second}" : first + second end end def max(cut_a, cut_b) cut_a > cut_b ? cut_a : cut_b end def min(cut_a, cut_b) cut_a < cut_b ? cut_a : cut_b end def right_type(type) (IntervalType::RIGHT_OPEN | IntervalType::RIGHT_CLOSED) & type end def left_type(type) (IntervalType::LEFT_OPEN | IntervalType::LEFT_CLOSED) & type end end class EmptyInterval < VersionInterval def initialize() end def to_s "empty" end end semver_dialects-1.5.0/lib/semver_dialects/semantic_version/semantic_version.rb0000644000004100000410000002147414545303723030120 0ustar www-datawww-data# frozen_string_literal: true require_relative '../../utils.rb' class SemanticVersion attr_reader :version_string, :prefix_delimiter, :prefix_segments, :suffix_segments, :segments def initialize(version_string, segments = nil) @version_string = version_string @prefix_segments = [] @suffix_segments = [] @prefix_delimiter = 0 version, _ = version_string.split('+') if segments.nil? @segments = split_version_string!(version) else @prefix_segments = segments @prefix_delimiter = segments.size - 1 @segments = segments end end def split_version_string!(version_string) delim_pattern = /[\.\-]/ split_array = version_string.split(delim_pattern).map { |grp| grp.split(/(\d+)/).reject { |cell| cell.nil? || cell.empty? } }.flatten # go as far to the right as possible considering numbers and placeholders (0..split_array.size - 1).each do |i| if split_array[i].number? || split_array[i] == "X" || split_array[i] == "x" @prefix_delimiter = i else break end end # remove redundant trailing zeros @prefix_delimiter.downto(0).each do |i| if split_array[i] == "0" split_array.delete_at(i) @prefix_delimiter -= 1 else break end end @prefix_segments = split_array[0..@prefix_delimiter].map { |group_string| SemanticVersionSegment.new(group_string) } unless @prefix_delimiter < 0 if split_array.size - 1 >= @prefix_delimiter + 1 @suffix_segments = split_array[@prefix_delimiter + 1, split_array.size].map { |group_string| SemanticVersionSegment.new(group_string) } end @prefix_segments.clone().concat(@suffix_segments) end def _get_equalized_arrays_for(array_a, array_b) first_array = array_a.clone() second_array = array_b.clone() if first_array.size < second_array.size (second_array.size - first_array.size).times do first_array << SemanticVersionSegment.new("0") end elsif first_array.size > second_array.size (first_array.size - second_array.size).times do second_array << SemanticVersionSegment.new("0") end end [first_array, second_array] end def get_equalized_arrays_for(semver_a, semver_b) first_array_prefix = semver_a.prefix_segments.clone() second_array_prefix = semver_b.prefix_segments.clone() first_array_suffix = semver_a.suffix_segments.clone() second_array_suffix = semver_b.suffix_segments.clone() first_array_prefix, second_array_prefix = _get_equalized_arrays_for(first_array_prefix, second_array_prefix) first_array_suffix, second_array_suffix = _get_equalized_arrays_for(first_array_suffix, second_array_suffix) [first_array_prefix.concat(first_array_suffix), second_array_prefix.concat(second_array_suffix)] end def get_equalized_prefix_arrays_for(semver_a, semver_b) first_array_prefix = semver_a.prefix_segments.clone() second_array_prefix = semver_b.prefix_segments.clone() first_array_prefix, second_array_prefix = _get_equalized_arrays_for(first_array_prefix, second_array_prefix) [first_array_prefix, second_array_prefix] end def <(other_semver) self_array, other_array = get_equalized_arrays_for(self, other_semver) (0..self_array.size - 1).each { |i| if self_array[i] < other_array[i] return true elsif self_array[i] > other_array[i] return false end } false end def is_zero? @prefix_segments.empty? || @prefix_segments.all? { |segment| segment.is_zero? } end def pre_release? @suffix_segments.any?(&:is_pre_release) end def post_release? @suffix_segments.any?(&:is_post_release) end def >(other_semver) self_array, other_array = get_equalized_arrays_for(self, other_semver) (0..self_array.size - 1).each { |i| if self_array[i] > other_array[i] return true elsif self_array[i] < other_array[i] return false end } false end def >=(other_semver) self == other_semver || self > other_semver || self == other_semver end def <=(other_semver) self == other_semver || self < other_semver end def ==(other_semver) segments_a = [] segments_b = [] segments_a += other_semver.segments segments_b += @segments if other_semver.segments.size < @segments.size (@segments.size - other_semver.segments.size).times {|_| segments_a << SemanticVersionSegment.new("0")} elsif other_semver.segments.size > @segments.size (other_semver.segments.size - @segments.size).times {|_| segments_b << SemanticVersionSegment.new("0") } end (0..segments_a.size - 1).each { |i| if segments_a[i] != segments_b[i] return false end } true end def !=(other_semver) return !(self == other_semver) end def diff(other_semver, abs = true) diffs = [] self_array, other_array = get_equalized_prefix_arrays_for(self, other_semver) (0..self_array.size - 1).each { |i| # diff semantic groups diffs << (self_array[i].diff(other_array[i], abs)) } diffs end def to_normalized_s @segments.map { |group| group.to_normalized_s }.join(":").to_s end def to_s @segments.map { |group| group.to_s }.join(":").to_s end def minor @prefix_segments.size >= 1 ? @prefix_segments[1].to_s : "0" end def major @prefix_segments.size >= 2 ? @prefix_segments[0].to_s : "0" end def patch @prefix_segments.size >= 3 ? @prefix_segments[2].to_s : "0" end def is_successor_of?(other_semver) filtered_segments = self.diff(other_semver, false).reject { |i| i.to_i == 0 } filtered_segments.size == 1 && filtered_segments.last.to_i == 1 end def cross_total() [self.major, self.minor, self.patch].reject { |i| i.empty? }.map { |i| i.to_i }.inject(0) { |sum, x| sum + x } end end class SemanticVersionSegment attr_accessor :normalized_group_string, :original_group_string, :is_post_release, :is_pre_release @@group_suffixes = { # pre-releases "PRE" => -16, "PREVIEW" => -16, "DEV" => -15, "A" => -14, "ALPHA" => -13, "B" => -12, "BETA" => -12, "RC" => -11, "M" => -10, "RELEASE" => 0, "FINAL" => 0, # PHP specific "STABLE" => 0, # post-releases "SP" => 1, } def initialize(group_string) @is_post_release = false @is_pre_release = false @version_string = group_string @original_group_string = group_string # use x as unique placeholder group_string_ucase = group_string.to_s.gsub(/\*/, "x").upcase if @@group_suffixes.key?(group_string_ucase) value = @@group_suffixes[group_string_ucase] @is_post_release = value > 0 @is_pre_release = value < 0 @normalized_group_string = @@group_suffixes[group_string_ucase].to_s else @normalized_group_string = group_string_ucase end end def compare(semver_a, semver_b, ret_anr_bnonr, ret_anonr_bnr, comparator) if semver_a.number? && semver_b.number? semver_a.to_i.send(comparator, semver_b.to_i) elsif semver_a.number? && !semver_b.number? if semver_b == "X" true else ret_anr_bnonr end elsif !semver_a.number? && semver_b.number? if semver_a == "X" true else ret_anonr_bnr end else # !semantic_version_b.group_string.is_number? && !semantic_version_agrous_string.is_number? if semver_a == "X" || semver_b == "X" true else semver_a.send(comparator, semver_b) end end end def <(other_semver) compare(self.normalized_group_string, other_semver.normalized_group_string, true, false, :<) end def >(other_semver) compare(self.normalized_group_string, other_semver.normalized_group_string, false, true, :>) end def >=(other_semver) self == other_semver || compare(self.normalized_group_string, other_semver.normalized_group_string, false, true, :>) end def <=(other_semver) self == other_semver || compare(self.normalized_group_string, other_semver.normalized_group_string, true, false, :<) end def ==(other_semver) self.normalized_group_string == other_semver.normalized_group_string end def !=(other_semver) return !(self == other_semver) end def to_normalized_s @normalized_group_string end def to_s @version_string end def is_number? self.normalized_group_string.number? end def is_zero? is_number? ? self.normalized_group_string.to_i == 0 : false end def diff(other_semver, abs = true) if other_semver.normalized_group_string == "X" || self.normalized_group_string == "X" "0" elsif self.normalized_group_string.number? && other_semver.normalized_group_string.number? ret = self.normalized_group_string.to_i - other_semver.normalized_group_string.to_i (abs && ret < 0) ? -ret : ret else nil end end end semver_dialects-1.5.0/lib/semver_dialects/semantic_version/version_cut.rb0000644000004100000410000000613414545303723027104 0ustar www-datawww-datarequire_relative "semantic_version" class VersionCut attr_accessor :semver, :value def initialize(value, segments = nil) @value = value.to_s if segments.nil? @semver = SemanticVersion.new(@value) else @semver = SemanticVersion.new(@value, segments) end end def to_s @value.to_s end def diff(other_cut, abs = true) if other_cut.instance_of?(BelowAll) AboveAll.new() elsif other_cut.instance_of?(AboveAll) BelowAll.new() else diff = self.semver.diff(other_cut.semver, abs) if diff.nil? EmptyInterval.new() else VersionCut.new(diff.join("."), diff) end end end def is_successor_of?(other_cut) return true if other_cut.instance_of?(BelowAll) return false if other_cut.instance_of?(AboveAll) self.semver.is_successor_of?(other_cut.semver) end def <(other_cut) other_cut.instance_of?(BelowAll) ? false : other_cut.instance_of?(AboveAll) ? true : @semver < other_cut.semver end def >(other_cut) other_cut.instance_of?(BelowAll) ? true : other_cut.instance_of?(AboveAll) ? false : @semver > other_cut.semver end def <=(other_cut) self < other_cut || self == other_cut end def >=(other_cut) self > other_cut || self == other_cut end def ==(other_cut) # self cannot be BelowAll or AboveAll if other_cut.instance_of?(BelowAll) || other_cut.instance_of?(AboveAll) false else @semver == other_cut.semver end end def !=(other_cut) # self cannot be BelowAll or AboveAll if other_cut.instance_of?(BelowAll) || other_cut.instance_of?(AboveAll) false else @semver != other_cut.semver end end def is_initial_version? @semver.is_zero? end def major @semver.major end def minor @semver.minor end def patch @semver.patch end def cross_total [minor, major, patch].reject { |i| i.empty? }.map { |i| i.to_i }.inject(0) { |sum, x| sum + x } end end class BelowAll < VersionCut def initialize() end def to_s "-inf" end def is_initial_version? false end def <(other_cut) other_cut.instance_of?(BelowAll) ? false : true end def >(other_cut) false end def <=(other_cut) self < other_cut || self == other_cut end def >=(other_cut) self > other_cut || self == other_cut end def ==(other_cut) (other_cut.instance_of? BelowAll) ? true : false end def !=(other_cut) !(self == other_cut) end def is_successor_of?(other_cut) false end end class AboveAll < VersionCut def initialize() end def to_s "+inf" end def is_initial_version? false end def <(other_cut) false end def >(other_cut) other_cut.instance_of?(AboveAll) ? false : true end def <=(other_cut) self < other_cut || self == other_cut end def >=(other_cut) self > other_cut || self == other_cut end def ==(other_cut) (other_cut.instance_of? AboveAll) ? true : false end def !=(other_cut) !(self == other_cut) end def is_successor_of?(other_cut) !other_cut.instance_of?(AboveAll) end end semver_dialects-1.5.0/lib/semver_dialects/semantic_version/version_range.rb0000644000004100000410000001044614545303723027406 0ustar www-datawww-data# frozen_string_literal: true require_relative 'version_parser' require 'set' # VersionRange is a utility class that helps managing consecutive version ranges automatically # given that they are added in-order # Note that join_if_possible should be only activated in case the ranges are added in consecutive order!! class VersionRange attr_reader :version_intervals, :join_if_possible def initialize(join_if_possible = true) @version_intervals = [] @version_interval_set = Set.new @join_if_possible = join_if_possible end def add_all(version_range) version_range.version_intervals.each { |interval| add(interval) } end def add(version_interval) return if @version_interval_set.include?(version_interval) if @join_if_possible if @version_intervals.empty? @version_intervals << version_interval @version_interval_set.add(version_interval) else last = @version_intervals.last # nothing to do return if last.end_cut == version_interval.start_cut && last.end_cut.value == version_interval.start_cut.value if last.joinable?(version_interval) @version_intervals[@version_intervals.size - 1] = last.join(version_interval) else @version_intervals << version_interval @version_interval_set.add(version_interval) end end else @version_intervals << version_interval @version_interval_set.add(version_interval) end end def <<(item) add(item) end def size @version_intervals.size end def to_s @version_intervals.map(&:to_s).join(',') end def to_description_s @version_intervals.map(&:to_description_s).join(', ').capitalize end def to_npm_s @version_intervals.map(&:to_npm_s).join('||') end def to_conan_s to_npm_s end def to_nuget_s to_maven_s end def to_maven_s @version_intervals.map(&:to_maven_s).join(',') end def to_gem_s @version_intervals.map(&:to_gem_s).join('||') end def to_pypi_s @version_intervals.map(&:to_pypi_s).join('||') end def to_go_s @version_intervals.map(&:to_go_s).join('||') end def to_packagist_s @version_intervals.map(&:to_packagist_s).join('||') end def to_version_s(package_type) case package_type when 'npm' to_npm_s when 'nuget' to_nuget_s when 'maven' to_maven_s when 'gem' to_gem_s when 'pypi' to_pypi_s when 'packagist' to_packagist_s when 'go' to_go_s when 'conan' to_conan_s else '' end end # inverts the given version interval -- note that this function amy return two version intervals # e.g., (2,10], (12, 13], [15, +inf) # 1) invert: (-inf, 2], (10, +inf), (-inf, 12], (13, +inf), (15) # 2) collapse (-inf, 2], (10, 12], (13, 15) # # the collapsed inverted ranges can potentially contain fixed versions def invert inverted = @version_intervals.map(&:invert).flatten version_intervals = collapse_intervals(inverted) version_intervals_to_range(version_intervals) end def collapse version_intervals = collapse_intervals(@version_intervals) version_intervals_to_range(version_intervals) end def includes?(version_interval) @version_interval_set.include?(version_interval) end def overlaps_with?(version_interval) @version_interval_set.each do |interval| return true unless interval.intersect(version_interval).instance_of? EmptyInterval end false end def first @version_intervals.first end def empty? @version_intervals.empty? end def any? @version_intervals.any? end def universal? @version_intervals.each do |version_interval| return true if version_interval.universal? end false end private def version_intervals_to_range(version_intervals) ret = VersionRange.new(false) version_intervals.each do |version_interval| ret << version_interval end ret end def collapse_intervals(version_intervals) interval_cp = [] interval_cp += version_intervals ret = [interval_cp.shift] interval_cp.each do |item| last = ret.last if last.intersect(item).instance_of?(EmptyInterval) ret << item else ret.pop ret << last.collapse(item) end end ret end end semver_dialects-1.5.0/lib/semver_dialects/semantic_version/version_parser.rb0000644000004100000410000000340514545303723027603 0ustar www-datawww-datarequire_relative "version_cut" require_relative "version_interval" module VersionParser def self.parse(versionstring) if (versionstring == "=*") # special case = All Versions return VersionInterval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_OPEN, BelowAll.new(), AboveAll.new()) end version_items = versionstring.split(" ") interval = VersionInterval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_OPEN, BelowAll.new(), AboveAll.new()) version_items.each do |version_item| matches = version_item.match /(?[><=]+) *(?[a-zA-Z0-9\-_\.\*]+)/ version_string = matches[:version] case matches[:op] when ">=" new_interval = VersionInterval.new(IntervalType::LEFT_CLOSED | IntervalType::RIGHT_OPEN, VersionCut.new(version_string), AboveAll.new()) interval = interval.intersect(new_interval) when "<=" new_interval = VersionInterval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_CLOSED, BelowAll.new(), VersionCut.new(version_string)) interval = interval.intersect(new_interval) when "<" new_interval = VersionInterval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_OPEN, BelowAll.new(), VersionCut.new(version_string)) interval = interval.intersect(new_interval) when ">" new_interval = VersionInterval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_OPEN, VersionCut.new(version_string), AboveAll.new()) interval = interval.intersect(new_interval) when "=", "==" new_interval = VersionInterval.new(IntervalType::LEFT_CLOSED | IntervalType::RIGHT_CLOSED, VersionCut.new(version_string), VersionCut.new(version_string)) interval = interval.intersect(new_interval) end end interval end end semver_dialects-1.5.0/lib/semver_dialects/version.rb0000644000004100000410000000011514545303723022652 0ustar www-datawww-data# frozen_string_literal: true module SemverDialects VERSION = '1.5.0' end semver_dialects-1.5.0/semver_dialects.gemspec0000644000004100000410000000624314545303723021447 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: semver_dialects 1.5.0 ruby lib Gem::Specification.new do |s| s.name = "semver_dialects".freeze s.version = "1.5.0" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "allowed_push_host" => "https://rubygems.org", "changelog_uri" => "https://gitlab.com/gitlab-org/vulnerability-research/foss/semver_dialects/-/blob/master/CHANGELOG.md", "homepage_uri" => "https://rubygems.org/gems/semver_dialects", "source_code_uri" => "https://gitlab.com/gitlab-org/vulnerability-research/foss/semver_dialects" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Julian Thome".freeze, "Isaac Dawson".freeze, "James Jonhson".freeze] s.bindir = "exe".freeze s.date = "2023-11-06" s.description = "This gem helps to parse, process and compare semantic versions for Maven, NPM, PHP, RubyGems and python packages.".freeze s.email = ["jthome@gitlab.com".freeze, "idawson@gitlab.com".freeze, "jjohnson@gitlab.com".freeze] s.files = ["lib/semver_dialects.rb".freeze, "lib/semver_dialects/cli.rb".freeze, "lib/semver_dialects/command.rb".freeze, "lib/semver_dialects/commands/check_version.rb".freeze, "lib/semver_dialects/semantic_version/semantic_version.rb".freeze, "lib/semver_dialects/semantic_version/version_cut.rb".freeze, "lib/semver_dialects/semantic_version/version_interval.rb".freeze, "lib/semver_dialects/semantic_version/version_parser.rb".freeze, "lib/semver_dialects/semantic_version/version_range.rb".freeze, "lib/semver_dialects/semantic_version/version_translator.rb".freeze, "lib/semver_dialects/version.rb".freeze, "lib/utils.rb".freeze] s.homepage = "https://rubygems.org/gems/semver_dialects".freeze s.licenses = ["MIT".freeze] s.rubygems_version = "3.3.15".freeze s.summary = "This gem provides utility function to process semantic versions expressed in different dialects.".freeze if s.respond_to? :specification_version then s.specification_version = 4 end if s.respond_to? :add_runtime_dependency then s.add_development_dependency(%q.freeze, ["~> 2.4.9"]) s.add_runtime_dependency(%q.freeze, ["~> 1.0.1"]) s.add_runtime_dependency(%q.freeze, ["~> 0.8.0"]) s.add_development_dependency(%q.freeze, ["~> 12.3.3"]) s.add_development_dependency(%q.freeze, ["~> 3.0"]) s.add_development_dependency(%q.freeze, ["~> 0.17.1"]) s.add_runtime_dependency(%q.freeze, ["~> 1.3"]) s.add_runtime_dependency(%q.freeze, ["~> 0.10.1"]) else s.add_dependency(%q.freeze, ["~> 2.4.9"]) s.add_dependency(%q.freeze, ["~> 1.0.1"]) s.add_dependency(%q.freeze, ["~> 0.8.0"]) s.add_dependency(%q.freeze, ["~> 12.3.3"]) s.add_dependency(%q.freeze, ["~> 3.0"]) s.add_dependency(%q.freeze, ["~> 0.17.1"]) s.add_dependency(%q.freeze, ["~> 1.3"]) s.add_dependency(%q.freeze, ["~> 0.10.1"]) end end