summaryrefslogtreecommitdiff
path: root/lib/bundler/index.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bundler/index.rb')
-rw-r--r--lib/bundler/index.rb203
1 files changed, 203 insertions, 0 deletions
diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb
new file mode 100644
index 0000000000..9aef2dfa12
--- /dev/null
+++ b/lib/bundler/index.rb
@@ -0,0 +1,203 @@
+# frozen_string_literal: true
+
+module Bundler
+ class Index
+ include Enumerable
+
+ def self.build
+ i = new
+ yield i
+ i
+ end
+
+ attr_reader :specs, :duplicates, :sources
+ protected :specs, :duplicates
+
+ RUBY = "ruby"
+ NULL = "\0"
+
+ def initialize
+ @sources = []
+ @cache = {}
+ @specs = {}
+ @duplicates = {}
+ end
+
+ def initialize_copy(o)
+ @sources = o.sources.dup
+ @cache = {}
+ @specs = {}
+ @duplicates = {}
+
+ o.specs.each do |name, hash|
+ @specs[name] = hash.dup
+ end
+ o.duplicates.each do |name, array|
+ @duplicates[name] = array.dup
+ end
+ end
+
+ def inspect
+ "#<#{self.class}:0x#{object_id} sources=#{sources.map(&:inspect)} specs.size=#{specs.size}>"
+ end
+
+ def empty?
+ each { return false }
+ true
+ end
+
+ # Search this index's specs, and any source indexes that this index knows
+ # about, returning all of the results.
+ def search(query)
+ results = local_search(query)
+ return results unless @sources.any?
+
+ @sources.each do |source|
+ results = safe_concat(results, source.search(query))
+ end
+ results.uniq!(&:full_name) unless results.empty? # avoid modifying frozen EMPTY_SEARCH
+ results
+ end
+
+ alias_method :[], :search
+
+ def local_search(query)
+ case query
+ when Gem::Specification, RemoteSpecification, LazySpecification, EndpointSpecification then search_by_spec(query)
+ when String then specs_by_name(query)
+ when Array then specs_by_name_and_version(*query)
+ else
+ raise "You can't search for a #{query.inspect}."
+ end
+ end
+
+ def add(spec)
+ (@specs[spec.name] ||= {}).store(spec.full_name, spec)
+ end
+ alias_method :<<, :add
+
+ def each(&blk)
+ return enum_for(:each) unless blk
+ specs.values.each do |spec_sets|
+ spec_sets.values.each(&blk)
+ end
+ sources.each {|s| s.each(&blk) }
+ self
+ end
+
+ def spec_names
+ names = specs.keys + sources.map(&:spec_names)
+ names.uniq!
+ names
+ end
+
+ def unmet_dependency_names
+ dependency_names.select do |name|
+ search(name).empty?
+ end
+ end
+
+ def dependency_names
+ names = []
+ each do |spec|
+ spec.dependencies.each do |dep|
+ next if dep.type == :development
+ names << dep.name
+ end
+ end
+ names.uniq
+ end
+
+ # Combines indexes proritizing existing specs, like `Hash#reverse_merge!`
+ # Duplicate specs found in `other` are stored in `@duplicates`.
+ def use(other)
+ return unless other
+ other.each do |spec|
+ exist?(spec) ? add_duplicate(spec) : add(spec)
+ end
+ self
+ end
+
+ # Combines indexes proritizing specs from `other`, like `Hash#merge!`
+ # Duplicate specs found in `self` are saved in `@duplicates`.
+ def merge!(other)
+ return unless other
+ other.each do |spec|
+ if existing = find_by_spec(spec)
+ unless dependencies_eql?(existing, spec)
+ Bundler.ui.warn "Local specification for #{spec.full_name} has different dependencies than the remote gem, ignoring it"
+ next
+ end
+
+ add_duplicate(existing)
+ end
+ add spec
+ end
+ self
+ end
+
+ def size
+ @sources.inject(@specs.size) do |size, source|
+ size += source.size
+ end
+ end
+
+ # Whether all the specs in self are in other
+ def subset?(other)
+ all? do |spec|
+ other_spec = other[spec].first
+ other_spec && dependencies_eql?(spec, other_spec) && spec.source == other_spec.source
+ end
+ end
+
+ def dependencies_eql?(spec, other_spec)
+ deps = spec.runtime_dependencies
+ other_deps = other_spec.runtime_dependencies
+ deps.sort == other_deps.sort
+ end
+
+ def add_source(index)
+ raise ArgumentError, "Source must be an index, not #{index.class}" unless index.is_a?(Index)
+ @sources << index
+ @sources.uniq! # need to use uniq! here instead of checking for the item before adding
+ end
+
+ private
+
+ def safe_concat(a, b)
+ return a if b.empty?
+ return b if a.empty?
+ a.concat(b)
+ end
+
+ def add_duplicate(spec)
+ (@duplicates[spec.name] ||= []) << spec
+ end
+
+ def specs_by_name_and_version(name, version)
+ results = @specs[name]&.values
+ return EMPTY_SEARCH unless results
+ results.select! {|spec| spec.version == version }
+ results
+ end
+
+ def specs_by_name(name)
+ @specs[name]&.values || EMPTY_SEARCH
+ end
+
+ EMPTY_SEARCH = [].freeze
+
+ def search_by_spec(spec)
+ spec = find_by_spec(spec)
+ spec ? [spec] : EMPTY_SEARCH
+ end
+
+ def find_by_spec(spec)
+ @specs[spec.name]&.fetch(spec.full_name, nil)
+ end
+
+ def exist?(spec)
+ @specs[spec.name]&.key?(spec.full_name)
+ end
+ end
+end