require "semantic_range/version" require "semantic_range/pre_release" require "semantic_range/range" require "semantic_range/comparator" module SemanticRange BUILDIDENTIFIER = /[0-9A-Za-z-]+/ BUILD = /(?:\+(#{BUILDIDENTIFIER.source}(?:\.#{BUILDIDENTIFIER.source})*))/ NUMERICIDENTIFIER = /0|[1-9]\d*/ NUMERICIDENTIFIERLOOSE = /[0-9]+/ NONNUMERICIDENTIFIER = /\d*[a-zA-Z-][a-zA-Z0-9-]*/ XRANGEIDENTIFIERLOOSE = /#{NUMERICIDENTIFIERLOOSE.source}|x|X|\*/ PRERELEASEIDENTIFIERLOOSE = /(?:#{NUMERICIDENTIFIERLOOSE.source}|#{NONNUMERICIDENTIFIER.source})/ PRERELEASELOOSE = /(?:-?(#{PRERELEASEIDENTIFIERLOOSE.source}(?:\.#{PRERELEASEIDENTIFIERLOOSE.source})*))/ XRANGEPLAINLOOSE = /[v=\s]*(#{XRANGEIDENTIFIERLOOSE.source})(?:\.(#{XRANGEIDENTIFIERLOOSE.source})(?:\.(#{XRANGEIDENTIFIERLOOSE.source})(?:#{PRERELEASELOOSE.source})?#{BUILD.source}?)?)?/ HYPHENRANGELOOSE = /^\s*(#{XRANGEPLAINLOOSE.source})\s+-\s+(#{XRANGEPLAINLOOSE.source})\s*$/ PRERELEASEIDENTIFIER = /(?:#{NUMERICIDENTIFIER.source}|#{NONNUMERICIDENTIFIER.source})/ PRERELEASE = /(?:-(#{PRERELEASEIDENTIFIER.source}(?:\.#{PRERELEASEIDENTIFIER.source})*))/ XRANGEIDENTIFIER = /#{NUMERICIDENTIFIER.source}|x|X|\*/ XRANGEPLAIN = /[v=\s]*(#{XRANGEIDENTIFIER.source})(?:\.(#{XRANGEIDENTIFIER.source})(?:\.(#{XRANGEIDENTIFIER.source})(?:#{PRERELEASE.source})?#{BUILD.source}?)?)?/ HYPHENRANGE = /^\s*(#{XRANGEPLAIN.source})\s+-\s+(#{XRANGEPLAIN.source})\s*$/ MAINVERSIONLOOSE = /(#{NUMERICIDENTIFIERLOOSE.source})\.(#{NUMERICIDENTIFIERLOOSE.source})\.(#{NUMERICIDENTIFIERLOOSE.source})/ LOOSEPLAIN = /[v=\s]*#{MAINVERSIONLOOSE.source}#{PRERELEASELOOSE.source}?#{BUILD.source}?/ GTLT = /((?:<|>)?=?)/ COMPARATORTRIM = /(\s*)#{GTLT.source}\s*(#{LOOSEPLAIN.source}|#{XRANGEPLAIN.source})/ LONETILDE = /(?:~>?)/ TILDETRIM = /(\s*)#{LONETILDE.source}\s+/ LONECARET = /(?:\^)/ CARETTRIM = /(\s*)#{LONECARET.source}\s+/ STAR = /(<|>)?=?\s*\*/ CARET = /^#{LONECARET.source}#{XRANGEPLAIN.source}$/ CARETLOOSE = /^#{LONECARET.source}#{XRANGEPLAINLOOSE.source}$/ MAINVERSION = /(#{NUMERICIDENTIFIER.source})\.(#{NUMERICIDENTIFIER.source})\.(#{NUMERICIDENTIFIER.source})/ FULLPLAIN = /v?#{MAINVERSION.source}#{PRERELEASE.source}?#{BUILD.source}?/ FULL = /^#{FULLPLAIN.source}$/ LOOSE = /^#{LOOSEPLAIN.source}$/ TILDE = /^#{LONETILDE.source}#{XRANGEPLAIN.source}$/ TILDELOOSE = /^#{LONETILDE.source}#{XRANGEPLAINLOOSE.source}$/ XRANGE = /^#{GTLT.source}\s*#{XRANGEPLAIN.source}$/ XRANGELOOSE = /^#{GTLT.source}\s*#{XRANGEPLAINLOOSE.source}$/ COMPARATOR = /^#{GTLT.source}\s*(#{FULLPLAIN.source})$|^$/ COMPARATORLOOSE = /^#{GTLT.source}\s*(#{LOOSEPLAIN.source})$|^$/ ANY = {} MAX_LENGTH = 256 class InvalidIncrement < StandardError; end class InvalidVersion < StandardError; end class InvalidComparator < StandardError; end class InvalidRange < StandardError; end def self.ltr(version, range, loose = false, platform = nil) outside(version, range, '<', loose, platform) end def self.gtr(version, range, loose = false, platform = nil) outside(version, range, '>', loose, platform) end def self.cmp(a, op, b, loose = false) case op when '===' a = a.version if !a.is_a?(String) b = b.version if !b.is_a?(String) a == b when '!==' a = a.version if !a.is_a?(String) b = b.version if !b.is_a?(String) a != b when '', '=', '==' eq(a, b, loose) when '!=' neq(a, b, loose) when '>' gt(a, b, loose) when '>=' gte(a, b, loose) when '<' lt(a, b, loose) when '<=' lte(a, b, loose) else raise 'Invalid operator: ' + op end end def self.outside(version, range, hilo, loose = false, platform = nil) version = Version.new(version, loose) range = Range.new(range, loose, platform) return false if satisfies(version, range, loose, platform) case hilo when '>' comp = '>' ecomp = '>=' when '<' comp = '<' ecomp = '<=' end range.set.each do |comparators| high = nil low = nil comparators.each do |comparator| if comparator.semver == ANY comparator = Comparator.new('>=0.0.0', loose) end high = high || comparator low = low || comparator case hilo when '>' if gt(comparator.semver, high.semver, loose) high = comparator elsif lt(comparator.semver, low.semver, loose) low = comparator end when '<' if lt(comparator.semver, high.semver, loose) high = comparator elsif gt(comparator.semver, low.semver, loose) low = comparator end end end return false if (high.operator == comp || high.operator == ecomp) case hilo when '>' if (low.operator.empty? || low.operator == comp) && lte(version, low.semver, loose) return false; elsif (low.operator == ecomp && lt(version, low.semver, loose)) return false; end when '<' if (low.operator.empty? || low.operator == comp) && gte(version, low.semver, loose) return false; elsif low.operator == ecomp && gt(version, low.semver, loose) return false; end end end true end def self.satisfies(version, range, loose = false, platform = nil) return false if !valid_range(range, loose, platform) Range.new(range, loose, platform).test(version) end def self.max_satisfying(versions, range, loose = false, platform = nil) versions.select { |version| satisfies(version, range, loose, platform) }.sort { |a, b| rcompare(a, b, loose) }[0] || nil end def self.valid_range(range, loose = false, platform = nil) begin r = Range.new(range, loose, platform).range r = '*' if r.nil? || r.empty? r rescue nil end end def self.compare(a, b, loose = false) Version.new(a, loose).compare(b) end def self.compare_loose(a, b) compare(a, b, true) end def self.rcompare(a, b, loose = false) compare(b, a, true) end def self.sort(list, loose = false) # TODO end def self.rsort(list, loose = false) # TODO end def self.lt(a, b, loose = false) compare(a, b, loose) < 0 end def self.gt(a, b, loose = false) compare(a, b, loose) > 0 end def self.eq(a, b, loose = false) compare(a, b, loose) == 0 end def self.neq(a, b, loose = false) compare(a, b, loose) != 0 end def self.gte(a, b, loose = false) compare(a, b, loose) >= 0 end def self.lte(a, b, loose = false) compare(a, b, loose) <= 0 end def self.valid(version, loose = false) v = parse(version, loose) return v ? v.version : nil end def self.clean(version, loose = false) s = parse(version.strip.gsub(/^[=v]+/, ''), loose) return s ? s.version : nil end def self.parse(version, loose = false) return version if version.is_a?(Version) return nil unless version.is_a?(String) version.strip! return nil if version.length > MAX_LENGTH rxp = loose ? LOOSE : FULL return nil if !rxp.match(version) Version.new(version, loose) end def self.increment!(version, release, loose, identifier) if loose.is_a? String identifier = loose loose = false end Version.new(version, loose).increment!(release, identifier).version rescue InvalidIncrement, InvalidVersion nil end def self.diff(a, b) a = Version.new(a, false) unless a.kind_of?(Version) b = Version.new(b, false) unless b.kind_of?(Version) pre_diff = a.prerelease.to_s != b.prerelease.to_s pre = pre_diff ? 'pre' : '' return "#{pre}major" if a.major != b.major return "#{pre}minor" if a.minor != b.minor return "#{pre}patch" if a.patch != b.patch return "prerelease" if pre_diff end def self.to_comparators(range, loose = false, platform = nil) Range.new(range, loose, platform).set.map do |comp| comp.map(&:to_s) end end end