2017-11-27 05:45:24 -05:00
|
|
|
# frozen_string_literal: true
|
2012-11-26 23:28:14 -05:00
|
|
|
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
|
|
|
|
|
2012-12-07 00:22:50 -05:00
|
|
|
def message # :nodoc:
|
2012-11-26 23:28:14 -05:00
|
|
|
"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.
|
|
|
|
|
2012-12-13 02:58:47 -05:00
|
|
|
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
|
2012-11-26 23:28:14 -05:00
|
|
|
|
|
|
|
##
|
|
|
|
# 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
|
|
|
|
|
2018-03-26 01:56:26 -04:00
|
|
|
##
|
|
|
|
# The lazy constants alias will be discovered in passing
|
|
|
|
|
|
|
|
attr_reader :unmatched_constant_alias
|
|
|
|
|
2012-11-26 23:28:14 -05:00
|
|
|
##
|
|
|
|
# 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 = {
|
2012-12-13 02:58:47 -05:00
|
|
|
:ancestors => {},
|
|
|
|
:attributes => {},
|
|
|
|
:class_methods => {},
|
|
|
|
:c_class_variables => {},
|
|
|
|
:c_singleton_class_variables => {},
|
|
|
|
:encoding => @encoding,
|
|
|
|
:instance_methods => {},
|
|
|
|
:main => nil,
|
|
|
|
:modules => [],
|
|
|
|
:pages => [],
|
|
|
|
:title => nil,
|
2012-11-26 23:28:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
@classes_hash = {}
|
|
|
|
@modules_hash = {}
|
|
|
|
@files_hash = {}
|
2018-10-17 02:28:20 -04:00
|
|
|
@text_files_hash = {}
|
2012-11-26 23:28:14 -05:00
|
|
|
|
|
|
|
@c_enclosure_classes = {}
|
2012-12-13 02:58:47 -05:00
|
|
|
@c_enclosure_names = {}
|
|
|
|
|
|
|
|
@c_class_variables = {}
|
|
|
|
@c_singleton_class_variables = {}
|
2012-11-26 23:28:14 -05:00
|
|
|
|
|
|
|
@unique_classes = nil
|
|
|
|
@unique_modules = nil
|
2018-03-26 01:56:26 -04:00
|
|
|
|
|
|
|
@unmatched_constant_alias = {}
|
2012-11-26 23:28:14 -05:00
|
|
|
end
|
|
|
|
|
2012-12-13 02:58:47 -05:00
|
|
|
##
|
|
|
|
# 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
|
|
|
|
|
2012-11-26 23:28:14 -05:00
|
|
|
##
|
|
|
|
# Adds the file with +name+ as an RDoc::TopLevel to the store. Returns the
|
|
|
|
# created RDoc::TopLevel.
|
|
|
|
|
2018-10-17 02:28:20 -04:00
|
|
|
def add_file absolute_name, relative_name: absolute_name, parser: nil
|
2012-11-27 03:54:03 -05:00
|
|
|
unless top_level = @files_hash[relative_name] then
|
|
|
|
top_level = RDoc::TopLevel.new absolute_name, relative_name
|
2018-10-17 02:28:20 -04:00
|
|
|
top_level.parser = parser if parser
|
2012-11-26 23:28:14 -05:00
|
|
|
top_level.store = self
|
2012-11-27 03:54:03 -05:00
|
|
|
@files_hash[relative_name] = top_level
|
2018-10-17 02:28:20 -04:00
|
|
|
@text_files_hash[relative_name] = top_level if top_level.text?
|
2012-11-26 23:28:14 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
top_level
|
|
|
|
end
|
|
|
|
|
2018-10-17 02:28:20 -04:00
|
|
|
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
|
|
|
|
|
2012-11-26 23:28:14 -05:00
|
|
|
##
|
|
|
|
# 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 }
|
|
|
|
|
2013-09-18 19:33:36 -04:00
|
|
|
unless min_visibility == :nodoc then
|
|
|
|
remove_nodoc @classes_hash
|
|
|
|
remove_nodoc @modules_hash
|
|
|
|
end
|
2012-11-26 23:28:14 -05:00
|
|
|
|
|
|
|
@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
|
|
|
|
|
2012-12-13 02:58:47 -05:00
|
|
|
##
|
|
|
|
# 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
|
2012-12-18 02:39:15 -05:00
|
|
|
|
|
|
|
return unless file # legacy data source
|
|
|
|
|
2012-12-13 02:58:47 -05:00
|
|
|
file.store = self
|
|
|
|
|
|
|
|
mod = file.add_module RDoc::NormalModule, name
|
|
|
|
end
|
|
|
|
|
|
|
|
@c_enclosure_classes[variable] = mod
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-11-26 23:28:14 -05:00
|
|
|
##
|
|
|
|
# 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
|
2018-10-17 02:28:20 -04:00
|
|
|
@text_files_hash.each_value.find do |file|
|
|
|
|
file.full_name == file_name
|
2012-11-26 23:28:14 -05:00
|
|
|
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
|
2016-09-06 22:51:12 -04:00
|
|
|
# version >= 1.9.
|
2012-11-26 23:28:14 -05:00
|
|
|
|
|
|
|
def fix_basic_object_inheritance
|
|
|
|
basic = classes_hash['BasicObject']
|
|
|
|
return unless basic
|
2016-09-06 22:51:12 -04:00
|
|
|
basic.superclass = nil
|
2012-11-26 23:28:14 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
##
|
|
|
|
# Friendly rendition of #path
|
|
|
|
|
|
|
|
def friendly_path
|
|
|
|
case type
|
|
|
|
when :gem then
|
|
|
|
parent = File.expand_path '..', @path
|
|
|
|
"gem #{File.basename parent}"
|
2020-04-23 06:16:06 -04:00
|
|
|
when :home then RDoc.home
|
2012-11-26 23:28:14 -05:00
|
|
|
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
|
2018-10-17 02:28:20 -04:00
|
|
|
@text_files_hash[page_name] = page if page.text?
|
2012-11-26 23:28:14 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
##
|
|
|
|
# Loads cache file for this store
|
|
|
|
|
|
|
|
def load_cache
|
|
|
|
#orig_enc = @encoding
|
|
|
|
|
2018-03-26 01:56:26 -04:00
|
|
|
File.open cache_path, 'rb' do |io|
|
2012-11-26 23:28:14 -05:00
|
|
|
@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
|
|
|
|
|
2012-12-13 02:58:47 -05:00
|
|
|
@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
|
2012-11-26 23:28:14 -05:00
|
|
|
|
|
|
|
@cache
|
|
|
|
rescue Errno::ENOENT
|
|
|
|
end
|
|
|
|
|
|
|
|
##
|
2012-12-13 02:58:47 -05:00
|
|
|
# Loads ri data for +klass_name+ and hooks it up to this store.
|
2012-11-26 23:28:14 -05:00
|
|
|
|
|
|
|
def load_class klass_name
|
2012-12-13 02:58:47 -05:00
|
|
|
obj = load_class_data klass_name
|
2012-11-26 23:28:14 -05:00
|
|
|
|
|
|
|
obj.store = self
|
|
|
|
|
|
|
|
case obj
|
|
|
|
when RDoc::NormalClass then
|
|
|
|
@classes_hash[klass_name] = obj
|
2018-03-26 01:56:26 -04:00
|
|
|
when RDoc::SingleClass then
|
|
|
|
@classes_hash[klass_name] = obj
|
2012-11-26 23:28:14 -05:00
|
|
|
when RDoc::NormalModule then
|
|
|
|
@modules_hash[klass_name] = obj
|
|
|
|
end
|
2012-12-13 02:58:47 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
##
|
|
|
|
# Loads ri data for +klass_name+
|
|
|
|
|
|
|
|
def load_class_data klass_name
|
|
|
|
file = class_file klass_name
|
|
|
|
|
2018-03-26 01:56:26 -04:00
|
|
|
File.open file, 'rb' do |io|
|
2012-12-13 02:58:47 -05:00
|
|
|
Marshal.load io.read
|
|
|
|
end
|
2012-11-26 23:28:14 -05:00
|
|
|
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
|
|
|
|
|
2018-03-26 01:56:26 -04:00
|
|
|
File.open file, 'rb' do |io|
|
2012-11-26 23:28:14 -05:00
|
|
|
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
|
|
|
|
|
2018-03-26 01:56:26 -04:00
|
|
|
File.open file, 'rb' do |io|
|
2012-11-26 23:28:14 -05:00
|
|
|
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
|
|
|
|
|
2012-12-13 02:58:47 -05:00
|
|
|
##
|
2013-11-18 19:51:06 -05:00
|
|
|
# Converts the variable => ClassModule map +variables+ from a C parser into
|
2012-12-13 02:58:47 -05:00
|
|
|
# a variable => class name map.
|
|
|
|
|
|
|
|
def make_variable_map variables
|
|
|
|
map = {}
|
|
|
|
|
|
|
|
variables.each { |variable, class_module|
|
|
|
|
map[variable] = class_module.full_name
|
|
|
|
}
|
|
|
|
|
|
|
|
map
|
|
|
|
end
|
|
|
|
|
2012-11-26 23:28:14 -05:00
|
|
|
##
|
|
|
|
# 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
|
2016-11-05 05:18:10 -04:00
|
|
|
method_name = method_name.gsub(/\W/) { "%%%02x" % $&[0].ord }
|
2012-11-26 23:28:14 -05:00
|
|
|
|
|
|
|
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
|
2018-10-17 02:28:20 -04:00
|
|
|
@text_files_hash.each_value.find do |file|
|
2020-08-04 02:46:39 -04:00
|
|
|
file.page_name == name or file.base_name == name
|
2012-11-26 23:28:14 -05:00
|
|
|
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
|
|
|
|
|
2012-12-13 02:58:47 -05:00
|
|
|
@cache[:c_class_variables].merge! @c_class_variables
|
|
|
|
@cache[:c_singleton_class_variables].merge! @c_singleton_class_variables
|
|
|
|
|
2012-11-26 23:28:14 -05:00
|
|
|
return if @dry_run
|
|
|
|
|
2018-03-26 01:56:26 -04:00
|
|
|
File.open cache_path, 'wb' do |io|
|
2019-08-16 08:27:05 -04:00
|
|
|
Marshal.dump @cache, io
|
2012-11-26 23:28:14 -05:00
|
|
|
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
|
|
|
|
|
2013-01-24 19:15:08 -05:00
|
|
|
attribute_definitions = klass.attributes.map do |attribute|
|
2012-11-26 23:28:14 -05:00
|
|
|
"#{attribute.definition} #{attribute.name}"
|
|
|
|
end
|
|
|
|
|
2013-01-24 19:15:08 -05:00
|
|
|
unless attribute_definitions.empty? then
|
2012-11-26 23:28:14 -05:00
|
|
|
@cache[:attributes][full_name] ||= []
|
2013-01-24 19:15:08 -05:00
|
|
|
@cache[:attributes][full_name].concat attribute_definitions
|
2012-11-26 23:28:14 -05:00
|
|
|
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 }
|
2013-01-24 19:15:08 -05:00
|
|
|
attribute_names = klass.attributes.map { |attr| attr.name }
|
2012-11-26 23:28:14 -05:00
|
|
|
|
|
|
|
old = @cache[:class_methods][full_name] - class_methods
|
|
|
|
to_delete.concat old.map { |method|
|
|
|
|
method_file full_name, "#{full_name}::#{method}"
|
|
|
|
}
|
|
|
|
|
2013-01-24 19:15:08 -05:00
|
|
|
old = @cache[:instance_methods][full_name] -
|
|
|
|
instance_methods - attribute_names
|
2012-11-26 23:28:14 -05:00
|
|
|
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
|
|
|
|
|
2018-03-26 01:56:26 -04:00
|
|
|
File.open path, 'wb' do |io|
|
2019-08-16 08:27:05 -04:00
|
|
|
Marshal.dump klass, io
|
2012-11-26 23:28:14 -05:00
|
|
|
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
|
|
|
|
|
2018-03-26 01:56:26 -04:00
|
|
|
File.open method_file(full_name, method.full_name), 'wb' do |io|
|
2019-08-16 08:27:05 -04:00
|
|
|
Marshal.dump method, io
|
2012-11-26 23:28:14 -05:00
|
|
|
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
|
|
|
|
|
2018-03-26 01:56:26 -04:00
|
|
|
File.open path, 'wb' do |io|
|
2019-08-16 08:27:05 -04:00
|
|
|
Marshal.dump page, io
|
2012-11-26 23:28:14 -05:00
|
|
|
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
|