diff --git a/lib/semantic_range.rb b/lib/semantic_range.rb index 4873849..ec9e3c6 100644 --- a/lib/semantic_range.rb +++ b/lib/semantic_range.rb @@ -1,3 +1,4 @@ +require "semantic_range/lru_cache" require "semantic_range/version" require "semantic_range/pre_release" require "semantic_range/range" @@ -45,6 +46,10 @@ module SemanticRange MAX_LENGTH = 256 + VERSION_CACHE = LRUCache.new(1000) + VERSION_CACHE_LOOSE = LRUCache.new(1000) + RANGE_CACHE = LRUCache.new(256) + class InvalidIncrement < StandardError; end class InvalidVersion < StandardError; end class InvalidComparator < StandardError; end @@ -149,11 +154,12 @@ def self.outside?(version, range, hilo, loose: false, platform: nil) end def self.satisfies?(version, range, loose: false, platform: nil) - if valid?(range, loose: loose) + if valid?(range, loose: loose) return version == range end - return false if !valid_range(range, loose: loose, platform: platform) - Range.new(range, loose: loose, platform: platform).test(version) + r = cached_range(range, loose: loose, platform: platform) + return false unless r + r.test(cached_version(version, loose: loose)) end def self.filter(versions, range, loose: false, platform: nil) @@ -171,17 +177,15 @@ def self.max_satisfying(versions, range, loose: false, platform: nil) end def self.valid_range(range, loose: false, platform: nil) - begin - r = Range.new(range, loose: loose, platform: platform).range - r = '*' if r.nil? || r.empty? - r - rescue - nil - end + r = cached_range(range, loose: loose, platform: platform) + return nil unless r + result = r.range + result = '*' if result.nil? || result.empty? + result end def self.compare(a, b, loose: false) - Version.new(a, loose: loose).compare(b) + cached_version(a, loose: loose).compare(cached_version(b, loose: loose)) end def self.compare_loose(a, b) @@ -274,6 +278,32 @@ def self.to_comparators(range, loose: false, platform: nil) end end + class << self + private + + def cached_version(version, loose:) + return version if version.is_a?(Version) + cache = loose ? VERSION_CACHE_LOOSE : VERSION_CACHE + cached = cache[version] + return cached unless cached.equal?(LRUCache::NOT_FOUND) + cache[version] = Version.new(version, loose: loose) + end + + def cached_range(range, loose:, platform:) + return range if range.is_a?(Range) + key = [range, loose, platform] + cached = RANGE_CACHE[key] + unless cached.equal?(LRUCache::NOT_FOUND) + return cached # may be nil for invalid ranges + end + begin + RANGE_CACHE[key] = Range.new(range, loose: loose, platform: platform) + rescue InvalidRange + RANGE_CACHE[key] = nil + end + end + end + class << self # Support for older non-inquisitive method versions alias_method :gt, :gt? diff --git a/lib/semantic_range/lru_cache.rb b/lib/semantic_range/lru_cache.rb new file mode 100644 index 0000000..0a1c456 --- /dev/null +++ b/lib/semantic_range/lru_cache.rb @@ -0,0 +1,24 @@ +module SemanticRange + class LRUCache + NOT_FOUND = Object.new.freeze + + def initialize(max_size) + @max_size = max_size + @data = {} + end + + def [](key) + return NOT_FOUND unless @data.key?(key) + # Move to MRU position + value = @data.delete(key) + @data[key] = value + end + + def []=(key, value) + @data.delete(key) # remove existing entry (if any) before reinserting + @data[key] = value + @data.shift if @data.size > @max_size # evict LRU entry + value + end + end +end diff --git a/lib/semantic_range/version.rb b/lib/semantic_range/version.rb index 66bfe13..201da2e 100644 --- a/lib/semantic_range/version.rb +++ b/lib/semantic_range/version.rb @@ -66,20 +66,15 @@ def compare_pre(other) prerelease <=> other.prerelease end - def self.compare_identifiers(a,b) - anum = /^[0-9]+$/.match(a.to_s) - bnum = /^[0-9]+$/.match(b.to_s) - - if anum && bnum - a = a.to_i - b = b.to_i - end + def self.compare_identifiers(a, b) + anum = a.is_a?(Integer) + bnum = b.is_a?(Integer) return (anum && !bnum) ? -1 : (bnum && !anum) ? 1 : a < b ? -1 : a > b ? 1 : - 0; + 0 end def increment!(release, identifier)