diff options
Diffstat (limited to 'ruby_1_9_3/lib/rdoc/ri/store.rb')
-rw-r--r-- | ruby_1_9_3/lib/rdoc/ri/store.rb | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/ruby_1_9_3/lib/rdoc/ri/store.rb b/ruby_1_9_3/lib/rdoc/ri/store.rb new file mode 100644 index 0000000000..fe4ccc244d --- /dev/null +++ b/ruby_1_9_3/lib/rdoc/ri/store.rb @@ -0,0 +1,358 @@ +require 'rdoc/code_objects' +require 'fileutils' + +## +# 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 + +class RDoc::RI::Store + + ## + # If true this Store will not write any files + + attr_accessor :dry_run + + ## + # Path this store reads or writes + + attr_accessor :path + + ## + # Type of ri datastore this was loaded from. See RDoc::RI::Driver, + # RDoc::RI::Paths. + + attr_accessor :type + + ## + # The contents of the Store + + attr_reader :cache + + ## + # The encoding of the contents in the Store + + attr_accessor :encoding + + ## + # Creates a new Store of +type+ that will load or save to +path+ + + 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 + + ## + # 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). + + def ancestors + @cache[:ancestors] + end + + ## + # Attributes cache accessor. Maps a class to an Array of its attributes. + + def attributes + @cache[:attributes] + end + + ## + # Path to the cache file + + def cache_path + File.join @path, 'cache.ri' + end + + ## + # Path to the ri data for +klass_name+ + + def class_file klass_name + name = klass_name.split('::').last + File.join class_path(klass_name), "cdesc-#{name}.ri" + end + + ## + # Class methods cache accessor. Maps a class to an Array of its class + # methods (not full name). + + def class_methods + @cache[:class_methods] + end + + ## + # Path where data for +klass_name+ will be stored (methods or class data) + + def class_path klass_name + File.join @path, *klass_name.split('::') + end + + ## + # Removes empty items and ensures item in each collection are unique and + # sorted + + 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 rendition of #path + + 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 + + def inspect # :nodoc: + "#<%s:0x%x %s %p>" % [self.class, object_id, @path, modules.sort] + end + + ## + # Instance methods cache accessor. Maps a class to an Array of its + # instance methods (not full name). + + def instance_methods + @cache[:instance_methods] + end + + ## + # Loads cache file for this store + + 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 + + ## + # Loads ri data for +klass_name+ + + def load_class klass_name + open class_file(klass_name), 'rb' do |io| + Marshal.load io.read + end + end + + ## + # Loads ri data for +method_name+ in +klass_name+ + + def load_method klass_name, method_name + open method_file(klass_name, method_name), 'rb' do |io| + Marshal.load io.read + end + end + + ## + # Path to the ri data for +method_name+ in +klass_name+ + + 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 + + ## + # Modules cache accessor. An Array of all the modules (and classes) in the + # store. + + def modules + @cache[:modules] + end + + ## + # Writes the cache file for this store + + 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 + + ## + # Writes the ri data for +klass+ + + 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 + + ## + # Writes the ri data for +method+ on +klass+ + + 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 + +end + |