# class RDoc::Store

A set of rdoc data for a single project (gem, path, etc.).

The store manages reading and writing ri data for a project 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 = {
      :ancestors        => {}, # class name => ancestor names
      :attributes       => {}, # class name => attributes
      :class_methods    => {}, # class name => class methods
      :instance_methods => {}, # class name => instance methods
      :modules          => [], # classes and modules in this store
      :pages            => [], # page names
    }

### Constants

MarshalFilter

### Attributes

c_class_variables [R]

Maps C variables to class or module names for each parsed C file.

c_singleton_class_variables [R]

Maps C variables to singleton class names for each parsed C file.

cache [R]

The contents of the `Store`

dry_run [RW]

If true this `Store` will not write any files

encoding [RW]

The encoding of the contents in the `Store`

options [R]
path [RW]

Path this store reads or writes

type [RW]

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

unmatched_constant_alias [R]

The lazy constants alias will be discovered in passing

### Public Class Methods

new (options, path: nil, type: nil)
Source

    # File lib/rdoc/store.rb, line 123
    def initialize(options, path: nil, type: nil)
      @options  = options
      @dry_run  = options.dry_run
      @encoding = options.encoding
      @path     = path || options.op_dir
      @type     = type

      @cache = {
        :ancestors                   => {},
        :attributes                  => {},
        :class_methods               => {},
        :c_class_variables           => {},
        :c_singleton_class_variables => {},
        :encoding                    => @encoding,
        :instance_methods            => {},
        :main                        => options.main_page,
        :modules                     => [],
        :pages                       => [],
        :title                       => options.title,
      }

      @classes_hash = {}
      @modules_hash = {}
      @files_hash   = {}
      @text_files_hash = {}

      @c_enclosure_classes = {}
      @c_enclosure_names   = {}

      @c_class_variables           = {}
      @c_singleton_class_variables = {}

      @unique_classes = nil
      @unique_modules = nil

      @unmatched_constant_alias = {}
    end

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

### Public Instance Methods

add_c_enclosure (variable, namespace)
Source

    # File lib/rdoc/store.rb, line 165
    def add_c_enclosure(variable, namespace)
      @c_enclosure_classes[variable] = namespace
    end

Adds `module` as an enclosure (namespace) for the given `variable` for C files.

add_c_variables (c_parser)
Source

    # File lib/rdoc/store.rb, line 172
    def add_c_variables(c_parser)
      filename = c_parser.top_level.relative_name

      @c_class_variables[filename] = make_variable_map c_parser.classes

      @c_singleton_class_variables[filename] = c_parser.singleton_classes
    end

Adds C variables from an `RDoc::Parser::C`

add_file (absolute_name, relative_name: absolute_name, parser: nil)
Source

    # File lib/rdoc/store.rb, line 184
    def add_file(absolute_name, relative_name: absolute_name, parser: nil)
      unless top_level = @files_hash[relative_name] then
        top_level = RDoc::TopLevel.new absolute_name, relative_name
        top_level.parser = parser if parser
        top_level.store = self
        @files_hash[relative_name] = top_level
        @text_files_hash[relative_name] = top_level if top_level.text?
      end

      top_level
    end

Adds the file with `name` as an RDoc::TopLevel to the store. Returns the created RDoc::TopLevel.

all_classes ()
Source

    # File lib/rdoc/store.rb, line 220
    def all_classes
      @classes_hash.values
    end

Returns all classes discovered by RDoc

all_classes_and_modules ()
Source

    # File lib/rdoc/store.rb, line 227
    def all_classes_and_modules
      @classes_hash.values + @modules_hash.values
    end

Returns all classes and modules discovered by RDoc

all_files ()
Source

    # File lib/rdoc/store.rb, line 234
    def all_files
      @files_hash.values
    end

All TopLevels known to RDoc

all_modules ()
Source

    # File lib/rdoc/store.rb, line 241
    def all_modules
      modules_hash.values
    end

Returns all modules discovered by RDoc

ancestors ()
Source

    # File lib/rdoc/store.rb, line 250
    def ancestors
      @cache[:ancestors]
    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).

attributes ()
Source

    # File lib/rdoc/store.rb, line 257
    def attributes
      @cache[:attributes]
    end

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

cache_path ()
Source

    # File lib/rdoc/store.rb, line 264
    def cache_path
      File.join @path, 'cache.ri'
    end

Path to the cache file

class_file (klass_name)
Source

    # File lib/rdoc/store.rb, line 271
    def class_file(klass_name)
      name = klass_name.split('::').last
      File.join class_path(klass_name), "cdesc-#{name}.ri"
    end

Path to the ri data for `klass_name`

class_methods ()
Source

    # File lib/rdoc/store.rb, line 280
    def class_methods
      @cache[:class_methods]
    end

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

class_path (klass_name)
Source

    # File lib/rdoc/store.rb, line 287
    def class_path(klass_name)
      File.join @path, *klass_name.split('::')
    end

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

classes_hash ()
Source

    # File lib/rdoc/store.rb, line 294
    def classes_hash
      @classes_hash
    end

`Hash` of all classes known to RDoc

complete (min_visibility)
Source

    # File lib/rdoc/store.rb, line 330
    def complete(min_visibility)
      fix_basic_object_inheritance

      # cache included modules before they are removed from the documentation
      all_classes_and_modules.each { |cm| cm.ancestors }

      unless min_visibility == :nodoc then
        remove_nodoc @classes_hash
        remove_nodoc @modules_hash
      end

      @unique_classes = find_unique @classes_hash
      @unique_modules = find_unique @modules_hash

      unique_classes_and_modules.each do |cm|
        cm.complete min_visibility
      end

      @files_hash.each_key do |file_name|
        tl = @files_hash[file_name]

        unless tl.text? then
          tl.modules_hash.clear
          tl.classes_hash.clear

          tl.classes_or_modules.each do |cm|
            name = cm.full_name
            if cm.type == 'class' then
              tl.classes_hash[name] = cm if @classes_hash[name]
            else
              tl.modules_hash[name] = cm if @modules_hash[name]
            end
          end
        end
      end
    end

Prepares the RDoc code object tree for use by a generator.

It finds unique classes/modules defined, and replaces classes/modules that are aliases for another one by a copy with RDoc::ClassModule#is_alias_for set.

It updates the RDoc::ClassModule#constant_aliases attribute of “real” classes or modules.

It also completely removes the classes and modules that should be removed from the documentation and the methods that have a visibility below `min_visibility`, which is the `--visibility` option.

See also RDoc::Context#remove_from_documentation?

files_hash ()
Source

    # File lib/rdoc/store.rb, line 370
    def files_hash
      @files_hash
    end

`Hash` of all files known to RDoc

find_c_enclosure (variable)
Source

    # File lib/rdoc/store.rb, line 377
    def find_c_enclosure(variable)
      @c_enclosure_classes.fetch variable do
        break unless name = @c_enclosure_names[variable]

        mod = find_class_or_module name

        unless mod then
          loaded_mod = load_class_data name

          file = loaded_mod.in_files.first

          return unless file # legacy data source

          file.store = self

          mod = file.add_module RDoc::NormalModule, name
        end

        @c_enclosure_classes[variable] = mod
      end
    end

Finds the enclosure (namespace) for the given C `variable`.

find_class_named (name)
Source

    # File lib/rdoc/store.rb, line 402
    def find_class_named(name)
      @classes_hash[name]
    end

Finds the class with `name` in all discovered classes

find_class_named_from (name, from)
Source

    # File lib/rdoc/store.rb, line 409
    def find_class_named_from(name, from)
      from = find_class_named from unless RDoc::Context === from

      until RDoc::TopLevel === from do
        return nil unless from

        klass = from.find_class_named name
        return klass if klass

        from = from.parent
      end

      find_class_named name
    end

Finds the class with `name` starting in namespace `from`

find_class_or_module (name)
Source

    # File lib/rdoc/store.rb, line 427
    def find_class_or_module(name)
      name = $' if name =~ /^::/
      @classes_hash[name] || @modules_hash[name]
    end

Finds the class or module with `name`

find_file_named (name)
Source

    # File lib/rdoc/store.rb, line 435
    def find_file_named(name)
      @files_hash[name]
    end

Finds the file with `name` in all discovered files

find_module_named (name)
Source

    # File lib/rdoc/store.rb, line 442
    def find_module_named(name)
      @modules_hash[name]
    end

Finds the module with `name` in all discovered modules

find_text_page (file_name)
Source

    # File lib/rdoc/store.rb, line 450
    def find_text_page(file_name)
      @text_files_hash.each_value.find do |file|
        file.full_name == file_name
      end
    end

Returns the RDoc::TopLevel that is a text file and has the given `file_name`

find_unique (all_hash)
Source

    # File lib/rdoc/store.rb, line 463
    def find_unique(all_hash)
      unique = []

      all_hash.each_pair do |full_name, cm|
        unique << cm if full_name == cm.full_name
      end

      unique
    end

Finds unique classes/modules defined in `all_hash`, and returns them as an array. Performs the alias updates in `all_hash`: see ::complete.

fix_basic_object_inheritance ()
Source

    # File lib/rdoc/store.rb, line 482
    def fix_basic_object_inheritance
      basic = classes_hash['BasicObject']
      return unless basic
      basic.superclass = nil
    end

Fixes the erroneous `BasicObject < Object` in 1.9.

Because we assumed all classes without a stated superclass inherit from `Object`, we have the above wrong inheritance.

We fix `BasicObject` right away if we are running in a Ruby version >= 1.9.

friendly_path ()
Source

    # File lib/rdoc/store.rb, line 491
    def friendly_path
      case type
      when :gem    then
        parent = File.expand_path '..', @path
        "gem #{File.basename parent}"
      when :home   then RDoc.home
      when :site   then 'ruby site'
      when :system then 'ruby core'
      else @path
      end
    end

Friendly rendition of `path`

instance_methods ()
Source

    # File lib/rdoc/store.rb, line 511
    def instance_methods
      @cache[:instance_methods]
    end

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

load_all ()
Source

    # File lib/rdoc/store.rb, line 519
    def load_all
      load_cache

      module_names.each do |module_name|
        mod = find_class_or_module(module_name) || load_class(module_name)

        # load method documentation since the loaded class/module does not have
        # it
        loaded_methods = mod.method_list.map do |method|
          load_method module_name, method.full_name
        end

        mod.method_list.replace loaded_methods

        loaded_attributes = mod.attributes.map do |attribute|
          load_method module_name, attribute.full_name
        end

        mod.attributes.replace loaded_attributes
      end

      all_classes_and_modules.each do |mod|
        descendent_re = /^#{mod.full_name}::[^:]+$/

        module_names.each do |name|
          next unless name =~ descendent_re

          descendent = find_class_or_module name

          case descendent
          when RDoc::NormalClass then
            mod.classes_hash[name] = descendent
          when RDoc::NormalModule then
            mod.modules_hash[name] = descendent
          end
        end
      end

      @cache[:pages].each do |page_name|
        page = load_page page_name
        @files_hash[page_name] = page
        @text_files_hash[page_name] = page if page.text?
      end
    end

Loads all items from this store into memory. This recreates a documentation tree for use by a generator

load_cache ()
Source

    # File lib/rdoc/store.rb, line 567
    def load_cache
      #orig_enc = @encoding

      @cache = marshal_load(cache_path)

      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 different 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[:pages]                       ||= []
      @cache[:main]                        ||= nil
      @cache[:c_class_variables]           ||= {}
      @cache[:c_singleton_class_variables] ||= {}

      @cache[:c_class_variables].each do |_, map|
        map.each do |variable, name|
          @c_enclosure_names[variable] = name
        end
      end

      @cache
    rescue Errno::ENOENT
    end

Loads cache file for this store

load_class (klass_name)
Source

    # File lib/rdoc/store.rb, line 606
    def load_class(klass_name)
      obj = load_class_data klass_name

      obj.store = self

      case obj
      when RDoc::NormalClass then
        @classes_hash[klass_name] = obj
      when RDoc::SingleClass then
        @classes_hash[klass_name] = obj
      when RDoc::NormalModule then
        @modules_hash[klass_name] = obj
      end
    end

Loads ri data for `klass_name` and hooks it up to this store.

load_class_data (klass_name)
Source

    # File lib/rdoc/store.rb, line 624
    def load_class_data(klass_name)
      file = class_file klass_name

      marshal_load(file)
    rescue Errno::ENOENT => e
      error = MissingFileError.new(self, file, klass_name)
      error.set_backtrace e.backtrace
      raise error
    end

Loads ri data for `klass_name`

load_method (klass_name, method_name)
Source

    # File lib/rdoc/store.rb, line 637
    def load_method(klass_name, method_name)
      file = method_file klass_name, method_name

      obj = marshal_load(file)
      obj.store = self
      obj.parent ||= find_class_or_module(klass_name) || load_class(klass_name)
      obj
    rescue Errno::ENOENT => e
      error = MissingFileError.new(self, file, klass_name + method_name)
      error.set_backtrace e.backtrace
      raise error
    end

Loads ri data for `method_name` in `klass_name`

load_page (page_name)
Source

    # File lib/rdoc/store.rb, line 653
    def load_page(page_name)
      file = page_file page_name

      obj = marshal_load(file)
      obj.store = self
      obj
    rescue Errno::ENOENT => e
      error = MissingFileError.new(self, file, page_name)
      error.set_backtrace e.backtrace
      raise error
    end

Loads ri data for `page_name`

main ()
Source

    # File lib/rdoc/store.rb, line 669
    def main
      @cache[:main]
    end

Gets the main page for this RDoc store. This page is used as the root of the RDoc server.

main= (page)
Source

    # File lib/rdoc/store.rb, line 676
    def main=(page)
      @cache[:main] = page
    end

Sets the main page for this RDoc store.

make_variable_map (variables)
Source

    # File lib/rdoc/store.rb, line 684
    def make_variable_map(variables)
      map = {}

      variables.each { |variable, class_module|
        map[variable] = class_module.full_name
      }

      map
    end

Converts the variable => ClassModule map `variables` from a C parser into a variable => class name map.

method_file (klass_name, method_name)
Source

    # File lib/rdoc/store.rb, line 697
    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 = method_name.gsub(/\W/) { "%%%02x" % $&[0].ord }

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

Path to the ri data for `method_name` in `klass_name`

module_names ()
Source

    # File lib/rdoc/store.rb, line 711
    def module_names
      @cache[:modules]
    end

Modules cache accessor. An `Array` of all the module (and class) names in the store.

modules_hash ()
Source

    # File lib/rdoc/store.rb, line 718
    def modules_hash
      @modules_hash
    end

`Hash` of all modules known to RDoc

page (name)
Source

    # File lib/rdoc/store.rb, line 725
    def page(name)
      @text_files_hash.each_value.find do |file|
        file.page_name == name or file.base_name == name
      end
    end

Returns the RDoc::TopLevel that is a text file and has the given `name`

page_file (page_name)
Source

    # File lib/rdoc/store.rb, line 734
    def page_file(page_name)
      file_name = File.basename(page_name).gsub('.', '_')

      File.join @path, File.dirname(page_name), "page-#{file_name}.ri"
    end

Path to the ri data for `page_name`

remove_nodoc (all_hash)
Source

    # File lib/rdoc/store.rb, line 745
    def remove_nodoc(all_hash)
      all_hash.keys.each do |name|
        context = all_hash[name]
        all_hash.delete(name) if context.remove_from_documentation?
      end
    end

Removes from `all_hash` the contexts that are nodoc or have no content.

See RDoc::Context#remove_from_documentation?

resolve_c_superclasses ()
Source

    # File lib/rdoc/store.rb, line 200
    def resolve_c_superclasses
      @classes_hash.each_value do |klass|
        if klass.superclass.is_a?(String) && (candidate = find_c_enclosure(klass.superclass))
          klass.superclass = candidate
        end
      end
    end

Make sure any references to C variable names are resolved to the corresponding class.

save ()
Source

    # File lib/rdoc/store.rb, line 755
    def save
      load_cache

      all_classes_and_modules.each do |klass|
        save_class klass

        klass.each_method do |method|
          save_method klass, method
        end

        klass.attributes.each do |attribute|
          save_method klass, attribute
        end
      end

      all_files.each do |file|
        save_page file
      end

      save_cache
    end

Saves all entries in the store

save_cache ()
Source

    # File lib/rdoc/store.rb, line 780
    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[:pages].uniq!
      @cache[:pages].sort!

      @cache[:encoding] = @encoding # this gets set twice due to assert_cache

      @cache[:c_class_variables].merge!           @c_class_variables
      @cache[:c_singleton_class_variables].merge! @c_singleton_class_variables

      return if @dry_run

      File.open cache_path, 'wb' do |io|
        Marshal.dump @cache, io
      end
    end

Writes the cache file for this store

save_class (klass)
Source

    # File lib/rdoc/store.rb, line 807
    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 MissingFileError
      end

      # BasicObject has no ancestors
      ancestors = klass.direct_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].concat ancestors

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

      unless attribute_definitions.empty? then
        @cache[:attributes][full_name] ||= []
        @cache[:attributes][full_name].concat attribute_definitions
      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 }
        attribute_names  = klass.attributes.map { |attr|   attr.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 - attribute_names
        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

      File.open path, 'wb' do |io|
        Marshal.dump klass, io
      end
    end

Writes the ri data for `klass` (or module)

save_method (klass, method)
Source

    # File lib/rdoc/store.rb, line 881
    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

      File.open method_file(full_name, method.full_name), 'wb' do |io|
        Marshal.dump method, io
      end
    end

Writes the ri data for `method` on `klass`

save_page (page)
Source

    # File lib/rdoc/store.rb, line 904
    def save_page(page)
      return unless page.text?

      path = page_file page.full_name

      FileUtils.mkdir_p File.dirname(path) unless @dry_run

      cache[:pages] ||= []
      cache[:pages] << page.full_name

      return if @dry_run

      File.open path, 'wb' do |io|
        Marshal.dump page, io
      end
    end

Writes the ri data for `page`

source ()
Source

    # File lib/rdoc/store.rb, line 930
    def source
      case type
      when :gem    then File.basename File.expand_path '..', @path
      when :home   then 'home'
      when :site   then 'site'
      when :system then 'ruby'
      else @path
      end
    end

Source of the contents of this store.

For a store from a gem the source is the gem name. For a store from the home directory the source is “home”. For system ri store (the standard library documentation) the source is“ruby”. For a store from the site ri directory the store is “site”. For other stores the source is the `path`.

title ()
Source

    # File lib/rdoc/store.rb, line 944
    def title
      @cache[:title]
    end

Gets the title for this RDoc store. This is used as the title in each page on the RDoc server

title= (title)
Source

    # File lib/rdoc/store.rb, line 951
    def title=(title)
      @cache[:title] = title
    end

Sets the title page for this RDoc store.

unique_classes ()
Source

    # File lib/rdoc/store.rb, line 960
    def unique_classes
      @unique_classes
    end

Returns the unique classes discovered by RDoc.

::complete must have been called prior to using this method.

unique_classes_and_modules ()
Source

    # File lib/rdoc/store.rb, line 968
    def unique_classes_and_modules
      @unique_classes + @unique_modules
    end

Returns the unique classes and modules discovered by RDoc. ::complete must have been called prior to using this method.

unique_modules ()
Source

    # File lib/rdoc/store.rb, line 976
    def unique_modules
      @unique_modules
    end

Returns the unique modules discovered by RDoc. ::complete must have been called prior to using this method.

update_parser_of_file (absolute_name, parser)
Source

    # File lib/rdoc/store.rb, line 211
    def update_parser_of_file(absolute_name, parser)
      if top_level = @files_hash[absolute_name] then
        @text_files_hash[absolute_name] = top_level if top_level.text?
      end
    end

Sets the parser of `absolute_name`, unless it from a source code file.

### Private Instance Methods

marshal_load (file)
Source

    # File lib/rdoc/store.rb, line 981
    def marshal_load(file)
      File.open(file, 'rb') {|io| Marshal.load(io, MarshalFilter)}
    end