| Class | Gem::SpecFetcher |
| In: |
lib/rubygems/spec_fetcher.rb
|
| Parent: | Object |
SpecFetcher handles metadata updates from remote gem repositories.
| FILES | = | { :all => 'specs', :latest => 'latest_specs', :prerelease => 'prerelease_specs', } |
# File lib/rubygems/spec_fetcher.rb, line 50
50: def initialize
51: require 'fileutils'
52:
53: @dir = File.join Gem.user_home, '.gem', 'specs'
54: @update_cache = File.stat(Gem.user_home).uid == Process.uid
55:
56: @specs = {}
57: @latest_specs = {}
58: @prerelease_specs = {}
59:
60: @caches = {
61: :latest => @latest_specs,
62: :prerelease => @prerelease_specs,
63: :all => @specs
64: }
65:
66: @fetcher = Gem::RemoteFetcher.fetcher
67: end
Returns the local directory to write uri to.
# File lib/rubygems/spec_fetcher.rb, line 72
72: def cache_dir(uri)
73: # Correct for windows paths
74: escaped_path = uri.path.sub(/^\/([a-z]):\//i, '/\\1-/')
75: File.join @dir, "#{uri.host}%#{uri.port}", File.dirname(escaped_path)
76: end
# File lib/rubygems/spec_fetcher.rb, line 100
100: def fetch(*args)
101: fetch_with_errors(*args).first
102: end
# File lib/rubygems/spec_fetcher.rb, line 104
104: def fetch_spec(spec, source_uri)
105: source_uri = URI.parse source_uri if String === source_uri
106: spec = spec - [nil, 'ruby', '']
107: spec_file_name = "#{spec.join '-'}.gemspec"
108:
109: uri = source_uri + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}"
110:
111: cache_dir = cache_dir uri
112:
113: local_spec = File.join cache_dir, spec_file_name
114:
115: if File.exist? local_spec then
116: spec = Gem.read_binary local_spec
117: else
118: uri.path << '.rz'
119:
120: spec = @fetcher.fetch_path uri
121: spec = Gem.inflate spec
122:
123: if @update_cache then
124: FileUtils.mkdir_p cache_dir
125:
126: open local_spec, 'wb' do |io|
127: io.write spec
128: end
129: end
130: end
131:
132: # TODO: Investigate setting Gem::Specification#loaded_from to a URI
133: Marshal.load spec
134: end
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.
# File lib/rubygems/spec_fetcher.rb, line 84
84: def fetch_with_errors(dependency,
85: all = false,
86: matching_platform = true,
87: prerelease = false)
88: specs_and_sources, errors = find_matching_with_errors(dependency,
89: all,
90: matching_platform,
91: prerelease)
92:
93: ss = specs_and_sources.map do |spec_tuple, source_uri|
94: [fetch_spec(spec_tuple, URI.parse(source_uri)), source_uri]
95: end
96:
97: return [ss, errors]
98: end
# File lib/rubygems/spec_fetcher.rb, line 172
172: def find_matching(*args)
173: find_matching_with_errors(*args).first
174: end
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.
# File lib/rubygems/spec_fetcher.rb, line 141
141: def find_matching_with_errors(dependency, all = false, matching_platform = true, prerelease = false)
142: found = {}
143:
144: rejected_specs = {}
145:
146: list(all, prerelease).each do |source_uri, specs|
147: found[source_uri] = specs.select do |spec_name, version, spec_platform|
148: if dependency.match?(spec_name, version)
149: if matching_platform and !Gem::Platform.match(spec_platform)
150: pm = (rejected_specs[dependency] ||= Gem::PlatformMismatch.new(spec_name, version))
151: pm.add_platform spec_platform
152: false
153: else
154: true
155: end
156: end
157: end
158: end
159:
160: errors = rejected_specs.values
161:
162: specs_and_sources = []
163:
164: found.each do |source_uri, specs|
165: uri_str = source_uri.to_s
166: specs_and_sources.push(*specs.map { |spec| [spec, uri_str] })
167: end
168:
169: [specs_and_sources, errors]
170: end
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.
# File lib/rubygems/spec_fetcher.rb, line 209
209: def list(all = false, prerelease = false)
210: # TODO: make type the only argument
211: type = if all
212: :all
213: elsif prerelease
214: :prerelease
215: else
216: :latest
217: end
218:
219: list = {}
220: file = FILES[type]
221: cache = @caches[type]
222:
223: Gem.sources.each do |source_uri|
224: source_uri = URI.parse source_uri
225:
226: unless cache.include? source_uri
227: cache[source_uri] = load_specs source_uri, file
228: end
229:
230: list[source_uri] = cache[source_uri]
231: end
232:
233: if type == :all
234: list.values.map do |gems|
235: gems.reject! { |g| !g[1] || g[1].prerelease? }
236: end
237: end
238:
239: list
240: end
Loads specs in file, fetching from source_uri if the on-disk cache is out of date.
# File lib/rubygems/spec_fetcher.rb, line 246
246: def load_specs(source_uri, file)
247: file_name = "#{file}.#{Gem.marshal_version}"
248: spec_path = source_uri + "#{file_name}.gz"
249: cache_dir = cache_dir spec_path
250: local_file = File.join(cache_dir, file_name)
251: loaded = false
252:
253: if File.exist? local_file then
254: spec_dump =
255: @fetcher.fetch_path(spec_path, File.mtime(local_file)) rescue nil
256:
257: loaded = true if spec_dump
258:
259: spec_dump ||= Gem.read_binary local_file
260: else
261: spec_dump = @fetcher.fetch_path spec_path
262: loaded = true
263: end
264:
265: specs = begin
266: Marshal.load spec_dump
267: rescue ArgumentError
268: spec_dump = @fetcher.fetch_path spec_path
269: loaded = true
270:
271: Marshal.load spec_dump
272: end
273:
274: if loaded and @update_cache then
275: begin
276: FileUtils.mkdir_p cache_dir
277:
278: open local_file, 'wb' do |io|
279: io << spec_dump
280: end
281: rescue
282: end
283: end
284:
285: specs
286: end
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.
# File lib/rubygems/spec_fetcher.rb, line 182
182: def suggest_gems_from_name gem_name
183: gem_name = gem_name.downcase
184: max = gem_name.size / 2
185: specs = list.values.flatten 1
186:
187: matches = specs.map { |name, version, platform|
188: next unless Gem::Platform.match platform
189:
190: distance = levenshtein_distance gem_name, name.downcase
191:
192: next if distance >= max
193:
194: return [name] if distance == 0
195:
196: [name, distance]
197: }.compact
198:
199: matches = matches.uniq.sort_by { |name, dist| dist }
200:
201: matches.first(5).map { |name, dist| name }
202: end