summaryrefslogtreecommitdiff
path: root/lib/rdoc/store.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rdoc/store.rb')
-rw-r--r--lib/rdoc/store.rb968
1 files changed, 0 insertions, 968 deletions
diff --git a/lib/rdoc/store.rb b/lib/rdoc/store.rb
deleted file mode 100644
index aaefff8f13..0000000000
--- a/lib/rdoc/store.rb
+++ /dev/null
@@ -1,968 +0,0 @@
-# frozen_string_literal: false
-require 'fileutils'
-
-##
-# 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
-# }
-#--
-# TODO need to prune classes
-
-class RDoc::Store
-
- ##
- # Errors raised from loading or saving the store
-
- class Error < RDoc::Error
- end
-
- ##
- # Raised when a stored file for a class, module, page or method is missing.
-
- class MissingFileError < Error
-
- ##
- # The store the file should exist in
-
- attr_reader :store
-
- ##
- # The file the #name should be saved as
-
- attr_reader :file
-
- ##
- # The name of the object the #file would be loaded from
-
- attr_reader :name
-
- ##
- # Creates a new MissingFileError for the missing +file+ for the given
- # +name+ that should have been in the +store+.
-
- def initialize store, file, name
- @store = store
- @file = file
- @name = name
- end
-
- def message # :nodoc:
- "store at #{@store.path} missing file #{@file} for #{@name}"
- end
-
- end
-
- ##
- # Stores the name of the C variable a class belongs to. This helps wire up
- # classes defined from C across files.
-
- attr_reader :c_enclosure_classes # :nodoc:
-
- attr_reader :c_enclosure_names # :nodoc:
-
- ##
- # Maps C variables to class or module names for each parsed C file.
-
- attr_reader :c_class_variables
-
- ##
- # Maps C variables to singleton class names for each parsed C file.
-
- attr_reader :c_singleton_class_variables
-
- ##
- # If true this Store will not write any files
-
- attr_accessor :dry_run
-
- ##
- # Path this store reads or writes
-
- attr_accessor :path
-
- ##
- # The RDoc::RDoc driver for this parse tree. This allows classes consulting
- # the documentation tree to access user-set options, for example.
-
- attr_accessor :rdoc
-
- ##
- # 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 = nil, type = nil
- @dry_run = false
- @encoding = nil
- @path = path
- @rdoc = nil
- @type = type
-
- @cache = {
- :ancestors => {},
- :attributes => {},
- :class_methods => {},
- :c_class_variables => {},
- :c_singleton_class_variables => {},
- :encoding => @encoding,
- :instance_methods => {},
- :main => nil,
- :modules => [],
- :pages => [],
- :title => nil,
- }
-
- @classes_hash = {}
- @modules_hash = {}
- @files_hash = {}
-
- @c_enclosure_classes = {}
- @c_enclosure_names = {}
-
- @c_class_variables = {}
- @c_singleton_class_variables = {}
-
- @unique_classes = nil
- @unique_modules = nil
- end
-
- ##
- # Adds +module+ as an enclosure (namespace) for the given +variable+ for C
- # files.
-
- def add_c_enclosure variable, namespace
- @c_enclosure_classes[variable] = namespace
- end
-
- ##
- # Adds C variables from an RDoc::Parser::C
-
- 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 the file with +name+ as an RDoc::TopLevel to the store. Returns the
- # created RDoc::TopLevel.
-
- def add_file absolute_name, relative_name = absolute_name
- unless top_level = @files_hash[relative_name] then
- top_level = RDoc::TopLevel.new absolute_name, relative_name
- top_level.store = self
- @files_hash[relative_name] = top_level
- end
-
- top_level
- end
-
- ##
- # Returns all classes discovered by RDoc
-
- def all_classes
- @classes_hash.values
- end
-
- ##
- # Returns all classes and modules discovered by RDoc
-
- def all_classes_and_modules
- @classes_hash.values + @modules_hash.values
- end
-
- ##
- # All TopLevels known to RDoc
-
- def all_files
- @files_hash.values
- end
-
- ##
- # Returns all modules discovered by RDoc
-
- def all_modules
- modules_hash.values
- 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
-
- ##
- # Hash of all classes known to RDoc
-
- def classes_hash
- @classes_hash
- 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
-
- ##
- # 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 <tt>--visibility</tt> option.
- #
- # See also RDoc::Context#remove_from_documentation?
-
- 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
-
- ##
- # Hash of all files known to RDoc
-
- def files_hash
- @files_hash
- end
-
- ##
- # Finds the enclosure (namespace) for the given C +variable+.
-
- 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 class with +name+ in all discovered classes
-
- def find_class_named name
- @classes_hash[name]
- end
-
- ##
- # Finds the class with +name+ starting in namespace +from+
-
- 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 or module with +name+
-
- def find_class_or_module name
- name = $' if name =~ /^::/
- @classes_hash[name] || @modules_hash[name]
- end
-
- ##
- # Finds the file with +name+ in all discovered files
-
- def find_file_named name
- @files_hash[name]
- end
-
- ##
- # Finds the module with +name+ in all discovered modules
-
- def find_module_named name
- @modules_hash[name]
- end
-
- ##
- # Returns the RDoc::TopLevel that is a text file and has the given
- # +file_name+
-
- def find_text_page file_name
- @files_hash.each_value.find do |file|
- file.text? and file.full_name == file_name
- end
- end
-
- ##
- # Finds unique classes/modules defined in +all_hash+,
- # and returns them as an array. Performs the alias
- # updates in +all_hash+: see ::complete.
- #--
- # TODO aliases should be registered by Context#add_module_alias
-
- 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
-
- ##
- # Fixes the erroneous <tt>BasicObject < Object</tt> 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.
-
- def fix_basic_object_inheritance
- basic = classes_hash['BasicObject']
- return unless basic
- basic.superclass = nil
- end
-
- ##
- # Friendly rendition of #path
-
- def friendly_path
- case type
- when :gem then
- parent = File.expand_path '..', @path
- "gem #{File.basename parent}"
- when :home then '~/.rdoc'
- 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, module_names.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 all items from this store into memory. This recreates a
- # documentation tree for use by a generator
-
- 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
- end
- 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 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 ri data for +klass_name+ and hooks it up to this store.
-
- 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::NormalModule then
- @modules_hash[klass_name] = obj
- end
- end
-
- ##
- # Loads ri data for +klass_name+
-
- def load_class_data klass_name
- file = class_file klass_name
-
- open file, 'rb' do |io|
- Marshal.load io.read
- end
- rescue Errno::ENOENT => e
- error = MissingFileError.new(self, file, klass_name)
- error.set_backtrace e.backtrace
- raise error
- end
-
- ##
- # Loads ri data for +method_name+ in +klass_name+
-
- def load_method klass_name, method_name
- file = method_file klass_name, method_name
-
- open file, 'rb' do |io|
- obj = Marshal.load io.read
- obj.store = self
- obj.parent =
- find_class_or_module(klass_name) || load_class(klass_name) unless
- obj.parent
- obj
- end
- 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 +page_name+
-
- def load_page page_name
- file = page_file page_name
-
- open file, 'rb' do |io|
- obj = Marshal.load io.read
- obj.store = self
- obj
- end
- rescue Errno::ENOENT => e
- error = MissingFileError.new(self, file, page_name)
- error.set_backtrace e.backtrace
- raise error
- end
-
- ##
- # Gets the main page for this RDoc store. This page is used as the root of
- # the RDoc server.
-
- def main
- @cache[:main]
- end
-
- ##
- # Sets the main page for this RDoc store.
-
- def main= page
- @cache[:main] = page
- end
-
- ##
- # Converts the variable => ClassModule map +variables+ from a C parser into
- # a variable => class name map.
-
- def make_variable_map variables
- map = {}
-
- variables.each { |variable, class_module|
- map[variable] = class_module.full_name
- }
-
- map
- 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 = method_name.gsub(/\W/) { "%%%02x" % $&[0].ord }
-
- File.join class_path(klass_name), "#{method_name}-#{method_type}.ri"
- end
-
- ##
- # Modules cache accessor. An Array of all the module (and class) names in
- # the store.
-
- def module_names
- @cache[:modules]
- end
-
- ##
- # Hash of all modules known to RDoc
-
- def modules_hash
- @modules_hash
- end
-
- ##
- # Returns the RDoc::TopLevel that is a text file and has the given +name+
-
- def page name
- @files_hash.each_value.find do |file|
- file.text? and file.page_name == name
- end
- end
-
- ##
- # Path to the ri data for +page_name+
-
- def page_file page_name
- file_name = File.basename(page_name).gsub('.', '_')
-
- File.join @path, File.dirname(page_name), "page-#{file_name}.ri"
- end
-
- ##
- # Removes from +all_hash+ the contexts that are nodoc or have no content.
- #
- # See RDoc::Context#remove_from_documentation?
-
- 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
-
- ##
- # Saves all entries in the store
-
- 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.each_attribute do |attribute|
- save_method klass, attribute
- end
- end
-
- all_files.each do |file|
- save_page file
- end
-
- save_cache
- 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[: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
-
- marshal = Marshal.dump @cache
-
- open cache_path, 'wb' do |io|
- io.write marshal
- end
- end
-
- ##
- # Writes the ri data for +klass+ (or module)
-
- 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
-
- 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
-
- ##
- # Writes the ri data for +page+
-
- 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
-
- marshal = Marshal.dump page
-
- open path, 'wb' do |io|
- io.write marshal
- 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.
-
- 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
-
- ##
- # Gets the title for this RDoc store. This is used as the title in each
- # page on the RDoc server
-
- def title
- @cache[:title]
- end
-
- ##
- # Sets the title page for this RDoc store.
-
- def title= title
- @cache[:title] = title
- end
-
- ##
- # Returns the unique classes discovered by RDoc.
- #
- # ::complete must have been called prior to using this method.
-
- def unique_classes
- @unique_classes
- end
-
- ##
- # Returns the unique classes and modules discovered by RDoc.
- # ::complete must have been called prior to using this method.
-
- def unique_classes_and_modules
- @unique_classes + @unique_modules
- end
-
- ##
- # Returns the unique modules discovered by RDoc.
- # ::complete must have been called prior to using this method.
-
- def unique_modules
- @unique_modules
- end
-
-end