Class: RDoc::RI::Store

Inherits:
Object
  • Object
show all
Defined in:
lib/rdoc/ri/store.rb

Overview

A set of ri data.

The store manages reading and writing ri data for a project (gem, path, etc.) and maintains a cache of methods, classes and ancestors in the store.

The store maintains a #cache of its contents for faster lookup. After adding items to the store it must be flushed using #save_cache. The cache contains the following structures:

@cache = {
  :class_methods    => {}, # class name => class methods
  :instance_methods => {}, # class name => instance methods
  :attributes       => {}, # class name => attributes
  :modules          => [], # classes and modules in this store
  :ancestors        => {}, # class name => ancestor names
}

-- TODO need to store the list of files and prune classes

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, type = nil) ⇒ Store

Creates a new Store of type that will load or save to path



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/rdoc/ri/store.rb', line 56

def initialize path, type = nil
  @dry_run  = false
  @type     = type
  @path     = path
  @encoding = nil

  @cache = {
    :ancestors        => {},
    :attributes       => {},
    :class_methods    => {},
    :encoding         => @encoding,
    :instance_methods => {},
    :modules          => [],
  }
end

Instance Attribute Details

#cacheObject (readonly)

The contents of the Store



46
47
48
# File 'lib/rdoc/ri/store.rb', line 46

def cache
  @cache
end

#dry_runObject

If true this Store will not write any files



30
31
32
# File 'lib/rdoc/ri/store.rb', line 30

def dry_run
  @dry_run
end

#encodingObject

The encoding of the contents in the Store



51
52
53
# File 'lib/rdoc/ri/store.rb', line 51

def encoding
  @encoding
end

#pathObject

Path this store reads or writes



35
36
37
# File 'lib/rdoc/ri/store.rb', line 35

def path
  @path
end

#typeObject

Type of ri datastore this was loaded from. See RDoc::RI::Driver, RDoc::RI::Paths.



41
42
43
# File 'lib/rdoc/ri/store.rb', line 41

def type
  @type
end

Instance Method Details

#ancestorsObject

Ancestors cache accessor. Maps a klass name to an Array of its ancestors in this store. If Foo in this store inherits from Object, Kernel won't be listed (it will be included from ruby's ri store).



77
78
79
# File 'lib/rdoc/ri/store.rb', line 77

def ancestors
  @cache[:ancestors]
end

#attributesObject

Attributes cache accessor. Maps a class to an Array of its attributes.



84
85
86
# File 'lib/rdoc/ri/store.rb', line 84

def attributes
  @cache[:attributes]
end

#cache_pathObject

Path to the cache file



91
92
93
# File 'lib/rdoc/ri/store.rb', line 91

def cache_path
  File.join @path, 'cache.ri'
end

#class_file(klass_name) ⇒ Object

Path to the ri data for klass_name



98
99
100
101
# File 'lib/rdoc/ri/store.rb', line 98

def class_file klass_name
  name = klass_name.split('::').last
  File.join class_path(klass_name), "cdesc-#{name}.ri"
end

#class_methodsObject

Class methods cache accessor. Maps a class to an Array of its class methods (not full name).



107
108
109
# File 'lib/rdoc/ri/store.rb', line 107

def class_methods
  @cache[:class_methods]
end

#class_path(klass_name) ⇒ Object

Path where data for klass_name will be stored (methods or class data)



114
115
116
# File 'lib/rdoc/ri/store.rb', line 114

def class_path klass_name
  File.join @path, *klass_name.split('::')
end

#clean_cache_collection(collection) ⇒ Object

Removes empty items and ensures item in each collection are unique and sorted



122
123
124
125
126
127
128
129
130
131
132
# File 'lib/rdoc/ri/store.rb', line 122

def clean_cache_collection collection # :nodoc:
  collection.each do |name, item|
    if item.empty? then
      collection.delete name
    else
      # HACK mongrel-1.1.5 documents its files twice
      item.uniq!
      item.sort!
    end
  end
end

#friendly_pathObject

Friendly rendition of #path



137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/rdoc/ri/store.rb', line 137

def friendly_path
  case type
  when :gem    then
    sep = Regexp.union(*['/', File::ALT_SEPARATOR].compact)
    @path =~ /#{sep}doc#{sep}(.*?)#{sep}ri$/
    "gem #{$1}"
  when :home   then '~/.ri'
  when :site   then 'ruby site'
  when :system then 'ruby core'
  else @path
  end
end

#inspectObject

:nodoc:



150
151
152
# File 'lib/rdoc/ri/store.rb', line 150

def inspect # :nodoc:
  "#<%s:0x%x %s %p>" % [self.class, object_id, @path, modules.sort]
end

#instance_methodsObject

Instance methods cache accessor. Maps a class to an Array of its instance methods (not full name).



158
159
160
# File 'lib/rdoc/ri/store.rb', line 158

def instance_methods
  @cache[:instance_methods]
end

#load_cacheObject

Loads cache file for this store



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/rdoc/ri/store.rb', line 165

def load_cache
  #orig_enc = @encoding

  open cache_path, 'rb' do |io|
    @cache = Marshal.load io.read
  end

  load_enc = @cache[:encoding]

  # TODO this feature will be time-consuming to add:
  # a) Encodings may be incompatible but transcodeable
  # b) Need to warn in the appropriate spots, wherever they may be
  # c) Need to handle cross-cache differences in encodings
  # d) Need to warn when generating into a cache with diffent encodings
  #
  #if orig_enc and load_enc != orig_enc then
  #  warn "Cached encoding #{load_enc} is incompatible with #{orig_enc}\n" \
  #       "from #{path}/cache.ri" unless
  #    Encoding.compatible? orig_enc, load_enc
  #end

  @encoding = load_enc unless @encoding

  @cache
rescue Errno::ENOENT
end

#load_class(klass_name) ⇒ Object

Loads ri data for klass_name



195
196
197
198
199
# File 'lib/rdoc/ri/store.rb', line 195

def load_class klass_name
  open class_file(klass_name), 'rb' do |io|
    Marshal.load io.read
  end
end

#load_method(klass_name, method_name) ⇒ Object

Loads ri data for method_name in klass_name



204
205
206
207
208
# File 'lib/rdoc/ri/store.rb', line 204

def load_method klass_name, method_name
  open method_file(klass_name, method_name), 'rb' do |io|
    Marshal.load io.read
  end
end

#method_file(klass_name, method_name) ⇒ Object

Path to the ri data for method_name in klass_name



213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/rdoc/ri/store.rb', line 213

def method_file klass_name, method_name
  method_name = method_name.split('::').last
  method_name =~ /#(.*)/
  method_type = $1 ? 'i' : 'c'
  method_name = $1 if $1

  method_name = if ''.respond_to? :ord then
                  method_name.gsub(/\W/) { "%%%02x" % $&[0].ord }
                else
                  method_name.gsub(/\W/) { "%%%02x" % $&[0] }
                end

  File.join class_path(klass_name), "#{method_name}-#{method_type}.ri"
end

#modulesObject

Modules cache accessor. An Array of all the modules (and classes) in the store.



232
233
234
# File 'lib/rdoc/ri/store.rb', line 232

def modules
  @cache[:modules]
end

#save_cacheObject

Writes the cache file for this store



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/rdoc/ri/store.rb', line 239

def save_cache
  clean_cache_collection @cache[:ancestors]
  clean_cache_collection @cache[:attributes]
  clean_cache_collection @cache[:class_methods]
  clean_cache_collection @cache[:instance_methods]

  @cache[:modules].uniq!
  @cache[:modules].sort!
  @cache[:encoding] = @encoding # this gets set twice due to assert_cache

  return if @dry_run

  marshal = Marshal.dump @cache

  open cache_path, 'wb' do |io|
    io.write marshal
  end
end

#save_class(klass) ⇒ Object

Writes the ri data for klass



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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/rdoc/ri/store.rb', line 261

def save_class klass
  full_name = klass.full_name

  FileUtils.mkdir_p class_path(full_name) unless @dry_run

  @cache[:modules] << full_name

  path = class_file full_name

  begin
    disk_klass = load_class full_name

    klass = disk_klass.merge klass
  rescue Errno::ENOENT
  end

  # BasicObject has no ancestors
  ancestors = klass.ancestors.compact.map do |ancestor|
    # HACK for classes we don't know about (class X < RuntimeError)
    String === ancestor ? ancestor : ancestor.full_name
  end

  @cache[:ancestors][full_name] ||= []
  @cache[:ancestors][full_name].push(*ancestors)

  attributes = klass.attributes.map do |attribute|
    "#{attribute.definition} #{attribute.name}"
  end

  unless attributes.empty? then
    @cache[:attributes][full_name] ||= []
    @cache[:attributes][full_name].push(*attributes)
  end

  to_delete = []

  unless klass.method_list.empty? then
    @cache[:class_methods][full_name]    ||= []
    @cache[:instance_methods][full_name] ||= []

    class_methods, instance_methods =
      klass.method_list.partition { |meth| meth.singleton }

    class_methods    = class_methods.   map { |method| method.name }
    instance_methods = instance_methods.map { |method| method.name }

    old = @cache[:class_methods][full_name] - class_methods
    to_delete.concat old.map { |method|
      method_file full_name, "#{full_name}::#{method}"
    }

    old = @cache[:instance_methods][full_name] - instance_methods
    to_delete.concat old.map { |method|
      method_file full_name, "#{full_name}##{method}"
    }

    @cache[:class_methods][full_name]    = class_methods
    @cache[:instance_methods][full_name] = instance_methods
  end

  return if @dry_run

  FileUtils.rm_f to_delete

  marshal = Marshal.dump klass

  open path, 'wb' do |io|
    io.write marshal
  end
end

#save_method(klass, method) ⇒ Object

Writes the ri data for method on klass



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/rdoc/ri/store.rb', line 335

def save_method klass, method
  full_name = klass.full_name

  FileUtils.mkdir_p class_path(full_name) unless @dry_run

  cache = if method.singleton then
            @cache[:class_methods]
          else
            @cache[:instance_methods]
          end
  cache[full_name] ||= []
  cache[full_name] << method.name

  return if @dry_run

  marshal = Marshal.dump method

  open method_file(full_name, method.full_name), 'wb' do |io|
    io.write marshal
  end
end