class Gem::SpecFetcher

SpecFetcher handles metadata updates from remote gem repositories.

Public Class Methods

fetcher() click to toggle source

Default fetcher instance. Use this instead of ::new to reduce object allocation.

# File rubygems/spec_fetcher.rb, line 42
def self.fetcher
  @fetcher ||= new
end
new(sources = nil) click to toggle source

Creates a new SpecFetcher. Ordinarily you want to use the default fetcher from Gem::SpecFetcher::fetcher which uses the Gem.sources.

If you need to retrieve specifications from a different source, you can send it as an argument.

# File rubygems/spec_fetcher.rb, line 57
def initialize(sources = nil)
  @sources = sources || Gem.sources

  @update_cache =
    begin
      File.stat(Gem.user_home).uid == Process.uid
    rescue Errno::EACCES, Errno::ENOENT
      false
    end

  @specs = {}
  @latest_specs = {}
  @prerelease_specs = {}

  @caches = {
    latest: @latest_specs,
    prerelease: @prerelease_specs,
    released: @specs,
  }

  @fetcher = Gem::RemoteFetcher.fetcher
end

Public Instance Methods

available_specs(type) click to toggle source

Returns a list of gems available for each source in Gem::sources.

type can be one of 3 values: :released => Return the list of all released specs :complete => Return the list of all specs :latest => Return the list of only the highest version of each gem :prerelease => Return the list of all prerelease only specs

# File rubygems/spec_fetcher.rb, line 243
def available_specs(type)
  errors = []
  list = {}

  @sources.each_source do |source|
    names = case type
            when :latest
              tuples_for source, :latest
            when :released
              tuples_for source, :released
            when :complete
              names =
                tuples_for(source, :prerelease, true) +
                tuples_for(source, :released)

              names.sort
            when :abs_latest
              names =
                tuples_for(source, :prerelease, true) +
                tuples_for(source, :latest)

              names.sort
            when :prerelease
              tuples_for(source, :prerelease)
            else
              raise Gem::Exception, "Unknown type - :#{type}"
    end
  rescue Gem::RemoteFetcher::FetchError => e
    errors << Gem::SourceFetchProblem.new(source, e)
  else
    list[source] = names
  end

  [list, errors]
end
detect(type=:complete) { |tup| ... } click to toggle source

Return all gem name tuples who’s names match obj

# File rubygems/spec_fetcher.rb, line 133
def detect(type=:complete)
  tuples = []

  list, _ = available_specs(type)
  list.each do |source, specs|
    specs.each do |tup|
      if yield(tup)
        tuples << [tup, source]
      end
    end
  end

  tuples
end
search_for_dependency(dependency, matching_platform=true) click to toggle source

Find and fetch gem name tuples that match dependency.

If matching_platform is false, gems for all platforms are returned.

# File rubygems/spec_fetcher.rb, line 86
def search_for_dependency(dependency, matching_platform=true)
  found = {}

  rejected_specs = {}

  list, errors = available_specs(dependency.identity)

  list.each do |source, specs|
    if dependency.name.is_a?(String) && specs.respond_to?(:bsearch)
      start_index = (0...specs.length).bsearch {|i| specs[i].name >= dependency.name }
      end_index   = (0...specs.length).bsearch {|i| specs[i].name > dependency.name }
      specs = specs[start_index...end_index] if start_index && end_index
    end

    found[source] = specs.select do |tup|
      if dependency.match?(tup)
        if matching_platform && !Gem::Platform.match_gem?(tup.platform, tup.name)
          pm = (
            rejected_specs[dependency] ||= \
              Gem::PlatformMismatch.new(tup.name, tup.version))
          pm.add_platform tup.platform
          false
        else
          true
        end
      end
    end
  end

  errors += rejected_specs.values

  tuples = []

  found.each do |source, specs|
    specs.each do |s|
      tuples << [s, source]
    end
  end

  tuples = tuples.sort_by {|x| x[0].version }

  [tuples, errors]
end
spec_for_dependency(dependency, matching_platform=true) click to toggle source

Find and fetch specs that match dependency.

If matching_platform is false, gems for all platforms are returned.

# File rubygems/spec_fetcher.rb, line 153
def spec_for_dependency(dependency, matching_platform=true)
  tuples, errors = search_for_dependency(dependency, matching_platform)

  specs = []
  tuples.each do |tup, source|
    spec = source.fetch_spec(tup)
  rescue Gem::RemoteFetcher::FetchError => e
    errors << Gem::SourceFetchProblem.new(source, e)
  else
    specs << [spec, source]
  end

  [specs, errors]
end
suggest_gems_from_name(gem_name, type = :latest, num_results = 5) click to toggle source

Suggests gems based on the supplied gem_name. Returns an array of alternative gem names.

# File rubygems/spec_fetcher.rb, line 172
def suggest_gems_from_name(gem_name, type = :latest, num_results = 5)
  gem_name = gem_name.downcase.tr("_-", "")

  # All results for 3-character-or-shorter (minus hyphens/underscores) gem
  # names get rejected, so we just return an empty array immediately instead.
  return [] if gem_name.length <= 3

  max   = gem_name.size / 2
  names = available_specs(type).first.values.flatten(1)

  min_length = gem_name.length - max
  max_length = gem_name.length + max

  gem_name_with_postfix = "#{gem_name}ruby"
  gem_name_with_prefix = "ruby#{gem_name}"

  matches = names.filter_map do |n|
    len = n.name.length
    # If the gem doesn't support the current platform, bail early.
    next unless n.match_platform?

    # If the length is min_length or shorter, we've done `max` deletions.
    # This would be rejected later, so we skip it for performance.
    next if len <= min_length

    # The candidate name, normalized the same as gem_name.
    normalized_name = n.name.downcase
    normalized_name.tr!("_-", "")

    # If the gem is "{NAME}-ruby" and "ruby-{NAME}", we want to return it.
    # But we already removed hyphens, so we check "{NAME}ruby" and "ruby{NAME}".
    next [n.name, 0] if normalized_name == gem_name_with_postfix
    next [n.name, 0] if normalized_name == gem_name_with_prefix

    # If the length is max_length or longer, we've done `max` insertions.
    # This would be rejected later, so we skip it for performance.
    next if len >= max_length

    # If we found an exact match (after stripping underscores and hyphens),
    # that's our most likely candidate.
    # Return it immediately, and skip the rest of the loop.
    return [n.name] if normalized_name == gem_name

    distance = levenshtein_distance gem_name, normalized_name

    # Skip current candidate, if the edit distance is greater than allowed.
    next if distance >= max

    # If all else fails, return the name and the calculated distance.
    [n.name, distance]
  end

  matches = if matches.empty? && type != :prerelease
    suggest_gems_from_name gem_name, :prerelease
  else
    matches.uniq.sort_by {|_name, dist| dist }
  end

  matches.map {|name, _dist| name }.uniq.first(num_results)
end