Class: Gem::SpecFetcher

Inherits:
Object
  • Object
show all
Includes:
Text, UserInteraction
Defined in:
lib/rubygems/spec_fetcher.rb

Overview

SpecFetcher handles metadata updates from remote gem repositories.

Constant Summary collapse

FILES =
{
  :all        => 'specs',
  :latest     => 'latest_specs',
  :prerelease => 'prerelease_specs',
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Text

#format_text, #levenshtein_distance

Methods included from UserInteraction

#alert, #alert_error, #alert_warning, #ask, #ask_for_password, #ask_yes_no, #choose_from_list, #say, #terminate_interaction

Methods included from DefaultUserInteraction

ui, #ui, ui=, #ui=, use_ui, #use_ui

Constructor Details

#initializeSpecFetcher

Returns a new instance of SpecFetcher.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/rubygems/spec_fetcher.rb', line 50

def initialize
  require 'fileutils'

  @dir = File.join Gem.user_home, '.gem', 'specs'
  @update_cache = File.stat(Gem.user_home).uid == Process.uid

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

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

  @fetcher = Gem::RemoteFetcher.fetcher
end

Instance Attribute Details

#dirObject (readonly)

The SpecFetcher cache dir.



23
24
25
# File 'lib/rubygems/spec_fetcher.rb', line 23

def dir
  @dir
end

#latest_specsObject (readonly)

Cache of latest specs



28
29
30
# File 'lib/rubygems/spec_fetcher.rb', line 28

def latest_specs
  @latest_specs
end

#prerelease_specsObject (readonly)

Cache of prerelease specs



38
39
40
# File 'lib/rubygems/spec_fetcher.rb', line 38

def prerelease_specs
  @prerelease_specs
end

#specsObject (readonly)

Cache of all released specs



33
34
35
# File 'lib/rubygems/spec_fetcher.rb', line 33

def specs
  @specs
end

Class Method Details

.fetcherObject



42
43
44
# File 'lib/rubygems/spec_fetcher.rb', line 42

def self.fetcher
  @fetcher ||= new
end

.fetcher=(fetcher) ⇒ Object

:nodoc:



46
47
48
# File 'lib/rubygems/spec_fetcher.rb', line 46

def self.fetcher=(fetcher) # :nodoc:
  @fetcher = fetcher
end

Instance Method Details

#cache_dir(uri) ⇒ Object

Returns the local directory to write uri to.



72
73
74
75
76
# File 'lib/rubygems/spec_fetcher.rb', line 72

def cache_dir(uri)
  # Correct for windows paths
  escaped_path = uri.path.sub(/^\/([a-z]):\//i, '/\\1-/')
  File.join @dir, "#{uri.host}%#{uri.port}", File.dirname(escaped_path)
end

#fetch(*args) ⇒ Object



101
102
103
# File 'lib/rubygems/spec_fetcher.rb', line 101

def fetch(*args)
  fetch_with_errors(*args).first
end

#fetch_spec(spec, source_uri) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/rubygems/spec_fetcher.rb', line 105

def fetch_spec(spec, source_uri)
  source_uri = URI.parse source_uri if String === source_uri
  spec = spec - [nil, 'ruby', '']
  spec_file_name = "#{spec.join '-'}.gemspec"

  uri = source_uri + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}"

  cache_dir = cache_dir uri

  local_spec = File.join cache_dir, spec_file_name

  if File.exist? local_spec then
    spec = Gem.read_binary local_spec
  else
    uri.path << '.rz'

    spec = @fetcher.fetch_path uri
    spec = Gem.inflate spec

    if @update_cache then
      FileUtils.mkdir_p cache_dir

      open local_spec, 'wb' do |io|
        io.write spec
      end
    end
  end

  # TODO: Investigate setting Gem::Specification#loaded_from to a URI
  Marshal.load spec
end

#fetch_with_errors(dependency, all = false, matching_platform = true, prerelease = false) ⇒ Object

Fetch specs matching dependency. If all is true, all matching (released) versions are returned. If matching_platform is false, all platforms are returned. If prerelease is true, prerelease versions are included.



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/rubygems/spec_fetcher.rb', line 84

def fetch_with_errors(dependency,
                      all               = false,
                      matching_platform = true,
                      prerelease        = false)

  specs_and_sources, errors = find_matching_with_errors(dependency,
                                                        all,
                                                        matching_platform,
                                                        prerelease)

  ss = specs_and_sources.map do |spec_tuple, source_uri|
    [fetch_spec(spec_tuple, URI.parse(source_uri)), source_uri]
  end

  return [ss, errors]
end

#find_matching(*args) ⇒ Object



176
177
178
# File 'lib/rubygems/spec_fetcher.rb', line 176

def find_matching(*args)
  find_matching_with_errors(*args).first
end

#find_matching_with_errors(dependency, all = false, matching_platform = true, prerelease = false) ⇒ Object

Find spec names that match dependency. If all is true, all matching released versions are returned. If matching_platform is false, gems for all platforms are returned.



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/rubygems/spec_fetcher.rb', line 142

def find_matching_with_errors(dependency,
                              all               = false,
                              matching_platform = true,
                              prerelease        = false)
  found = {}

  rejected_specs = {}

  list(all, prerelease).each do |source_uri, specs|
    found[source_uri] = specs.select do |spec_name, version, spec_platform|
      if dependency.match?(spec_name, version)
        if matching_platform and !Gem::Platform.match(spec_platform)
          pm = (rejected_specs[dependency] ||= Gem::PlatformMismatch.new(spec_name, version))
          pm.add_platform spec_platform
          false
        else
          true
        end
      end
    end
  end

  errors = rejected_specs.values

  specs_and_sources = []

  found.each do |source_uri, specs|
    uri_str = source_uri.to_s
    specs_and_sources.concat(specs.map { |spec| [spec, uri_str] })
  end

  [specs_and_sources, errors]
end

#list(all = false, prerelease = false) ⇒ Object

Returns a list of gems available for each source in Gem::sources. If all is true, all released versions are returned instead of only latest versions. If prerelease is true, include prerelease versions.



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/rubygems/spec_fetcher.rb', line 213

def list(all = false, prerelease = false)
  # TODO: make type the only argument
  type = if all
           :all
         elsif prerelease
           :prerelease
         else
           :latest
         end

  list  = {}
  file  = FILES[type]
  cache = @caches[type]

  Gem.sources.each do |source_uri|
    source_uri = URI.parse source_uri

    unless cache.include? source_uri
      cache[source_uri] = load_specs source_uri, file
    end

    list[source_uri] = cache[source_uri]
  end

  if type == :all
    list.values.map do |gems|
      gems.reject! { |g| !g[1] || g[1].prerelease? }
    end
  end

  list
end

#load_specs(source_uri, file) ⇒ Object

Loads specs in file, fetching from source_uri if the on-disk cache is out of date.



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/rubygems/spec_fetcher.rb', line 250

def load_specs(source_uri, file)
  file_name  = "#{file}.#{Gem.marshal_version}"
  spec_path  = source_uri + "#{file_name}.gz"
  cache_dir  = cache_dir spec_path
  local_file = File.join(cache_dir, file_name)
  loaded     = false

  if File.exist? local_file then
    begin
      spec_dump =
        @fetcher.fetch_path(spec_path, File.mtime(local_file))
    rescue Gem::RemoteFetcher::FetchError => e
      alert_warning "Error fetching data: #{e.message}"
    end

    loaded = true if spec_dump

    spec_dump ||= Gem.read_binary local_file
  else
    spec_dump = @fetcher.fetch_path spec_path
    loaded = true
  end

  specs = begin
            Marshal.load spec_dump
          rescue ArgumentError
            spec_dump = @fetcher.fetch_path spec_path
            loaded = true

            Marshal.load spec_dump
          end

  if loaded and @update_cache then
    begin
      FileUtils.mkdir_p cache_dir

      open local_file, 'wb' do |io|
        io << spec_dump
      end
    rescue
    end
  end

  specs
end

#suggest_gems_from_name(gem_name) ⇒ Object

Suggests a gem based on the supplied gem_name. Returns a string of the gem name if an approximate match can be found or nil otherwise. NOTE: for performance reasons only gems which exactly match the first character of gem_name are considered.



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/rubygems/spec_fetcher.rb', line 186

def suggest_gems_from_name gem_name
  gem_name        = gem_name.downcase
  max             = gem_name.size / 2
  specs           = list.values.flatten 1

  matches = specs.map { |name, version, platform|
    next unless Gem::Platform.match platform

    distance = levenshtein_distance gem_name, name.downcase

    next if distance >= max

    return [name] if distance == 0

    [name, distance]
  }.compact

  matches = matches.uniq.sort_by { |name, dist| dist }

  matches.first(5).map { |name, dist| name }
end