| 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 101
101: def fetch(*args)
102: fetch_with_errors(*args).first
103: end
# File lib/rubygems/spec_fetcher.rb, line 105
105: def fetch_spec(spec, source_uri)
106: source_uri = URI.parse source_uri if String === source_uri
107: spec = spec - [nil, 'ruby', '']
108: spec_file_name = "#{spec.join '-'}.gemspec"
109:
110: uri = source_uri + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}"
111:
112: cache_dir = cache_dir uri
113:
114: local_spec = File.join cache_dir, spec_file_name
115:
116: if File.exist? local_spec then
117: spec = Gem.read_binary local_spec
118: else
119: uri.path << '.rz'
120:
121: spec = @fetcher.fetch_path uri
122: spec = Gem.inflate spec
123:
124: if @update_cache then
125: FileUtils.mkdir_p cache_dir
126:
127: open local_spec, 'wb' do |io|
128: io.write spec
129: end
130: end
131: end
132:
133: # TODO: Investigate setting Gem::Specification#loaded_from to a URI
134: Marshal.load spec
135: 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:
89: specs_and_sources, errors = find_matching_with_errors(dependency,
90: all,
91: matching_platform,
92: prerelease)
93:
94: ss = specs_and_sources.map do |spec_tuple, source_uri|
95: [fetch_spec(spec_tuple, URI.parse(source_uri)), source_uri]
96: end
97:
98: return [ss, errors]
99: end
# File lib/rubygems/spec_fetcher.rb, line 176
176: def find_matching(*args)
177: find_matching_with_errors(*args).first
178: 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 142
142: def find_matching_with_errors(dependency,
143: all = false,
144: matching_platform = true,
145: prerelease = false)
146: found = {}
147:
148: rejected_specs = {}
149:
150: list(all, prerelease).each do |source_uri, specs|
151: found[source_uri] = specs.select do |spec_name, version, spec_platform|
152: if dependency.match?(spec_name, version)
153: if matching_platform and !Gem::Platform.match(spec_platform)
154: pm = (rejected_specs[dependency] ||= Gem::PlatformMismatch.new(spec_name, version))
155: pm.add_platform spec_platform
156: false
157: else
158: true
159: end
160: end
161: end
162: end
163:
164: errors = rejected_specs.values
165:
166: specs_and_sources = []
167:
168: found.each do |source_uri, specs|
169: uri_str = source_uri.to_s
170: specs_and_sources.concat(specs.map { |spec| [spec, uri_str] })
171: end
172:
173: [specs_and_sources, errors]
174: 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 213
213: def list(all = false, prerelease = false)
214: # TODO: make type the only argument
215: type = if all
216: :all
217: elsif prerelease
218: :prerelease
219: else
220: :latest
221: end
222:
223: list = {}
224: file = FILES[type]
225: cache = @caches[type]
226:
227: Gem.sources.each do |source_uri|
228: source_uri = URI.parse source_uri
229:
230: unless cache.include? source_uri
231: cache[source_uri] = load_specs source_uri, file
232: end
233:
234: list[source_uri] = cache[source_uri]
235: end
236:
237: if type == :all
238: list.values.map do |gems|
239: gems.reject! { |g| !g[1] || g[1].prerelease? }
240: end
241: end
242:
243: list
244: 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 250
250: def load_specs(source_uri, file)
251: file_name = "#{file}.#{Gem.marshal_version}"
252: spec_path = source_uri + "#{file_name}.gz"
253: cache_dir = cache_dir spec_path
254: local_file = File.join(cache_dir, file_name)
255: loaded = false
256:
257: if File.exist? local_file then
258: spec_dump =
259: @fetcher.fetch_path(spec_path, File.mtime(local_file)) rescue nil
260:
261: loaded = true if spec_dump
262:
263: spec_dump ||= Gem.read_binary local_file
264: else
265: spec_dump = @fetcher.fetch_path spec_path
266: loaded = true
267: end
268:
269: specs = begin
270: Marshal.load spec_dump
271: rescue ArgumentError
272: spec_dump = @fetcher.fetch_path spec_path
273: loaded = true
274:
275: Marshal.load spec_dump
276: end
277:
278: if loaded and @update_cache then
279: begin
280: FileUtils.mkdir_p cache_dir
281:
282: open local_file, 'wb' do |io|
283: io << spec_dump
284: end
285: rescue
286: end
287: end
288:
289: specs
290: 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 186
186: def suggest_gems_from_name gem_name
187: gem_name = gem_name.downcase
188: max = gem_name.size / 2
189: specs = list.values.flatten 1
190:
191: matches = specs.map { |name, version, platform|
192: next unless Gem::Platform.match platform
193:
194: distance = levenshtein_distance gem_name, name.downcase
195:
196: next if distance >= max
197:
198: return [name] if distance == 0
199:
200: [name, distance]
201: }.compact
202:
203: matches = matches.uniq.sort_by { |name, dist| dist }
204:
205: matches.first(5).map { |name, dist| name }
206: end